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
@@ -8,6 +8,7 @@ import {
type ReactNode,
type RefObject,
} from "react";
import { useTranslation } from "react-i18next";
import {
View,
Text,
@@ -56,7 +57,7 @@ import type { AgentProviderDefinition } from "@getpaseo/protocol/provider-manife
import {
getFeatureHighlightColor,
getFeatureTooltip,
getAgentControlHint,
getAgentControlHintKey,
formatThinkingOptionLabel,
resolveAgentModelSelection,
} from "@/composer/agent-controls/utils";
@@ -415,6 +416,7 @@ function ControlledAgentControls({
isCompactLayout,
}: ControlledAgentControlsProps) {
const { theme } = useUnistyles();
const { t } = useTranslation();
const isCompactFormFactor = useIsCompactFormFactor();
const isCompact = isCompactLayout ?? isCompactFormFactor;
const [activeSheet, setActiveSheet] = useState<ActiveSheet>(null);
@@ -432,7 +434,11 @@ function ControlledAgentControls({
onSelectThinkingOption && thinkingOptions && thinkingOptions.length > 0,
);
const displayProvider = findOptionLabel(providerOptions, selectedProviderId, "Provider");
const displayProvider = findOptionLabel(
providerOptions,
selectedProviderId,
t("agentControls.provider.fallback"),
);
const formattedThinkingOptions = useMemo(
() => toThinkingControlOptions(thinkingOptions),
[thinkingOptions],
@@ -440,7 +446,7 @@ function ControlledAgentControls({
const displayThinking = findOptionLabel(
formattedThinkingOptions,
selectedThinkingOptionId,
formattedThinkingOptions[0]?.label ?? "Unknown",
formattedThinkingOptions[0]?.label ?? t("agentControls.thinking.unknown"),
);
const ProviderIcon = resolveProviderIcon(provider);
@@ -709,6 +715,7 @@ const DESKTOP_SEARCH_THRESHOLD = 6;
function DesktopAgentControlsContent(props: DesktopAgentControlsContentProps) {
const { theme } = useUnistyles();
const { t } = useTranslation();
const {
provider,
providerOptions,
@@ -764,7 +771,7 @@ function DesktopAgentControlsContent(props: DesktopAgentControlsContentProps) {
onPress={handleProviderPress}
style={providerPressableStyle}
accessibilityRole="button"
accessibilityLabel="Select agent provider"
accessibilityLabel={t("agentControls.provider.select")}
testID="agent-provider-selector"
>
<Text style={styles.modeBadgeText}>{displayProvider}</Text>
@@ -805,7 +812,7 @@ function DesktopAgentControlsContent(props: DesktopAgentControlsContentProps) {
</View>
</TooltipTrigger>
<TooltipContent side="top" align="center" offset={8}>
<Text style={styles.tooltipText}>{getAgentControlHint("model")}</Text>
<Text style={styles.tooltipText}>{t(getAgentControlHintKey("model"))}</Text>
</TooltipContent>
</Tooltip>
) : null}
@@ -821,7 +828,9 @@ function DesktopAgentControlsContent(props: DesktopAgentControlsContentProps) {
onPress={handleThinkingPress}
style={thinkingPressableStyle}
accessibilityRole="button"
accessibilityLabel={`Select thinking option (${displayThinking})`}
accessibilityLabel={t("agentControls.thinking.selectWithValue", {
value: displayThinking,
})}
testID="agent-thinking-selector"
>
<Brain size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
@@ -830,7 +839,7 @@ function DesktopAgentControlsContent(props: DesktopAgentControlsContentProps) {
</Pressable>
</TooltipTrigger>
<TooltipContent side="top" align="center" offset={8}>
<Text style={styles.tooltipText}>{getAgentControlHint("thinking")}</Text>
<Text style={styles.tooltipText}>{t(getAgentControlHintKey("thinking"))}</Text>
</TooltipContent>
</Tooltip>
<Combobox
@@ -901,6 +910,7 @@ interface SheetAgentControlsContentProps {
function SheetAgentControlsContent(props: SheetAgentControlsContentProps) {
const { theme } = useUnistyles();
const { t } = useTranslation();
const {
provider,
selectedModelId,
@@ -936,6 +946,10 @@ function SheetAgentControlsContent(props: SheetAgentControlsContentProps) {
const hasThinking = comboboxThinkingOptions.length > 0;
const hasFeatures = Boolean(features && features.length > 0);
const featuresSheetHeader = useMemo<SheetHeader>(
() => ({ title: t("agentControls.features.title") }),
[t],
);
const handleOpenThinking = useCallback(() => handleOpenSheet("thinking"), [handleOpenSheet]);
const handleOpenFeatures = useCallback(() => handleOpenSheet("features"), [handleOpenSheet]);
@@ -1012,7 +1026,7 @@ function SheetAgentControlsContent(props: SheetAgentControlsContentProps) {
disabled={disabled || !canSelectThinking}
style={thinkingButtonStyle}
accessibilityRole="button"
accessibilityLabel="Select thinking option"
accessibilityLabel={t("agentControls.thinking.select")}
testID="agent-controls-thinking"
>
<Brain size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
@@ -1025,7 +1039,7 @@ function SheetAgentControlsContent(props: SheetAgentControlsContentProps) {
disabled={disabled}
style={featuresButtonStyle}
accessibilityRole="button"
accessibilityLabel="Open agent features"
accessibilityLabel={t("agentControls.features.open")}
testID="agent-controls-features"
>
<Settings2 size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
@@ -1038,7 +1052,7 @@ function SheetAgentControlsContent(props: SheetAgentControlsContentProps) {
value={selectedThinkingOptionId ?? ""}
onSelect={handleSelectThinkingAndClose}
searchable={false}
title="Thinking"
title={t("agentControls.thinking.title")}
open={activeSheet === "thinking"}
onOpenChange={handleThinkingSheetOpenChange}
anchorRef={thinkingAnchorRef}
@@ -1047,7 +1061,7 @@ function SheetAgentControlsContent(props: SheetAgentControlsContentProps) {
) : null}
<AdaptiveModalSheet
header={FEATURES_SHEET_HEADER}
header={featuresSheetHeader}
visible={activeSheet === "features"}
onClose={handleCloseSheet}
testID="agent-features-sheet"
@@ -1206,6 +1220,7 @@ function SheetFeatureItem({
onSetFeature?: (featureId: string, value: unknown) => void;
}) {
const { theme } = useUnistyles();
const { t } = useTranslation();
const featureSelector: AgentControlSelector = `feature-${feature.id}`;
const handleFeatureOpenChange = useMemo(
@@ -1257,7 +1272,9 @@ function SheetFeatureItem({
)}
/>
<Text style={styles.sheetSelectText}>{feature.label}</Text>
<Text style={styles.modeBadgeText}>{feature.value ? "On" : "Off"}</Text>
<Text style={styles.modeBadgeText}>
{feature.value ? t("agentControls.features.on") : t("agentControls.features.off")}
</Text>
</Pressable>
</View>
);
@@ -1344,8 +1361,6 @@ function ThinkingComboboxOption({
);
}
const FEATURES_SHEET_HEADER: SheetHeader = { title: "Features" };
export const AgentControls = memo(function AgentControls({
agentId,
serverId,
@@ -7,6 +7,7 @@ import {
type ComponentType,
type ReactElement,
} from "react";
import { useTranslation } from "react-i18next";
import { Pressable, Text, View, type PressableStateCallbackType } from "react-native";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { useShallow } from "zustand/shallow";
@@ -108,6 +109,7 @@ function AgentModeControlView({
disabled = false,
}: AgentModeControlViewProps) {
const { theme } = useUnistyles();
const { t } = useTranslation();
const anchorRef = useRef<View>(null);
const [open, setOpen] = useState(false);
const [searchQuery, setSearchQuery] = useState("");
@@ -182,14 +184,14 @@ function AgentModeControlView({
const sheetHeader = useMemo<SheetHeader>(
() => ({
title: "Mode",
title: t("agentControls.mode.title"),
search: {
onChange: setSearchQuery,
placeholder: "Search modes...",
placeholder: t("agentControls.mode.searchPlaceholder"),
testID: "mode-search-input",
},
}),
[],
[t],
);
if (!selectedMode) return null;
@@ -203,7 +205,9 @@ function AgentModeControlView({
onPress={handlePress}
style={pressableStyle}
accessibilityRole="button"
accessibilityLabel={`Select agent mode (${selectedModeLabel})`}
accessibilityLabel={t("agentControls.mode.selectWithValue", {
value: selectedModeLabel,
})}
testID="mode-control"
>
{Icon ? <Icon size={theme.iconSize.md} color={iconColor} /> : null}
@@ -3,17 +3,17 @@ import {
formatAgentModeLabel,
getFeatureHighlightColor,
getFeatureTooltip,
getAgentControlHint,
getAgentControlHintKey,
formatThinkingOptionLabel,
normalizeModelId,
resolveAgentModelSelection,
} from "./utils";
describe("getAgentControlHint", () => {
it("explains what each editable agent control does", () => {
expect(getAgentControlHint("thinking")).toBe("Thinking mode");
expect(getAgentControlHint("model")).toBe("Change model");
expect(getAgentControlHint("mode")).toBe("Change permission mode");
describe("getAgentControlHintKey", () => {
it("returns translation keys for each editable agent control hint", () => {
expect(getAgentControlHintKey("thinking")).toBe("agentControls.hints.thinking");
expect(getAgentControlHintKey("model")).toBe("agentControls.hints.model");
expect(getAgentControlHintKey("mode")).toBe("agentControls.hints.mode");
});
});
@@ -1,16 +1,21 @@
import type { AgentFeature, AgentModelDefinition } from "@getpaseo/protocol/agent-types";
import { i18n } from "@/i18n/i18next";
export type ExplainedAgentControl = "mode" | "model" | "thinking";
export type FeatureHighlightColor = "blue" | "default" | "green" | "yellow";
export type AgentControlHintKey =
| "agentControls.hints.thinking"
| "agentControls.hints.model"
| "agentControls.hints.mode";
export function getAgentControlHint(selector: ExplainedAgentControl): string {
export function getAgentControlHintKey(selector: ExplainedAgentControl): AgentControlHintKey {
switch (selector) {
case "thinking":
return "Thinking mode";
return "agentControls.hints.thinking";
case "model":
return "Change model";
return "agentControls.hints.model";
case "mode":
return "Change permission mode";
return "agentControls.hints.mode";
default:
throw new Error("unreachable");
}
@@ -79,7 +84,7 @@ export function formatThinkingOptionLabel(option: ControlLabelInput): string {
const compactLabel = rawLabel.replace(/[\s_-]+/g, "").toLowerCase();
if (compactId === "xhigh" || compactLabel === "xhigh") {
return "Extra high";
return i18n.t("agentControls.thinking.extraHigh");
}
return formatControlLabel(option, true);
@@ -143,17 +148,19 @@ function resolveModelDisplay(
selectedModel: AgentModelDefinition | null,
preferredModelId: string | null,
fallbackModel: AgentModelDefinition | null,
unknownModelLabel: string,
): { activeModelId: string | null; displayModel: string } {
return {
activeModelId: selectedModel?.id ?? preferredModelId ?? null,
displayModel:
selectedModel?.label ?? preferredModelId ?? fallbackModel?.label ?? "Unknown model",
selectedModel?.label ?? preferredModelId ?? fallbackModel?.label ?? unknownModelLabel,
};
}
function resolveThinkingDisplay(
effectiveThinking: ThinkingOption | null,
selectedThinkingId: string | null,
unknownThinkingLabel: string,
): string {
if (effectiveThinking) {
return formatThinkingOptionLabel(effectiveThinking);
@@ -163,7 +170,7 @@ function resolveThinkingDisplay(
return formatThinkingOptionLabel({ id: selectedThinkingId });
}
return "Unknown";
return unknownThinkingLabel;
}
export function resolveAgentModelSelection(input: {
@@ -189,13 +196,18 @@ export function resolveAgentModelSelection(input: {
selectedModel,
preferredModelId,
fallbackModel,
i18n.t("agentControls.model.unknown"),
);
const thinkingOptions = selectedModel?.thinkingOptions ?? null;
const resolvedThinkingId = resolveThinkingId(explicitThinkingOptionId, selectedModel);
const effectiveThinking = resolveEffectiveThinking(thinkingOptions, resolvedThinkingId);
const selectedThinkingId = effectiveThinking?.id ?? null;
const displayThinking = resolveThinkingDisplay(effectiveThinking, selectedThinkingId);
const displayThinking = resolveThinkingDisplay(
effectiveThinking,
selectedThinkingId,
i18n.t("agentControls.thinking.unknown"),
);
return {
selectedModel,