diff --git a/components/automation/FlowViewer.tsx b/components/automation/FlowViewer.tsx index be5e562..9cba0eb 100644 --- a/components/automation/FlowViewer.tsx +++ b/components/automation/FlowViewer.tsx @@ -1,5 +1,6 @@ import React from "react"; import { View, Text, ScrollView } from "react-native"; +import { useTranslation } from "react-i18next"; /* ------------------------------------------------------------------ */ /* Props */ @@ -46,6 +47,7 @@ function findEdgesForNode( * Read-only flow diagram showing automation nodes and edges. */ export function FlowViewer({ nodes, edges }: FlowViewerProps) { + const { t } = useTranslation(); if (nodes.length === 0) { return ( @@ -57,7 +59,7 @@ export function FlowViewer({ nodes, edges }: FlowViewerProps) { } return ( - + {nodes.map((node, index) => { const outEdges = findEdgesForNode(node.id, edges); diff --git a/components/common/ScreenHeader.tsx b/components/common/ScreenHeader.tsx index 9271b55..e2f3558 100644 --- a/components/common/ScreenHeader.tsx +++ b/components/common/ScreenHeader.tsx @@ -3,6 +3,7 @@ import { View, Text, Pressable } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { ChevronLeft } from "lucide-react-native"; import { useRouter } from "expo-router"; +import { useTranslation } from "react-i18next"; import { useThemeColors } from "~/lib/theme-colors"; export interface ScreenHeaderProps { @@ -45,6 +46,7 @@ export function ScreenHeader({ }: ScreenHeaderProps) { const router = useRouter(); const insets = useSafeAreaInsets(); + const { t } = useTranslation(); const { accent } = useThemeColors(); const handleBack = () => { @@ -73,7 +75,7 @@ export function ScreenHeader({ onPress={handleBack} hitSlop={8} accessibilityRole="button" - accessibilityLabel="Go back" + accessibilityLabel={t("a11y.goBack")} className="h-11 w-11 items-center justify-center rounded-full active:bg-muted" > diff --git a/components/common/SearchBar.tsx b/components/common/SearchBar.tsx index 2ea5de6..f732b5b 100644 --- a/components/common/SearchBar.tsx +++ b/components/common/SearchBar.tsx @@ -1,6 +1,7 @@ import React from "react"; import { Pressable, TextInput, View } from "react-native"; import { Search, X } from "lucide-react-native"; +import { useTranslation } from "react-i18next"; import { cn } from "~/lib/utils"; export interface SearchBarProps { @@ -18,6 +19,7 @@ export function SearchBar({ debounceMs = 300, className, }: SearchBarProps) { + const { t } = useTranslation(); const [localValue, setLocalValue] = React.useState(value); const timerRef = React.useRef | null>(null); @@ -70,7 +72,7 @@ export function SearchBar({ onPress={handleClear} hitSlop={8} accessibilityRole="button" - accessibilityLabel="Clear search" + accessibilityLabel={t("a11y.clearSearch")} className="rounded-full p-1 active:opacity-60" > diff --git a/components/common/SkeletonDashboard.tsx b/components/common/SkeletonDashboard.tsx index d681061..21ad38c 100644 --- a/components/common/SkeletonDashboard.tsx +++ b/components/common/SkeletonDashboard.tsx @@ -1,5 +1,6 @@ import React from "react"; import { View } from "react-native"; +import { useTranslation } from "react-i18next"; export interface SkeletonDashboardProps { cards?: number; @@ -10,8 +11,9 @@ export function SkeletonDashboard({ cards = 4, testID = "skeleton-dashboard", }: SkeletonDashboardProps) { + const { t } = useTranslation(); return ( - + {Array.from({ length: cards }).map((_, i) => ( diff --git a/components/common/SkeletonDetail.tsx b/components/common/SkeletonDetail.tsx index 60592db..2a2ed6a 100644 --- a/components/common/SkeletonDetail.tsx +++ b/components/common/SkeletonDetail.tsx @@ -1,5 +1,6 @@ import React from "react"; import { View } from "react-native"; +import { useTranslation } from "react-i18next"; export interface SkeletonDetailProps { sections?: number; @@ -12,8 +13,9 @@ export function SkeletonDetail({ fieldsPerSection = 4, testID = "skeleton-detail", }: SkeletonDetailProps) { + const { t } = useTranslation(); return ( - + {/* Header skeleton */} diff --git a/components/common/SkeletonForm.tsx b/components/common/SkeletonForm.tsx index fbca630..a0a755b 100644 --- a/components/common/SkeletonForm.tsx +++ b/components/common/SkeletonForm.tsx @@ -1,5 +1,6 @@ import React from "react"; import { View } from "react-native"; +import { useTranslation } from "react-i18next"; export interface SkeletonFormProps { fields?: number; @@ -10,8 +11,9 @@ export function SkeletonForm({ fields = 5, testID = "skeleton-form", }: SkeletonFormProps) { + const { t } = useTranslation(); return ( - + {Array.from({ length: fields }).map((_, i) => ( {/* Label skeleton */} diff --git a/components/common/SkeletonList.tsx b/components/common/SkeletonList.tsx index 412df8a..edf5a6d 100644 --- a/components/common/SkeletonList.tsx +++ b/components/common/SkeletonList.tsx @@ -1,5 +1,6 @@ import React from "react"; import { View } from "react-native"; +import { useTranslation } from "react-i18next"; export interface SkeletonListProps { rows?: number; @@ -8,8 +9,9 @@ export interface SkeletonListProps { } export function SkeletonList({ rows = 5, showAvatar = true, testID = "skeleton-list" }: SkeletonListProps) { + const { t } = useTranslation(); return ( - + {Array.from({ length: rows }).map((_, i) => ( {showAvatar && ( diff --git a/components/renderers/SwipeableRow.tsx b/components/renderers/SwipeableRow.tsx index 7a047d3..05e6028 100644 --- a/components/renderers/SwipeableRow.tsx +++ b/components/renderers/SwipeableRow.tsx @@ -78,7 +78,7 @@ export function SwipeableRow({ onEdit(); }} accessibilityRole="button" - accessibilityLabel="Edit" + accessibilityLabel={t("common.edit")} className="items-center justify-center bg-blue-600 active:opacity-80" style={{ width: ACTION_WIDTH }} > @@ -94,7 +94,7 @@ export function SwipeableRow({ onDelete(); }} accessibilityRole="button" - accessibilityLabel="Delete" + accessibilityLabel={t("common.delete")} className="items-center justify-center bg-red-600 active:opacity-80" style={{ width: ACTION_WIDTH }} > diff --git a/components/ui/BottomSheet.tsx b/components/ui/BottomSheet.tsx index 13fbc95..815b53d 100644 --- a/components/ui/BottomSheet.tsx +++ b/components/ui/BottomSheet.tsx @@ -1,5 +1,6 @@ import React from "react"; import { Modal, Pressable, Text, View } from "react-native"; +import { useTranslation } from "react-i18next"; import { cn } from "~/lib/utils"; export interface BottomSheetProps { @@ -17,6 +18,7 @@ export function BottomSheet({ children, className, }: BottomSheetProps) { + const { t } = useTranslation(); return ( onOpenChange(false)} /> diff --git a/components/ui/Reasoning.tsx b/components/ui/Reasoning.tsx index 2ef3f6c..dc4ebb4 100644 --- a/components/ui/Reasoning.tsx +++ b/components/ui/Reasoning.tsx @@ -29,7 +29,7 @@ export function Reasoning({ className="flex-row items-center gap-2 px-2.5 py-2 active:bg-muted" onPress={() => setOpen((o) => !o)} accessibilityRole="button" - accessibilityLabel="Reasoning" + accessibilityLabel={t("ai.reasoning")} accessibilityState={{ expanded: open }} > diff --git a/components/ui/Toast.tsx b/components/ui/Toast.tsx index 3889755..dcb9507 100644 --- a/components/ui/Toast.tsx +++ b/components/ui/Toast.tsx @@ -1,6 +1,7 @@ import React from "react"; import { Pressable, Text, View } from "react-native"; import { X } from "lucide-react-native"; +import { useTranslation } from "react-i18next"; import * as Haptics from "expo-haptics"; import { cn } from "~/lib/utils"; @@ -35,6 +36,7 @@ const variantTextStyles: Record = { }; export function ToastProvider({ children }: { children: React.ReactNode }) { + const { t: tr } = useTranslation(); const [toasts, setToasts] = React.useState([]); const dismiss = React.useCallback((id: number) => { @@ -90,7 +92,7 @@ export function ToastProvider({ children }: { children: React.ReactNode }) { onPress={() => dismiss(t.id)} hitSlop={8} accessibilityRole="button" - accessibilityLabel="Dismiss" + accessibilityLabel={tr("a11y.dismiss")} className="active:opacity-70" > diff --git a/components/ui/ToolInvocations.tsx b/components/ui/ToolInvocations.tsx index 557e78a..04e79cd 100644 --- a/components/ui/ToolInvocations.tsx +++ b/components/ui/ToolInvocations.tsx @@ -1,6 +1,7 @@ import { useState } from "react"; import { View, Text, Pressable } from "react-native"; import { ChevronRight, ChevronDown, Wrench, Check, Loader } from "lucide-react-native"; +import { useTranslation } from "react-i18next"; import { cn } from "~/lib/utils"; import type { ToolInvocation } from "~/lib/ai-chat"; @@ -87,9 +88,10 @@ export function ToolInvocations({ tools: ToolInvocation[]; className?: string; }) { + const { t } = useTranslation(); if (!tools || tools.length === 0) return null; return ( - + {tools.map((t) => ( ))} diff --git a/components/workflow/StateMachineViewer.tsx b/components/workflow/StateMachineViewer.tsx index 5a3d345..ef96b67 100644 --- a/components/workflow/StateMachineViewer.tsx +++ b/components/workflow/StateMachineViewer.tsx @@ -1,5 +1,6 @@ import React from "react"; import { View, Text, ScrollView } from "react-native"; +import { useTranslation } from "react-i18next"; /* ------------------------------------------------------------------ */ /* Props */ @@ -96,6 +97,7 @@ export function StateMachineViewer({ currentState, scrollable = true, }: StateMachineViewerProps) { + const { t: tr } = useTranslation(); if (states.length === 0) { return ( @@ -144,14 +146,14 @@ export function StateMachineViewer({ if (!scrollable) { return ( - + {body} ); } return ( - + {body} ); diff --git a/locales/ar.json b/locales/ar.json index 81e9f5d..979f1a9 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -261,6 +261,18 @@ "flowsDesc": "لا توجد تدفقات أتمتة على هذا الخادم بعد.", "flowCount": "{{count}} تدفقات" }, + "a11y": { + "goBack": "رجوع", + "clearSearch": "مسح البحث", + "dismiss": "إغلاق", + "toolActivity": "نشاط الأداة", + "flowDiagram": "مخطط التدفق", + "stateMachineDiagram": "مخطط آلة الحالة", + "loadingList": "جارٍ تحميل القائمة", + "loadingDashboard": "جارٍ تحميل لوحة المعلومات", + "loadingDetail": "جارٍ تحميل التفاصيل", + "loadingForm": "جارٍ تحميل النموذج" + }, "nav": { "home": "الرئيسية", "search": "بحث", diff --git a/locales/en.json b/locales/en.json index 81b01da..802b078 100644 --- a/locales/en.json +++ b/locales/en.json @@ -257,6 +257,18 @@ "flowsDesc": "This server has no automation flows yet.", "flowCount": "{{count}} flows" }, + "a11y": { + "goBack": "Go back", + "clearSearch": "Clear search", + "dismiss": "Dismiss", + "toolActivity": "Tool activity", + "flowDiagram": "Flow diagram", + "stateMachineDiagram": "State machine diagram", + "loadingList": "Loading list", + "loadingDashboard": "Loading dashboard", + "loadingDetail": "Loading detail", + "loadingForm": "Loading form" + }, "nav": { "home": "Home", "search": "Search", diff --git a/locales/zh.json b/locales/zh.json index 265865b..a629139 100644 --- a/locales/zh.json +++ b/locales/zh.json @@ -256,6 +256,18 @@ "flowsDesc": "此服务器还没有自动化流程。", "flowCount": "{{count}} 个流程" }, + "a11y": { + "goBack": "返回", + "clearSearch": "清除搜索", + "dismiss": "关闭", + "toolActivity": "工具活动", + "flowDiagram": "流程图", + "stateMachineDiagram": "状态机图", + "loadingList": "正在加载列表", + "loadingDashboard": "正在加载仪表盘", + "loadingDetail": "正在加载详情", + "loadingForm": "正在加载表单" + }, "nav": { "home": "首页", "search": "搜索",