add string descriptions

This commit is contained in:
Cogent Apps
2023-03-16 20:05:45 +00:00
parent b216cf2900
commit c4a14110eb
16 changed files with 281 additions and 99 deletions

View File

@@ -63,7 +63,7 @@ export function LoginModal(props: any) {
<FormattedMessage defaultMessage={"Sign in"} />
</Button>
<Button fullWidth variant="subtle" onClick={onCreateAccountClick}>
<FormattedMessage defaultMessage={"Or create an account"} />
<FormattedMessage defaultMessage={"Or create an account"} description="Label for a button on the Sign In page that lets the user create an account instead" />
</Button>
</Container>
</Modal>
@@ -100,7 +100,7 @@ export function CreateAccountModal(props: any) {
<FormattedMessage defaultMessage={"Sign up"} />
</Button>
<Button fullWidth variant="subtle" onClick={onSignInClick}>
<FormattedMessage defaultMessage={"Or sign in to an existing account"} />
<FormattedMessage defaultMessage={"Or sign in to an existing account"} description="Label for a button on the Create Account page that lets the user sign into their existing account instead" />
</Button>
</Container>
</Modal>

View File

@@ -160,16 +160,16 @@ export default function Header(props: HeaderProps) {
<Helmet>
<title>
{props.title ? `${props.title} - ` : ''}
{intl.formatMessage({ defaultMessage: "Chat with GPT - Unofficial ChatGPT app" })}
{intl.formatMessage({ defaultMessage: "Chat with GPT - Unofficial ChatGPT app", description: "HTML title tag" })}
</title>
</Helmet>
{!sidebarOpen && <Burger opened={sidebarOpen} onClick={onBurgerClick} aria-label={burgerLabel} transitionDuration={0} />}
{context.isHome && <h2>{intl.formatMessage({ defaultMessage: "Chat with GPT" })}</h2>}
{context.isHome && <h2>{intl.formatMessage({ defaultMessage: "Chat with GPT", description: "app name" })}</h2>}
<div className="spacer" />
<HeaderButton icon="search" onClick={spotlight.openSpotlight} />
<HeaderButton icon="gear" onClick={openSettings} />
{backend.current && !props.share && props.canShare && typeof navigator.share !== 'undefined' && <HeaderButton icon="share" onClick={props.onShare}>
<FormattedMessage defaultMessage="Share" />
<FormattedMessage defaultMessage="Share" description="Label for the button used to create a public share URL for a chat log" />
</HeaderButton>}
{backend.current && !context.authenticated && (
<HeaderButton onClick={() => {
@@ -180,13 +180,14 @@ export default function Header(props: HeaderProps) {
}
}}>
<FormattedMessage defaultMessage="Sign in <h>to sync</h>"
description="Label for sign in button, indicating the purpose of signing in is to sync your data between devices"
values={{
h: (chunks: any) => <span className="hide-on-mobile">{chunks}</span>
}} />
</HeaderButton>
)}
<HeaderButton icon="plus" onClick={onNewChat} loading={loading} variant="light">
<FormattedMessage defaultMessage="New Chat" />
<FormattedMessage defaultMessage="New Chat" description="Label for the button used to start a new chat session" />
</HeaderButton>
</HeaderContainer>
), [sidebarOpen, onBurgerClick, props.title, props.share, props.canShare, props.onShare, openSettings, onNewChat, loading, context.authenticated, context.isHome, context.isShare, spotlight.openSpotlight]);

View File

@@ -79,7 +79,7 @@ export default function MessageInput(props: MessageInputProps) {
<Button variant="subtle" size="xs" compact onClick={() => {
context.chat.cancelReply(context.currentChat.leaf!.id);
}}>
<FormattedMessage defaultMessage={"Cancel"} />
<FormattedMessage defaultMessage={"Cancel"} description="Label for the button that can be clicked while the AI is generating a response to cancel generation" />
</Button>
<Loader size="xs" style={{ padding: '0 0.8rem 0 0.5rem' }} />
</>)}
@@ -119,7 +119,7 @@ export default function MessageInput(props: MessageInputProps) {
compact
onClick={onCustomizeSystemPromptClick}>
<span>
<FormattedMessage defaultMessage={"Customize system prompt"} />
<FormattedMessage defaultMessage={"Customize system prompt"} description="Label for the button that opens a modal for customizing the 'system prompt', a message used to customize and influence how the AI responds." />
</span>
</Button>
<Button variant="subtle"
@@ -129,6 +129,7 @@ export default function MessageInput(props: MessageInputProps) {
onClick={onTemperatureClick}>
<span>
<FormattedMessage defaultMessage="Temperature: {temperature, number, ::.0}"
description="Label for the button that opens a modal for setting the 'temperature' (randomness) of AI responses"
values={{ temperature }} />
</span>
</Button>

View File

@@ -97,7 +97,10 @@ export function Markdown(props: MarkdownProps) {
{({ copy, copied }) => (
<Button variant="subtle" size="sm" compact onClick={copy}>
<i className="fa fa-clipboard" />
<span>{copied ? <FormattedMessage defaultMessage="Copied" /> : <FormattedMessage defaultMessage="Copy" />}</span>
<span>
{copied ? <FormattedMessage defaultMessage="Copied" description="Label for copy-to-clipboard button after a successful copy" />
: <FormattedMessage defaultMessage="Copy" description="Label for copy-to-clipboard button" />}
</span>
</Button>
)}
</CopyButton>

View File

@@ -208,15 +208,15 @@ export default function MessageComponent(props: { message: Message, last: boolea
switch (role) {
case 'user':
if (share) {
return intl.formatMessage({ id: 'role-user-formal', defaultMessage: 'User' });
return intl.formatMessage({ id: 'role-user-formal', defaultMessage: 'User', description: "Label that is shown above messages written by the user (as opposed to the AI) for publicly shared conversation (third person, formal)." });
} else {
return intl.formatMessage({ id: 'role-user', defaultMessage: 'You' });
return intl.formatMessage({ id: 'role-user', defaultMessage: 'You', description: "Label that is shown above messages written by the user (as opposed to the AI) in the user's own chat sessions (first person)." });
}
break;
case 'assistant':
return intl.formatMessage({ id: 'role-chatgpt', defaultMessage: 'ChatGPT' });
return intl.formatMessage({ id: 'role-chatgpt', defaultMessage: 'ChatGPT', description: "Label that is shown above messages written by the AI (as opposed to the user)" });
case 'system':
return intl.formatMessage({ id: 'role-system', defaultMessage: 'System' });
return intl.formatMessage({ id: 'role-system', defaultMessage: 'System', description: "Label that is shown above messages inserted into the conversation automatically by the system (as opposed to either the user or AI)" });
default:
return role;
}
@@ -243,7 +243,8 @@ export default function MessageComponent(props: { message: Message, last: boolea
{({ copy, copied }) => (
<Button variant="subtle" size="sm" compact onClick={copy} style={{ marginLeft: '1rem' }}>
<i className="fa fa-clipboard" />
<span>{copied ? <FormattedMessage defaultMessage="Copied" /> : <FormattedMessage defaultMessage="Copy" />}</span>
{copied ? <FormattedMessage defaultMessage="Copied" description="Label for copy-to-clipboard button after a successful copy" />
: <FormattedMessage defaultMessage="Copy" description="Label for copy-to-clipboard button" />}
</Button>
)}
</CopyButton>
@@ -251,7 +252,7 @@ export default function MessageComponent(props: { message: Message, last: boolea
<Button variant="subtle" size="sm" compact onClick={() => share(props.message.content)}>
<i className="fa fa-share" />
<span>
<FormattedMessage defaultMessage="Share" />
<FormattedMessage defaultMessage="Share" description="Label for a button which shares the text of a chat message using the user device's share functionality" />
</span>
</Button>
)}
@@ -261,14 +262,17 @@ export default function MessageComponent(props: { message: Message, last: boolea
setEditing(v => !v);
}}>
<i className="fa fa-edit" />
<span>{editing ? <FormattedMessage defaultMessage="Cancel" /> : <FormattedMessage defaultMessage="Edit" />}</span>
<span>
{editing ? <FormattedMessage defaultMessage="Cancel" description="Label for a button that appears when the user is editing the text of one of their messages, to cancel without saving changes" />
: <FormattedMessage defaultMessage="Edit" description="Label for the button the user can click to edit the text of one of their messages" />}
</span>
</Button>
)}
{!context.isShare && props.message.role === 'assistant' && (
<Button variant="subtle" size="sm" compact onClick={() => context.regenerateMessage(props.message)}>
<i className="fa fa-refresh" />
<span>
<FormattedMessage defaultMessage="Regenerate" />
<FormattedMessage defaultMessage="Regenerate" description="Label for the button used to ask the AI to regenerate one of its messages. Since message generations are stochastic, the resulting message will be different." />
</span>
</Button>
)}
@@ -279,10 +283,10 @@ export default function MessageComponent(props: { message: Message, last: boolea
onChange={e => setContent(e.currentTarget.value)}
autosize={true} />
<Button variant="light" onClick={() => context.editMessage(props.message, content)}>
<FormattedMessage defaultMessage="Save changes" />
<FormattedMessage defaultMessage="Save changes" description="Label for a button that appears when the user is editing the text of one of their messages, to save the changes" />
</Button>
<Button variant="subtle" onClick={() => setEditing(false)}>
<FormattedMessage defaultMessage="Cancel" />
<FormattedMessage defaultMessage="Cancel" description="Label for a button that appears when the user is editing the text of one of their messages, to cancel without saving changes" />
</Button>
</Editor>)}
</div>

View File

@@ -27,7 +27,8 @@ export default function LandingPage(props: any) {
return <Page id={'landing'} showSubHeader={true}>
<Container>
<p>
<FormattedMessage defaultMessage={'Hello, how can I help you today?'} />
<FormattedMessage defaultMessage={'Hello, how can I help you today?'}
description="A friendly message that appears at the start of new chat sessions" />
</p>
{!openAIApiKey && (
<Button size="xs" variant="light" compact onClick={onConnectButtonClick}>

View File

@@ -103,7 +103,9 @@ export default function SettingsDrawer(props: SettingsDrawerProps) {
</Tabs>
<div id="save">
<Button variant="light" fullWidth size="md" onClick={close}>
<FormattedMessage defaultMessage={"Save and Close"} />
<FormattedMessage defaultMessage={"Save and Close"}
description="Label for the button that closes the Settings screen, saving any changes"
/>
</Button>
</div>
</Container>

View File

@@ -30,7 +30,7 @@ export default function GenerationOptionsTab(props: any) {
&& (model?.trim() !== defaultModel.trim());
const systemPromptOption = useMemo(() => (
<SettingsOption heading={intl.formatMessage({ defaultMessage: "System Prompt" })}
<SettingsOption heading={intl.formatMessage({ defaultMessage: "System Prompt", description: "Heading for the setting that lets users customize the System Prompt, on the settings screen" })}
focused={option === 'system-prompt'}>
<Textarea
value={initialSystemPrompt || defaultSystemPrompt}
@@ -49,7 +49,7 @@ export default function GenerationOptionsTab(props: any) {
), [option, initialSystemPrompt, resettableSystemPromopt, onSystemPromptChange, onResetSystemPrompt]);
const modelOption = useMemo(() => (
<SettingsOption heading={intl.formatMessage({ defaultMessage: "Model" })}
<SettingsOption heading={intl.formatMessage({ defaultMessage: "Model", description: "Heading for the setting that lets users choose a model to interact with, on the settings screen" })}
focused={option === 'model'}>
<Select
value={model || defaultModel}
@@ -77,7 +77,10 @@ export default function GenerationOptionsTab(props: any) {
), [option, model, resettableModel, onModelChange, onResetModel]);
const temperatureOption = useMemo(() => (
<SettingsOption heading={intl.formatMessage({ defaultMessage: "Temperature: {temperature, number, ::.0}", }, { temperature })}
<SettingsOption heading={intl.formatMessage({
defaultMessage: "Temperature: {temperature, number, ::.0}",
description: "Label for the button that opens a modal for setting the 'temperature' (randomness) of AI responses",
}, { temperature })}
focused={option === 'temperature'}>
<Slider value={temperature} onChange={onTemperatureChange} step={0.1} min={0} max={1} precision={3} />
<p>

View File

@@ -33,7 +33,7 @@ export default function SpeechOptionsTab() {
}, [elevenLabsApiKey]);
const apiKeyOption = useMemo(() => (
<SettingsOption heading={intl.formatMessage({ defaultMessage: 'Your ElevenLabs Text-to-Speech API Key (optional)' })}
<SettingsOption heading={intl.formatMessage({ defaultMessage: 'Your ElevenLabs Text-to-Speech API Key (optional)', description: "Heading for the ElevenLabs API key setting on the settings screen" })}
focused={option === 'elevenlabs-api-key'}>
<TextInput placeholder={intl.formatMessage({ defaultMessage: "Paste your API key here" })}
value={elevenLabsApiKey || ''} onChange={onElevenLabsApiKeyChange} />
@@ -50,7 +50,7 @@ export default function SpeechOptionsTab() {
), [option, elevenLabsApiKey, onElevenLabsApiKeyChange]);
const voiceOption = useMemo(() => (
<SettingsOption heading={intl.formatMessage({ defaultMessage: 'Voice' })}
<SettingsOption heading={intl.formatMessage({ defaultMessage: 'Voice', description: 'Heading for the setting that lets users choose an ElevenLabs text-to-speech voice, on the settings screen' })}
focused={option === 'elevenlabs-voice'}>
<Select
value={voice}
@@ -64,7 +64,7 @@ export default function SpeechOptionsTab() {
<Button onClick={() => (document.getElementById('voice-preview') as HTMLMediaElement)?.play()} variant='light' compact style={{ marginTop: '1rem' }}>
<i className='fa fa-headphones' />
<span>
<FormattedMessage defaultMessage="Preview voice" />
<FormattedMessage defaultMessage="Preview voice" description="Label for the button that plays a preview of the selected ElevenLabs text-to-speech voice" />
</span>
</Button>
</SettingsOption>

View File

@@ -17,7 +17,7 @@ export default function UserOptionsTab(props: any) {
const elem = useMemo(() => (
<SettingsTab name="user">
<SettingsOption heading={intl.formatMessage({ defaultMessage: "Your OpenAI API Key" })}
<SettingsOption heading={intl.formatMessage({ defaultMessage: "Your OpenAI API Key", description: "Heading for the OpenAI API key setting on the settings screen" })}
focused={option === 'openai-api-key'}>
<TextInput
placeholder={intl.formatMessage({ defaultMessage: "Paste your API key here" })}
@@ -25,7 +25,7 @@ export default function UserOptionsTab(props: any) {
onChange={onOpenAIApiKeyChange} />
<p>
<a href="https://platform.openai.com/account/api-keys" target="_blank" rel="noreferrer">
<FormattedMessage defaultMessage="Find your API key here." />
<FormattedMessage defaultMessage="Find your API key here." description="Label for the link that takes the user to the page on the OpenAI website where they can find their API key." />
</a>
</p>
<p>

View File

@@ -115,7 +115,7 @@ export default function Sidebar(props: {
const elem = useMemo(() => (
<Container className={"sidebar " + (sidebarOpen ? 'opened' : 'closed')} ref={ref}>
<div className="sidebar-header">
<h2><FormattedMessage defaultMessage={"Chat History"} /></h2>
<h2><FormattedMessage defaultMessage={"Chat History"} description="Heading for the chat history screen" /></h2>
<Burger opened={sidebarOpen} onClick={onBurgerClick} aria-label={burgerLabel} transitionDuration={0} />
</div>
<div className="sidebar-content">
@@ -141,7 +141,7 @@ export default function Sidebar(props: {
<Menu.Item onClick={() => {
dispatch(setTab('user'));
}} icon={<i className="fas fa-gear" />}>
<FormattedMessage defaultMessage={"User settings"} />
<FormattedMessage defaultMessage={"User settings"} description="Menu item that opens the user settings screen" />
</Menu.Item>
{/*
<Menu.Divider />

View File

@@ -84,12 +84,12 @@ export default function RecentChats(props: any) {
onClick={onClick}
data-chat-id={c.chatID}
className={c.chatID === currentChatID ? 'selected' : ''}>
<strong>{c.title || <FormattedMessage defaultMessage={"Untitled"} />}</strong>
<strong>{c.title || <FormattedMessage defaultMessage={"Untitled"} description="default title for untitled chat sessions" />}</strong>
</ChatListItem>
))}
</ChatList>}
{recentChats.length === 0 && <Empty>
<FormattedMessage defaultMessage={"No chats yet."} />
<FormattedMessage defaultMessage={"No chats yet."} description="Message shown on the Chat History screen for new users who haven't started their first chat session" />
</Empty>}
</Container>
);

View File

@@ -47,11 +47,17 @@ const root = ReactDOM.createRoot(
);
async function loadLocaleData(locale: string) {
const messages = await fetch(`/lang/${locale}.json`);
if (!messages.ok) {
const response = await fetch(`/lang/${locale}.json`);
if (!response.ok) {
throw new Error("Failed to load locale data");
}
return messages.json()
const messages: any = await response.json();
for (const key of Object.keys(messages)) {
if (typeof messages[key] !== 'string') {
messages[key] = messages[key].defaultMessage;
}
}
return messages;
}
async function bootstrapApplication() {

View File

@@ -272,13 +272,13 @@ export function ElevenLabsReaderButton(props: { selector: string }) {
<Button variant="subtle" size="sm" compact onClickCapture={onClick} loading={status === 'init'}>
{status !== 'init' && <i className="fa fa-headphones" />}
{status === 'idle' && <span>
<FormattedMessage defaultMessage="Play" />
<FormattedMessage defaultMessage="Play" description="Label for the button that starts text-to-speech playback" />
</span>}
{status === 'buffering' && <span>
<FormattedMessage defaultMessage="Loading audio..." />
<FormattedMessage defaultMessage="Loading audio..." description="Message indicating that text-to-speech audio is buffering" />
</span>}
{status !== 'idle' && status !== 'buffering' && <span>
<FormattedMessage defaultMessage="Stop" />
<FormattedMessage defaultMessage="Stop" description="Label for the button that stops text-to-speech playback" />
</span>}
</Button>
);