add string descriptions

main
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

@ -49,7 +49,7 @@
"build": "GENERATE_SOURCEMAP=false craco build",
"test": "craco test",
"eject": "craco eject",
"extract": "formatjs extract 'src/**/*.ts*' --ignore='**/*.d.ts' --out-file public/lang/en-us.json --format simple --id-interpolation-pattern '[sha512:contenthash:base64:6]'"
"extract": "formatjs extract 'src/**/*.ts*' --ignore='**/*.d.ts' --out-file public/lang/en-us.json --id-interpolation-pattern '[sha512:contenthash:base64:6]'"
},
"eslintConfig": {
"extends": [

View File

@ -1,61 +1,222 @@
{
"+G35mR": "Open sidebar",
"/OKZrc": "Find your API key here.",
"0vL5u1": "Create an account",
"1W07dM": "No chats yet.",
"2GFjIN": "Enter your password",
"3T9nRn": "Your API key is stored only on this device and never transmitted to anyone except OpenAI.",
"3kbIhS": "Untitled",
"47FYwb": "Cancel",
"4I+enA": "GPT 4 (requires invite)",
"4l6vz1": "Copy",
"5sg7KC": "Password",
"6PgVSe": "Regenerate",
"8HJxXG": "Sign up",
"A4iXFN": "Temperature: {temperature, number, ::.0}",
"AfTuRr": "User settings",
"BdPrnc": "Chat with GPT - Unofficial ChatGPT app",
"BwIZY+": "System Prompt",
"CJwO9s": "GPT 3.5 Turbo (default)",
"ECx3EW": "Chat with GPT",
"ExZfjk": "Sign in <h>to sync</h>",
"HIqSlE": "Preview voice",
"HyS0qp": "Close sidebar",
"J3ca41": "Play",
"KKa5Br": "Give ChatGPT a realisic human voice by connecting your ElevenLabs account (preview the available voices below). <a>Click here to sign up.</a>",
"KMq1sg": "Or create an account",
"KbaJTs": "Loading audio...",
"L5s+z7": "OpenAI API key usage is billed at a pay-as-you-go rate, separate from your ChatGPT subscription.",
"MI5gZ+": "Download SVG",
"NRJ4IQ": "Note: GPT-4 will only work if your OpenAI account has been granted access to the new model. <a>Request access here.</a>",
"NgCT/u": "Enter your email address",
"O83lC6": "Enter a message here...",
"OKhRC6": "Share",
"Q97T+z": "Paste your API key here",
"SQJto2": "Sign in",
"UT7Nkj": "New Chat",
"Ua8luY": "Hello, how can I help you today?",
"VL24Xt": "Search your chats",
"X0ha1a": "Save changes",
"Xzm66E": "Connect your OpenAI account to get started",
"b/VC/6": "Or sign in to an existing account",
"c60o5M": "Your OpenAI API Key",
"ghuZox": "Save and Close",
"hJZwTS": "Email address",
"jkpK/t": "Your ElevenLabs Text-to-Speech API Key (optional)",
"jtu3jt": "You can find your API key by clicking your avatar or initials in the top right of the ElevenLabs website, then clicking Profile. Your API key is stored only on this device and never transmitted to anyone except ElevenLabs.",
"mhtiX2": "Customize system prompt",
"mnJYBQ": "Voice",
"p556q3": "Copied",
"q/uwLT": "Stop",
"rhSI1/": "Model",
"role-chatgpt": "ChatGPT",
"role-system": "System",
"role-user": "You",
"role-user-formal": "User",
"rvqkVI": "Chat History",
"sPtnbA": "The System Prompt is shown to ChatGPT by the &quot;System&quot; before your first message. The <code>'{{ datetime }}'</code> tag is automatically replaced by the current date and time.",
"ss6kle": "Reset to default",
"tZdXp/": "The temperature parameter controls the randomness of the AI's responses. Lower values will make the AI more predictable, while higher values will make it more creative.",
"wEQDC6": "Edit"
"+G35mR": {
"defaultMessage": "Open sidebar"
},
"+LMWDJ": {
"defaultMessage": "Chat History",
"description": "Heading for the chat history screen"
},
"/xTTti": {
"defaultMessage": "User settings",
"description": "Menu item that opens the user settings screen"
},
"0vL5u1": {
"defaultMessage": "Create an account"
},
"1j61Mn": {
"defaultMessage": "Or create an account",
"description": "Label for a button on the Sign In page that lets the user create an account instead"
},
"1r/ryM": {
"defaultMessage": "Cancel",
"description": "Label for the button that can be clicked while the AI is generating a response to cancel generation"
},
"2GFjIN": {
"defaultMessage": "Enter your password"
},
"3T9nRn": {
"defaultMessage": "Your API key is stored only on this device and never transmitted to anyone except OpenAI."
},
"4I+enA": {
"defaultMessage": "GPT 4 (requires invite)"
},
"5sg7KC": {
"defaultMessage": "Password"
},
"6LJByb": {
"defaultMessage": "Loading audio...",
"description": "Message indicating that text-to-speech audio is buffering"
},
"74eGxP": {
"defaultMessage": "New Chat",
"description": "Label for the button used to start a new chat session"
},
"8CYNFt": {
"defaultMessage": "No chats yet.",
"description": "Message shown on the Chat History screen for new users who haven't started their first chat session"
},
"8HJxXG": {
"defaultMessage": "Sign up"
},
"Bi0lS9": {
"defaultMessage": "Share",
"description": "Label for the button used to create a public share URL for a chat log"
},
"Bm3EKs": {
"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"
},
"CJwO9s": {
"defaultMessage": "GPT 3.5 Turbo (default)"
},
"FEzBCd": {
"defaultMessage": "Untitled",
"description": "default title for untitled chat sessions"
},
"Fko0yT": {
"defaultMessage": "Stop",
"description": "Label for the button that stops text-to-speech playback"
},
"HyS0qp": {
"defaultMessage": "Close sidebar"
},
"JpZMMj": {
"defaultMessage": "Voice",
"description": "Heading for the setting that lets users choose an ElevenLabs text-to-speech voice, on the settings screen"
},
"KKa5Br": {
"defaultMessage": "Give ChatGPT a realisic human voice by connecting your ElevenLabs account (preview the available voices below). <a>Click here to sign up.</a>"
},
"L5s+z7": {
"defaultMessage": "OpenAI API key usage is billed at a pay-as-you-go rate, separate from your ChatGPT subscription."
},
"MI5gZ+": {
"defaultMessage": "Download SVG"
},
"N2NGTf": {
"defaultMessage": "Chat with GPT",
"description": "app name"
},
"NRJ4IQ": {
"defaultMessage": "Note: GPT-4 will only work if your OpenAI account has been granted access to the new model. <a>Request access here.</a>"
},
"NgCT/u": {
"defaultMessage": "Enter your email address"
},
"O83lC6": {
"defaultMessage": "Enter a message here..."
},
"Q97T+z": {
"defaultMessage": "Paste your API key here"
},
"S9BFby": {
"defaultMessage": "Play",
"description": "Label for the button that starts text-to-speech playback"
},
"SQJto2": {
"defaultMessage": "Sign in"
},
"SQh9En": {
"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"
},
"Tgo3vj": {
"defaultMessage": "Edit",
"description": "Label for the button the user can click to edit the text of one of their messages"
},
"VL24Xt": {
"defaultMessage": "Search your chats"
},
"WOuVxP": {
"defaultMessage": "Model",
"description": "Heading for the setting that lets users choose a model to interact with, on the settings screen"
},
"Xzm66E": {
"defaultMessage": "Connect your OpenAI account to get started"
},
"Y0tGn6": {
"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"
},
"bIacvz": {
"defaultMessage": "Chat with GPT - Unofficial ChatGPT app",
"description": "HTML title tag"
},
"cAtzqn": {
"defaultMessage": "System Prompt",
"description": "Heading for the setting that lets users customize the System Prompt, on the settings screen"
},
"cmcjSh": {
"defaultMessage": "Preview voice",
"description": "Label for the button that plays a preview of the selected ElevenLabs text-to-speech voice"
},
"f/hGIY": {
"defaultMessage": "Hello, how can I help you today?",
"description": "A friendly message that appears at the start of new chat sessions"
},
"gzJlXS": {
"defaultMessage": "Share",
"description": "Label for a button which shares the text of a chat message using the user device's share functionality"
},
"hJZwTS": {
"defaultMessage": "Email address"
},
"iqbKb2": {
"defaultMessage": "Save and Close",
"description": "Label for the button that closes the Settings screen, saving any changes"
},
"jtu3jt": {
"defaultMessage": "You can find your API key by clicking your avatar or initials in the top right of the ElevenLabs website, then clicking Profile. Your API key is stored only on this device and never transmitted to anyone except ElevenLabs."
},
"ljHOzQ": {
"defaultMessage": "Copied",
"description": "Label for copy-to-clipboard button after a successful copy"
},
"pv41j8": {
"defaultMessage": "Temperature: {temperature, number, ::.0}",
"description": "Label for the button that opens a modal for setting the 'temperature' (randomness) of AI responses"
},
"raQMIg": {
"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"
},
"role-chatgpt": {
"defaultMessage": "ChatGPT",
"description": "Label that is shown above messages written by the AI (as opposed to the user)"
},
"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)"
},
"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)."
},
"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)."
},
"sPtnbA": {
"defaultMessage": "The System Prompt is shown to ChatGPT by the &quot;System&quot; before your first message. The <code>'{{ datetime }}'</code> tag is automatically replaced by the current date and time."
},
"ss6kle": {
"defaultMessage": "Reset to default"
},
"sskUPZ": {
"defaultMessage": "Your ElevenLabs Text-to-Speech API Key (optional)",
"description": "Heading for the ElevenLabs API key setting on the settings screen"
},
"tZdXp/": {
"defaultMessage": "The temperature parameter controls the randomness of the AI's responses. Lower values will make the AI more predictable, while higher values will make it more creative."
},
"upBSoW": {
"defaultMessage": "Copy",
"description": "Label for copy-to-clipboard button"
},
"w5Dmuu": {
"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."
},
"y1F8Hs": {
"defaultMessage": "Your OpenAI API Key",
"description": "Heading for the OpenAI API key setting on the settings screen"
},
"zBmup+": {
"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."
},
"zFt1cV": {
"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."
}
}

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>
);