Complete client UI i18n migration
Adds client-side i18n support, language settings, translated UI copy, and locale parity coverage.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { memo, useCallback, useMemo, useState, type ReactElement } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Text, View } from "react-native";
|
||||
import { FileText, Layers, MessageSquare, Undo2 } from "lucide-react-native";
|
||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||
@@ -42,7 +43,16 @@ export const RewindMenu = memo(function RewindMenu({
|
||||
testID = "rewind-menu",
|
||||
}: RewindMenuProps) {
|
||||
const { theme } = useUnistyles();
|
||||
const items = useRewindCapabilities(capabilities);
|
||||
const { t } = useTranslation();
|
||||
const rewindLabels = useMemo(
|
||||
() => ({
|
||||
conversation: t("rewind.actions.conversation"),
|
||||
files: t("rewind.actions.files"),
|
||||
both: t("rewind.actions.both"),
|
||||
}),
|
||||
[t],
|
||||
);
|
||||
const items = useRewindCapabilities(capabilities, rewindLabels);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [pendingMode, setPendingMode] = useState<RewindMode | null>(null);
|
||||
const isLocked = isPendingProp || pendingMode !== null;
|
||||
@@ -79,10 +89,10 @@ export const RewindMenu = memo(function RewindMenu({
|
||||
const tooltipContent = useMemo(
|
||||
() => (
|
||||
<TooltipContent side="top" align="center" offset={8}>
|
||||
<Text style={styles.tooltipText}>Rewind to this message</Text>
|
||||
<Text style={styles.tooltipText}>{t("rewind.tooltip")}</Text>
|
||||
</TooltipContent>
|
||||
),
|
||||
[],
|
||||
[t],
|
||||
);
|
||||
|
||||
if (items.length === 0) {
|
||||
@@ -95,7 +105,7 @@ export const RewindMenu = memo(function RewindMenu({
|
||||
<TooltipTrigger asChild>
|
||||
<View style={styles.triggerSlot} collapsable={false}>
|
||||
<DropdownMenuTrigger
|
||||
accessibilityLabel="Rewind to this message"
|
||||
accessibilityLabel={t("rewind.tooltip")}
|
||||
accessibilityRole="button"
|
||||
disabled={isLocked}
|
||||
style={triggerStyle}
|
||||
@@ -114,7 +124,7 @@ export const RewindMenu = memo(function RewindMenu({
|
||||
</Tooltip>
|
||||
<DropdownMenuContent align="end" minWidth={220} side="bottom" testID={`${testID}-content`}>
|
||||
<View style={styles.warningHeader}>
|
||||
<Text style={styles.warningText}>This action cannot be undone</Text>
|
||||
<Text style={styles.warningText}>{t("rewind.warning")}</Text>
|
||||
</View>
|
||||
<DropdownMenuSeparator />
|
||||
{items.map((item) => (
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { useToast } from "@/contexts/toast-context";
|
||||
import type { DaemonClient } from "@getpaseo/client/internal/daemon-client";
|
||||
@@ -25,11 +26,12 @@ export function useRewindAgentMutation(input: UseRewindAgentMutationInput): {
|
||||
isPending: boolean;
|
||||
} {
|
||||
const toast = useToast();
|
||||
const { t } = useTranslation();
|
||||
const composerRestore = useRewindComposerRestore();
|
||||
const { isPending, mutateAsync } = useMutation({
|
||||
mutationFn: async ({ mode }: RewindAgentInput) => {
|
||||
if (!input.client || !input.agentId || !input.messageId) {
|
||||
throw new Error("Daemon client not available");
|
||||
throw new Error(t("common.errors.daemonClientUnavailable"));
|
||||
}
|
||||
await input.client.rewindAgent(input.agentId, input.messageId, mode);
|
||||
if (mode !== "files") {
|
||||
@@ -59,7 +61,7 @@ export function useRewindAgentMutation(input: UseRewindAgentMutationInput): {
|
||||
composerRestore?.restoreTextIfComposerEmpty(variables.rewoundText);
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(error instanceof Error ? error.message : "Failed to rewind agent");
|
||||
toast.error(error instanceof Error ? error.message : t("rewind.errors.failed"));
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -33,4 +33,21 @@ describe("resolveRewindMenuItems", () => {
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("uses caller-provided labels for available capabilities", () => {
|
||||
expect(
|
||||
resolveRewindMenuItems(
|
||||
{
|
||||
supportsRewindConversation: true,
|
||||
supportsRewindFiles: true,
|
||||
supportsRewindBoth: false,
|
||||
},
|
||||
{
|
||||
conversation: "Conversation label",
|
||||
files: "Files label",
|
||||
both: "Both label",
|
||||
},
|
||||
).map((item) => item.label),
|
||||
).toEqual(["Conversation label", "Files label"]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,6 +9,18 @@ export interface RewindMenuItem {
|
||||
testID: string;
|
||||
}
|
||||
|
||||
export interface RewindMenuLabels {
|
||||
conversation: string;
|
||||
files: string;
|
||||
both: string;
|
||||
}
|
||||
|
||||
const DEFAULT_REWIND_MENU_LABELS: RewindMenuLabels = {
|
||||
conversation: "Rewind conversation",
|
||||
files: "Rewind files",
|
||||
both: "Rewind conversation and files",
|
||||
};
|
||||
|
||||
export function resolveRewindMenuItems(
|
||||
capabilities:
|
||||
| Pick<
|
||||
@@ -17,29 +29,31 @@ export function resolveRewindMenuItems(
|
||||
>
|
||||
| null
|
||||
| undefined,
|
||||
labelsInput?: Partial<RewindMenuLabels>,
|
||||
): RewindMenuItem[] {
|
||||
if (!capabilities) {
|
||||
return [];
|
||||
}
|
||||
const labels = { ...DEFAULT_REWIND_MENU_LABELS, ...labelsInput };
|
||||
const items: RewindMenuItem[] = [];
|
||||
if (capabilities.supportsRewindConversation) {
|
||||
items.push({
|
||||
mode: "conversation",
|
||||
label: "Rewind conversation",
|
||||
label: labels.conversation,
|
||||
testID: "rewind-menu-conversation",
|
||||
});
|
||||
}
|
||||
if (capabilities.supportsRewindFiles) {
|
||||
items.push({
|
||||
mode: "files",
|
||||
label: "Rewind files",
|
||||
label: labels.files,
|
||||
testID: "rewind-menu-files",
|
||||
});
|
||||
}
|
||||
if (capabilities.supportsRewindBoth) {
|
||||
items.push({
|
||||
mode: "both",
|
||||
label: "Rewind conversation and files",
|
||||
label: labels.both,
|
||||
testID: "rewind-menu-both",
|
||||
});
|
||||
}
|
||||
@@ -48,6 +62,7 @@ export function resolveRewindMenuItems(
|
||||
|
||||
export function useRewindCapabilities(
|
||||
capabilities: Parameters<typeof resolveRewindMenuItems>[0],
|
||||
labels?: Partial<RewindMenuLabels>,
|
||||
): RewindMenuItem[] {
|
||||
return useMemo(() => resolveRewindMenuItems(capabilities), [capabilities]);
|
||||
return useMemo(() => resolveRewindMenuItems(capabilities, labels), [capabilities, labels]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user