Add i18n foundation and translations (#1027)

* feat: add i18n foundation

* feat: localize sessions sidebar

* Localize multirun/scheduled tasks and fix dialog dropdown interactions

* localize git sidebar surface and add zh-CN keys

* feat(ui): localize context panel, diff/plan views, and context sidebar content

* fix(config): resolve user config home via fs/home before embedded home

* localize header/chat UI and complete model/worktree panel strings

* localize worktree + github issue/pr dialog flows

* localize settings sections and split settings i18n dictionaries

* localize additional settings sections and sidebars

* localize more settings pages and dialogs

* fix settings select trigger localization

* localize tunnel settings ui surface

* localize additional settings sections

* localize keyboard shortcuts labels in settings

* localize terminal and utility dialogs surfaces

* feat(i18n): localize remaining UI strings

* Add Ukrainian locale

* Add Spanish locale

* Add Brazilian Portuguese locale

* Polish locale translations
This commit is contained in:
Bohdan Triapitsyn
2026-04-26 14:03:39 +03:00
committed by GitHub
parent 87db2ea210
commit 7d7285655d
198 changed files with 24173 additions and 4365 deletions
@@ -10,6 +10,7 @@ import { isIMECompositionEvent } from '@/lib/ime';
import { useConfigStore } from '@/stores/useConfigStore';
import { useModelLists } from '@/hooks/useModelLists';
import type { ModelMetadata } from '@/types';
import { useI18n } from '@/lib/i18n';
/** Chip height class - shared between chips and add button */
const CHIP_HEIGHT_CLASS = 'h-7';
@@ -110,11 +111,12 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
onRemove,
onUpdate,
minModels,
addButtonLabel = 'Add model',
addButtonLabel,
showChips = true,
maxModels,
addButtonClassName,
}) => {
const { t } = useI18n();
const providers = useConfigStore((state) => state.providers);
const modelsMetadata = useConfigStore((state) => state.modelsMetadata);
const { favoriteModelsList, recentModelsList } = useModelLists();
@@ -334,7 +336,7 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
onClick={() => setIsOpen(!isOpen)}
>
<RiAddLine className="h-3.5 w-3.5 mr-1" />
{addButtonLabel}
{addButtonLabel ?? t('multirun.modelMultiSelect.actions.addModel')}
</Button>
{isOpen && (() => {
@@ -414,7 +416,7 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
<Input
ref={searchInputRef}
type="text"
placeholder="Search models"
placeholder={t('multirun.modelMultiSelect.search.placeholder')}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
onKeyDown={handleKeyDown}
@@ -431,7 +433,7 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
<div className="p-1">
{!hasResults && (
<div className="px-2 py-4 text-center typography-meta text-muted-foreground">
No models found
{t('multirun.modelMultiSelect.search.noResults')}
</div>
)}
@@ -440,7 +442,7 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
<>
<div className="typography-micro font-semibold text-muted-foreground uppercase tracking-wider sticky top-0 z-10 -mx-1 flex items-center gap-2 border-b border-border/30 px-3 py-1.5 [background:linear-gradient(var(--surface-elevated),var(--surface-elevated)),linear-gradient(var(--surface-background),var(--surface-background))]">
<RiStarFill className="h-4 w-4 text-primary" />
Favorites
{t('multirun.modelMultiSelect.sections.favorites')}
</div>
{filteredFavorites.map(({ model, providerID, modelID }) => {
const idx = currentFlatIndex++;
@@ -455,7 +457,7 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
{filteredFavorites.length > 0 && <div className="h-px bg-border/40 my-1" />}
<div className="typography-micro font-semibold text-muted-foreground uppercase tracking-wider sticky top-0 z-10 -mx-1 flex items-center gap-2 border-b border-border/30 px-3 py-1.5 [background:linear-gradient(var(--surface-elevated),var(--surface-elevated)),linear-gradient(var(--surface-background),var(--surface-background))]">
<RiTimeLine className="h-4 w-4" />
Recent
{t('multirun.modelMultiSelect.sections.recent')}
</div>
{filteredRecents.map(({ model, providerID, modelID }) => {
const idx = currentFlatIndex++;
@@ -491,7 +493,7 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
{/* Keyboard hints footer */}
<div className="px-3 pt-1 pb-1.5 border-t border-border/40 typography-micro text-muted-foreground">
navigate Enter select Esc close
{t('multirun.modelMultiSelect.keyboard.hint')}
</div>
</div>
);
@@ -544,11 +546,11 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
variantValue === DEFAULT_VARIANT_VALUE ? 'text-muted-foreground' : 'text-[color:var(--status-info)]'
)}
/>
<SelectValue placeholder="Thinking" />
<SelectValue placeholder={t('multirun.modelMultiSelect.variant.placeholder')} />
</SelectTrigger>
<SelectContent fitContent>
<SelectItem value={DEFAULT_VARIANT_VALUE} className="pr-2 [&>span:first-child]:hidden">
Default
{t('multirun.modelMultiSelect.variant.default')}
</SelectItem>
{variantKeys.map((variant) => (
<SelectItem key={variant} value={variant} className="pr-2 [&>span:first-child]:hidden">
@@ -568,7 +570,9 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
{/* Validation hint */}
{minModels !== undefined && selectedModels.length < minModels && (
<p className="typography-micro text-muted-foreground">
Select from {minModels} {maxModels !== undefined ? `to ${maxModels} models` : ''}.
{maxModels !== undefined
? t('multirun.modelMultiSelect.validation.minToMax', { min: minModels, max: maxModels })
: t('multirun.modelMultiSelect.validation.minOnly', { min: minModels })}
</p>
)}
</div>