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
@@ -1,5 +1,6 @@
import { Text, View } from "react-native";
import { ArrowLeftToLine, RotateCw, Settings } from "lucide-react-native";
import { useTranslation } from "react-i18next";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { Button } from "@/components/ui/button";
import { LoadingSpinner } from "@/components/ui/loading-spinner";
@@ -43,24 +44,26 @@ export function renderWorkspaceRouteGate(input: {
function getWorkspaceHostStateTitle(
state: Extract<WorkspaceRouteState, { kind: "unreachable" }>,
t: ReturnType<typeof useTranslation>["t"],
): string {
if (state.connectionStatus === "connecting" || state.connectionStatus === "idle") {
return "Connecting";
return t("workspace.route.connecting");
}
if (state.connectionStatus === "offline") {
return `${state.hostName} is offline`;
return t("workspace.route.hostOffline", { hostName: state.hostName });
}
return `Cannot reach ${state.hostName}`;
return t("workspace.route.cannotReachHost", { hostName: state.hostName });
}
function WorkspaceConnecting({ hostName }: { hostName: string }) {
const { theme } = useUnistyles();
const { t } = useTranslation();
return (
<View style={styles.emptyState}>
<LoadingSpinner size="small" color={theme.colors.foregroundMuted} />
<View style={styles.textStack}>
<Text style={styles.title}>Loading workspace</Text>
<Text style={styles.title}>{t("workspace.route.loading")}</Text>
<Text style={styles.description}>{hostName}</Text>
</View>
</View>
@@ -77,6 +80,7 @@ function WorkspaceUnreachable({
onManageHost: () => void;
}) {
const { theme } = useUnistyles();
const { t } = useTranslation();
const canRetry = state.connectionStatus === "offline" || state.connectionStatus === "error";
return (
@@ -85,11 +89,13 @@ function WorkspaceUnreachable({
<LoadingSpinner size="small" color={theme.colors.foregroundMuted} />
) : null}
<View style={styles.textStack}>
<Text style={styles.title}>{getWorkspaceHostStateTitle(state)}</Text>
<Text style={styles.title}>{getWorkspaceHostStateTitle(state, t)}</Text>
<Text style={styles.description}>
{state.connectionStatus === "connecting" || state.connectionStatus === "idle"
? state.hostName
: `Host status: ${formatConnectionStatus(state.connectionStatus)}`}
: t("workspace.route.hostStatus", {
status: formatConnectionStatus(state.connectionStatus),
})}
</Text>
{state.lastError ? (
<Tooltip delayDuration={0} enabledOnDesktop enabledOnMobile={false}>
@@ -107,10 +113,10 @@ function WorkspaceUnreachable({
{canRetry ? (
<View style={styles.actions}>
<Button size="sm" variant="default" leftIcon={RotateCw} onPress={onRetry}>
Retry
{t("common.actions.retry")}
</Button>
<Button size="sm" variant="outline" leftIcon={Settings} onPress={onManageHost}>
Manage host
{t("workspace.route.manageHost")}
</Button>
</View>
) : null}
@@ -119,15 +125,17 @@ function WorkspaceUnreachable({
}
function WorkspaceMissing({ hostName, onDismiss }: { hostName: string; onDismiss: () => void }) {
const { t } = useTranslation();
return (
<View style={styles.emptyState}>
<View style={styles.textStack}>
<Text style={styles.title}>Workspace not found</Text>
<Text style={styles.title}>{t("workspace.route.missing")}</Text>
<Text style={styles.description}>{hostName}</Text>
</View>
<View style={styles.actions}>
<Button size="sm" variant="default" leftIcon={ArrowLeftToLine} onPress={onDismiss}>
Back
{t("common.actions.back")}
</Button>
</View>
</View>