Complete client UI i18n migration

Adds client-side i18n support, language settings, translated UI copy, and locale parity coverage.
This commit is contained in:
chyendongnhanh338
2026-06-11 14:15:35 +08:00
committed by GitHub
parent 41cf070fd8
commit 08ebe5e7e1
199 changed files with 16426 additions and 1821 deletions
@@ -2,6 +2,7 @@ import { useCallback } from "react";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { useQuery, useQueryClient, type QueryClient } from "@tanstack/react-query";
import { queryClient as appQueryClient } from "@/query/query-client";
import type { AppLanguage } from "@/i18n/locales";
import {
DEFAULT_DESKTOP_SETTINGS,
loadDesktopSettings,
@@ -58,6 +59,7 @@ export {
};
export type {
AppSettings,
AppLanguage,
DesktopSettingsBridge,
KeyValueStorage,
ReleaseChannel,
@@ -143,6 +145,9 @@ export function useSettings(): UseSettingsReturn {
if (updates.theme !== undefined) {
appUpdates.theme = updates.theme;
}
if (updates.language !== undefined) {
appUpdates.language = updates.language;
}
if (updates.sendBehavior !== undefined) {
appUpdates.sendBehavior = updates.sendBehavior;
}
@@ -47,11 +47,20 @@ describe("loadAppSettingsFromStorage", () => {
const result = await loadAppSettingsFromStorage(deps);
expect(result).toEqual(DEFAULT_CLIENT_SETTINGS);
expect(DEFAULT_CLIENT_SETTINGS.language).toBe("system");
expect(deps.storage.entries.get(APP_SETTINGS_KEY)).toBe(
JSON.stringify(DEFAULT_CLIENT_SETTINGS),
);
});
it("defaults language to system when storage is empty", async () => {
const deps = makeDeps();
const result = await loadAppSettingsFromStorage(deps);
expect(result.language).toBe("system");
});
it("loads configured terminal scrollback lines from app settings", async () => {
const deps = makeDeps({
storage: createInMemoryKeyValueStorage({
@@ -95,6 +104,30 @@ describe("loadAppSettingsFromStorage", () => {
});
expect(deps.storage.entries.get(APP_SETTINGS_KEY)).toBe(JSON.stringify(result));
});
it("loads a persisted explicit language", async () => {
const deps = makeDeps({
storage: createInMemoryKeyValueStorage({
[APP_SETTINGS_KEY]: JSON.stringify({ language: "zh-CN" }),
}),
});
const result = await loadAppSettingsFromStorage(deps);
expect(result.language).toBe("zh-CN");
});
it("drops an unknown persisted language back to system", async () => {
const deps = makeDeps({
storage: createInMemoryKeyValueStorage({
[APP_SETTINGS_KEY]: JSON.stringify({ language: "klingon" }),
}),
});
const result = await loadAppSettingsFromStorage(deps);
expect(result.language).toBe("system");
});
});
describe("loadSettingsFromStorage", () => {
@@ -1,6 +1,7 @@
import { isSyntaxThemeId, type SyntaxThemeId } from "@getpaseo/highlight";
import type { QueryClient } from "@tanstack/react-query";
import type { DesktopSettings } from "@/desktop/settings/desktop-settings";
import { parseAppLanguage, type AppLanguage } from "@/i18n/locales";
import { THEME_TO_UNISTYLES, type ThemeName } from "@/styles/theme";
export const APP_SETTINGS_KEY = "@paseo:app-settings";
@@ -26,6 +27,7 @@ export const MAX_FONT_FAMILY_LENGTH = 200;
export interface AppSettings {
theme: ThemeName | "auto";
language: AppLanguage;
sendBehavior: SendBehavior;
serviceUrlBehavior: ServiceUrlBehavior;
terminalScrollbackLines: number;
@@ -43,6 +45,7 @@ export interface Settings extends AppSettings {
export const DEFAULT_CLIENT_SETTINGS: AppSettings = {
theme: "auto",
language: "system",
sendBehavior: "interrupt",
serviceUrlBehavior: "ask",
terminalScrollbackLines: DEFAULT_TERMINAL_SCROLLBACK_LINES,
@@ -149,6 +152,10 @@ function pickAppSettings(stored: Partial<AppSettings>): Partial<AppSettings> {
if (typeof stored.theme === "string" && VALID_THEMES.has(stored.theme)) {
result.theme = stored.theme;
}
const language = parseAppLanguage(stored.language);
if (language !== null) {
result.language = language;
}
if (stored.sendBehavior === "interrupt" || stored.sendBehavior === "queue") {
result.sendBehavior = stored.sendBehavior;
}