🌐 refactor: Interpolate Localization Keys (#10650)

* fix: replace string concatenation of localization keys with interpolations and add keys for unlocalized string literals

* chore: update test for new localization key

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
This commit is contained in:
Dustin Healy 2025-11-25 12:19:49 -08:00 committed by Danny Avila
parent 39cecc97bd
commit b6dcefc53a
No known key found for this signature in database
GPG key ID: BF31EEB2C5CA0956
28 changed files with 130 additions and 85 deletions

View file

@ -154,9 +154,9 @@ const MarketplaceAdminSettings = () => {
</Button>
</OGDialogTrigger>
<OGDialogContent className="w-11/12 max-w-md border-border-light bg-surface-primary text-text-primary">
<OGDialogTitle>{`${localize('com_ui_admin_settings')} - ${localize(
'com_ui_marketplace',
)}`}</OGDialogTitle>
<OGDialogTitle>
{localize('com_ui_admin_settings_section', { section: localize('com_ui_marketplace') })}
</OGDialogTitle>
<div className="p-2">
{/* Role selection dropdown */}
<div className="flex items-center gap-2">

View file

@ -21,7 +21,7 @@ function MCPSelectContent() {
const renderSelectedValues = useCallback(
(values: string[], placeholder?: string) => {
if (values.length === 0) {
return placeholder || localize('com_ui_select') + '...';
return placeholder || localize('com_ui_select_placeholder');
}
if (values.length === 1) {
return values[0];

View file

@ -145,7 +145,7 @@ const EditPresetDialog = ({
<OGDialog open={presetModalVisible} onOpenChange={handleOpenChange}>
<OGDialogContent className="h-[100dvh] max-h-[100dvh] w-full max-w-full overflow-y-auto bg-white dark:border-gray-700 dark:bg-gray-850 dark:text-gray-300 md:h-auto md:max-h-[90vh] md:max-w-[75vw] md:rounded-lg lg:max-w-[950px]">
<OGDialogTitle>
{`${localize('com_ui_edit')} ${localize('com_endpoint_preset')} - ${preset?.title}`}
{localize('com_ui_edit_preset_title', { title: preset?.title })}
</OGDialogTitle>
<div className="flex w-full flex-col gap-2 px-1 pb-4 md:gap-4">

View file

@ -63,7 +63,7 @@ const PresetItems: FC<{
<button
type="button"
className="mr-1 flex h-[32px] cursor-pointer items-center rounded bg-transparent px-2 py-1 text-xs font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-red-700 focus:ring-ring dark:bg-transparent dark:text-gray-300 dark:hover:bg-gray-700 dark:hover:text-red-700"
aria-label={localize('com_ui_clear') + ' ' + localize('com_ui_all')}
aria-label={localize('com_ui_clear_all')}
>
<svg
width="24"
@ -76,12 +76,12 @@ const PresetItems: FC<{
>
<path d="M9.293 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4.707A1 1 0 0 0 13.707 4L10 .293A1 1 0 0 0 9.293 0M9.5 3.5v-2l3 3h-2a1 1 0 0 1-1-1M6.854 7.146 8 8.293l1.146-1.147a.5.5 0 1 1 .708.708L8.707 9l1.147 1.146a.5.5 0 0 1-.708.708L8 9.707l-1.146 1.147a.5.5 0 0 1-.708-.708L7.293 9 6.146 7.854a.5.5 0 1 1 .708-.708"></path>
</svg>
{localize('com_ui_clear')} {localize('com_ui_all')}
{localize('com_ui_clear_all')}
</button>
</DialogTrigger>
<DialogTemplate
showCloseButton={false}
title={`${localize('com_ui_clear')} ${localize('com_endpoint_presets')}`}
title={localize('com_ui_clear_presets')}
className="max-w-[450px]"
main={
<>

View file

@ -137,7 +137,9 @@ export default function Conversation({ conversation, retainView, toggleNav }: Co
)}
role="button"
tabIndex={renaming ? -1 : 0}
aria-label={`${title || localize('com_ui_untitled')} conversation`}
aria-label={localize('com_ui_conversation_label', {
title: title || localize('com_ui_untitled'),
})}
onClick={(e) => {
if (renaming) {
return;

View file

@ -1,4 +1,5 @@
import React, { useCallback } from 'react';
import { Trans } from 'react-i18next';
import { QueryKeys } from 'librechat-data-provider';
import { useQueryClient } from '@tanstack/react-query';
import { useParams, useNavigate } from 'react-router-dom';
@ -80,7 +81,7 @@ export function DeleteConversationDialog({
return (
<OGDialogContent
title={localize('com_ui_delete_confirm') + ' ' + title}
title={localize('com_ui_delete_confirm', { title })}
className="w-11/12 max-w-md"
showCloseButton={false}
>
@ -88,7 +89,11 @@ export function DeleteConversationDialog({
<OGDialogTitle>{localize('com_ui_delete_conversation')}</OGDialogTitle>
</OGDialogHeader>
<div className="w-full truncate">
{localize('com_ui_delete_confirm')} <strong>{title}</strong> ?
<Trans
i18nKey="com_ui_delete_confirm_strong"
values={{ item: title }}
components={{ strong: <strong /> }}
/>
</div>
<div className="flex justify-end gap-4 pt-4">
<Button aria-label="cancel" variant="outline" onClick={() => setShowDeleteDialog(false)}>

View file

@ -137,7 +137,7 @@ const SearchBar = forwardRef((props: SearchBarProps, ref: React.Ref<HTMLDivEleme
/>
<button
type="button"
aria-label={`${localize('com_ui_clear')} ${localize('com_ui_search')}`}
aria-label={localize('com_ui_clear_search')}
className={cn(
'absolute right-[7px] flex h-5 w-5 items-center justify-center rounded-full border-none bg-transparent p-0 transition-opacity duration-200',
showClearIcon ? 'opacity-100' : 'opacity-0',

View file

@ -19,12 +19,12 @@ const ChatDirection = () => {
</div>
<Button
variant="outline"
aria-label={`${localize('com_nav_chat_direction')}: ${localize('com_ui_x_selected', {
0:
aria-label={localize('com_nav_chat_direction_selected', {
direction:
direction === 'LTR'
? localize('chat_direction_left_to_right')
: localize('chat_direction_right_to_left'),
})}`}
})}
onClick={toggleChatDirection}
data-testid="chatDirection"
>

View file

@ -251,7 +251,9 @@ export default function SharedLinks() {
onClick={() => {
window.open(`/c/${row.original.conversationId}`, '_blank');
}}
aria-label={`${localize('com_ui_view_source')} - ${row.original.title || localize('com_ui_untitled')}`}
aria-label={localize('com_ui_view_source', {
title: row.original.title || localize('com_ui_untitled'),
})}
>
<MessageSquare className="size-4" aria-hidden="true" />
</Button>
@ -262,7 +264,9 @@ export default function SharedLinks() {
setDeleteRow(row.original);
setIsDeleteOpen(true);
}}
aria-label={`${localize('com_ui_delete')} - ${row.original.title || localize('com_ui_untitled')}`}
aria-label={localize('com_ui_delete_shared_link', {
title: row.original.title || localize('com_ui_untitled'),
})}
>
<TrashIcon className="size-4" aria-hidden="true" />
</Button>

View file

@ -1,4 +1,5 @@
import { useState, useCallback, useMemo, useEffect } from 'react';
import { Trans } from 'react-i18next';
import debounce from 'lodash/debounce';
import { useRecoilValue } from 'recoil';
import { TrashIcon, ArchiveRestore, ArrowUp, ArrowDown, ArrowUpDown } from 'lucide-react';
@ -287,12 +288,18 @@ export default function ArchivedChatsTable({
<OGDialog open={isDeleteOpen} onOpenChange={onOpenChange}>
<OGDialogContent
title={localize('com_ui_delete_confirm') + ' ' + (deleteConversation?.title ?? '')}
title={localize('com_ui_delete_confirm', {
title: deleteConversation?.title ?? localize('com_ui_untitled'),
})}
className="w-11/12 max-w-md"
>
<OGDialogHeader>
<OGDialogTitle>
{localize('com_ui_delete_confirm')} <strong>{deleteConversation?.title}</strong>
<Trans
i18nKey="com_ui_delete_confirm_strong"
values={{ title: deleteConversation?.title }}
components={{ strong: <strong /> }}
/>
</OGDialogTitle>
</OGDialogHeader>
<div className="flex justify-end gap-4 pt-4">

View file

@ -156,7 +156,7 @@ const AdminSettings = () => {
</OGDialogTrigger>
<OGDialogContent className="max-w-lg border-border-light bg-surface-primary text-text-primary lg:w-1/4">
<OGDialogTitle>
{`${localize('com_ui_admin_settings')} - ${localize('com_ui_prompts')}`}
{localize('com_ui_admin_settings_section', { section: localize('com_ui_prompts') })}
</OGDialogTitle>
<div className="p-2">
{/* Role selection dropdown */}

View file

@ -72,11 +72,7 @@ function ChatGroupItem({
<button
id={`prompt-actions-${group._id}`}
type="button"
aria-label={
localize('com_ui_sr_actions_menu', { 0: group.name }) +
' ' +
localize('com_ui_prompt')
}
aria-label={localize('com_ui_sr_actions_menu', { name: group.name })}
onClick={(e) => {
e.stopPropagation();
}}

View file

@ -114,7 +114,7 @@ function DashGroupItemComponent({ group, instanceProjectId }: DashGroupItemProps
variant="ghost"
onClick={(e) => e.stopPropagation()}
className="h-8 w-8 p-0 hover:bg-surface-hover"
aria-label={localize('com_ui_rename_prompt') + ' ' + group.name}
aria-label={localize('com_ui_rename_prompt_name', { name: group.name })}
>
<Pen className="icon-sm text-text-primary" aria-hidden="true" />
</Button>
@ -130,7 +130,7 @@ function DashGroupItemComponent({ group, instanceProjectId }: DashGroupItemProps
value={nameInputValue}
onChange={(e) => setNameInputValue(e.target.value)}
className="w-full"
aria-label={localize('com_ui_rename_prompt') + ' ' + group.name}
aria-label={localize('com_ui_rename_prompt_name', { name: group.name })}
/>
</div>
</div>
@ -153,7 +153,7 @@ function DashGroupItemComponent({ group, instanceProjectId }: DashGroupItemProps
variant="ghost"
className="h-8 w-8 p-0 hover:bg-surface-hover"
onClick={(e) => e.stopPropagation()}
aria-label={localize('com_ui_delete_prompt') + ' ' + group.name}
aria-label={localize('com_ui_delete_prompt_name', { name: group.name })}
>
<TrashIcon className="icon-sm text-text-primary" aria-hidden="true" />
</Button>

View file

@ -42,10 +42,7 @@ export default function VariablesDropdown({
};
return (
<div
className={className}
title={`${localize('com_ui_add')} ${localize('com_ui_special_variables')}`}
>
<div className={className} title={localize('com_ui_add_special_variables')}>
<DropdownPopup
portal={true}
mountByState={true}
@ -56,7 +53,7 @@ export default function VariablesDropdown({
trigger={
<Menu.MenuButton
id="variables-menu-button"
aria-label={`${localize('com_ui_add')} ${localize('com_ui_special_variables')}`}
aria-label={localize('com_ui_add_special_variables')}
className="flex h-8 items-center gap-1 rounded-md border border-border-medium bg-surface-secondary px-2 py-0 text-sm text-text-primary transition-colors duration-200 hover:bg-surface-tertiary"
>
<PlusCircle className="mr-1 h-3 w-3 text-text-secondary" aria-hidden={true} />

View file

@ -166,9 +166,9 @@ const PeoplePickerAdminSettings = () => {
</Button>
</OGDialogTrigger>
<OGDialogContent className="w-full border-border-light bg-surface-primary text-text-primary lg:w-1/4">
<OGDialogTitle>{`${localize('com_ui_admin_settings')} - ${localize(
'com_ui_people_picker',
)}`}</OGDialogTitle>
<OGDialogTitle>
{localize('com_ui_admin_settings_section', { section: localize('com_ui_people_picker') })}
</OGDialogTitle>
<div className="p-2">
{/* Role selection dropdown */}
<div className="flex items-center gap-2">

View file

@ -160,9 +160,9 @@ const AdminSettings = () => {
</Button>
</OGDialogTrigger>
<OGDialogContent className="border-border-light bg-surface-primary text-text-primary lg:w-1/4">
<OGDialogTitle>{`${localize('com_ui_admin_settings')} - ${localize(
'com_ui_agents',
)}`}</OGDialogTitle>
<OGDialogTitle>
{localize('com_ui_admin_settings_section', { section: localize('com_ui_agents') })}
</OGDialogTitle>
<div className="p-2">
{/* Role selection dropdown */}
<div className="flex items-center gap-2">

View file

@ -315,9 +315,18 @@ export default function AgentConfig() {
{/* Agent Tools & Actions */}
<div className="mb-4">
<label className={labelClass}>
{`${toolsEnabled === true ? localize('com_ui_tools') : ''}
${toolsEnabled === true && actionsEnabled === true ? ' + ' : ''}
${actionsEnabled === true ? localize('com_assistants_actions') : ''}`}
{(() => {
if (toolsEnabled === true && actionsEnabled === true) {
return localize('com_ui_tools_and_actions');
}
if (toolsEnabled === true) {
return localize('com_ui_tools');
}
if (actionsEnabled === true) {
return localize('com_assistants_actions');
}
return '';
})()}
</label>
<div>
<div className="mb-1">

View file

@ -335,7 +335,8 @@ describe('AgentPanel - Update Agent Toast Messages', () => {
await waitFor(() => {
expect(mockShowToast).toHaveBeenCalledWith({
message: 'com_assistants_update_success Test Agent',
message: 'com_assistants_update_success_name',
status: undefined,
});
});
});
@ -355,7 +356,8 @@ describe('AgentPanel - Update Agent Toast Messages', () => {
await waitFor(() => {
expect(mockShowToast).toHaveBeenCalledWith({
message: 'com_assistants_update_success com_ui_agent',
message: 'com_assistants_update_success_name',
status: undefined,
});
});
});
@ -375,7 +377,8 @@ describe('AgentPanel - Update Agent Toast Messages', () => {
await waitFor(() => {
expect(mockShowToast).toHaveBeenCalledWith({
message: 'com_assistants_update_success Test Agent',
message: 'com_assistants_update_success_name',
status: undefined,
});
});
});

View file

@ -46,7 +46,7 @@ function getUpdateToastMessage(
if (noVersionChange) {
return localize('com_ui_no_changes');
}
return `${localize('com_assistants_update_success')} ${name ?? localize('com_ui_agent')}`;
return localize('com_assistants_update_success_name', { name: name ?? localize('com_ui_agent') });
}
/**
@ -504,20 +504,10 @@ export default function AgentPanel() {
setCurrentAgentId(undefined);
}}
disabled={agentQuery.isInitialLoading}
aria-label={
localize('com_ui_create') +
' ' +
localize('com_ui_new') +
' ' +
localize('com_ui_agent')
}
aria-label={localize('com_ui_create_new_agent')}
>
<Plus className="mr-1 h-4 w-4" aria-hidden="true" />
{localize('com_ui_create') +
' ' +
localize('com_ui_new') +
' ' +
localize('com_ui_agent')}
{localize('com_ui_create_new_agent')}
</Button>
<Button
variant="submit"
@ -526,7 +516,7 @@ export default function AgentPanel() {
e.preventDefault();
handleSelectAgent();
}}
aria-label={localize('com_ui_select') + ' ' + localize('com_ui_agent')}
aria-label={localize('com_ui_select_agent')}
>
{localize('com_ui_select')}
</Button>

View file

@ -181,7 +181,7 @@ export default function AgentSelect({
};
}, [selectedAgentId, agents, onSelect]);
const createAgent = localize('com_ui_create') + ' ' + localize('com_ui_agent');
const createAgent = localize('com_ui_create_new_agent');
return (
<Controller

View file

@ -87,7 +87,7 @@ export default function DeleteButton({
<Button
size="sm"
variant="outline"
aria-label={localize('com_ui_delete') + ' ' + localize('com_ui_agent')}
aria-label={localize('com_ui_delete_agent')}
type="button"
>
<div className="flex w-full items-center justify-center gap-2 text-red-500">
@ -96,7 +96,7 @@ export default function DeleteButton({
</Button>
</OGDialogTrigger>
<OGDialogTemplate
title={localize('com_ui_delete') + ' ' + localize('com_ui_agent')}
title={localize('com_ui_delete_agent')}
className="max-w-[450px]"
main={
<>

View file

@ -36,7 +36,7 @@ export default function DuplicateAgent({ agent_id }: { agent_id: string }) {
<Button
size="sm"
variant="outline"
aria-label={localize('com_ui_duplicate') + ' ' + localize('com_ui_agent')}
aria-label={localize('com_ui_duplicate_agent')}
type="button"
onClick={handleDuplicate}
>

View file

@ -263,7 +263,7 @@ export default function AssistantSelect({
};
}, [selectedAssistant, query.data, onSelect]);
const createAssistant = localize('com_ui_create') + ' ' + localize('com_ui_assistant');
const createAssistant = localize('com_ui_create_assistant');
return (
<SelectDropDown
value={!value ? createAssistant : value}

View file

@ -100,7 +100,7 @@ export default function ContextButton({
</button>
</DialogTrigger>
<DialogTemplate
title={localize('com_ui_delete') + ' ' + localize('com_ui_assistant')}
title={localize('com_ui_delete_assistant')}
className="max-w-[450px]"
main={
<>

View file

@ -194,7 +194,7 @@ function MCPPanelContent() {
variant="outline"
className="flex-1 justify-start dark:hover:bg-gray-700"
onClick={() => handleServerClickToEdit(server.serverName)}
aria-label={localize('com_ui_edit') + ' ' + server.serverName}
aria-label={localize('com_ui_edit_server', { serverName: server.serverName })}
>
<div className="flex items-center gap-2">
<span>{server.serverName}</span>

View file

@ -149,9 +149,9 @@ const AdminSettings = () => {
</Button>
</OGDialogTrigger>
<OGDialogContent className="border-border-light bg-surface-primary text-text-primary lg:w-1/4">
<OGDialogTitle>{`${localize('com_ui_admin_settings')} - ${localize(
'com_ui_memories',
)}`}</OGDialogTitle>
<OGDialogTitle>
{localize('com_ui_admin_settings_section', { section: localize('com_ui_memories') })}
</OGDialogTitle>
<div className="p-2">
{/* Role selection dropdown */}
<div className="flex items-center gap-2">