diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx index 7c69237..96756a1 100644 --- a/app/(tabs)/index.tsx +++ b/app/(tabs)/index.tsx @@ -732,7 +732,7 @@ export default function SessionsScreen() { { diff --git a/app/connection/[id].tsx b/app/connection/[id].tsx index 49701d2..8043c3e 100644 --- a/app/connection/[id].tsx +++ b/app/connection/[id].tsx @@ -233,7 +233,7 @@ export default function EditConnectionScreen() { {t("connection.shared.serverUrl")} {t("connection.shared.directoryOptional")} {t("connection.shared.username")} : {t("connection.shared.directoryOptional")} {t("connection.shared.username")} {t("connection.shared.password")} cycleAgent(-1)} > - {agent || "build"} + {agent || t("session.agentFallback")} diff --git a/package-lock.json b/package-lock.json index 7a846b5..7010fd3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@opencode-ai/mobile", - "version": "0.4.11", + "version": "0.4.15", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@opencode-ai/mobile", - "version": "0.4.11", + "version": "0.4.15", "dependencies": { "@expo/vector-icons": "^15.0.3", "@gorhom/bottom-sheet": "5.2.8", diff --git a/src/components/chat/DirectorySwitcher.tsx b/src/components/chat/DirectorySwitcher.tsx index 314de86..a44e5f7 100644 --- a/src/components/chat/DirectorySwitcher.tsx +++ b/src/components/chat/DirectorySwitcher.tsx @@ -87,7 +87,7 @@ export function DirectorySwitcher({ sheetRef, current, recents, serverHome, isDa { diff --git a/src/components/chat/MessageBubble.tsx b/src/components/chat/MessageBubble.tsx index 0f82eca..4e8d38a 100644 --- a/src/components/chat/MessageBubble.tsx +++ b/src/components/chat/MessageBubble.tsx @@ -4,6 +4,7 @@ import { Ionicons } from "@expo/vector-icons" import { Markdown } from "../markdown" import { ToolCallCard } from "./ToolCallCard" import { ReasoningBlock } from "./ReasoningBlock" +import { useTranslation } from "react-i18next" import type { Message, Part } from "../../lib/sdk" const SCREEN_WIDTH = Dimensions.get("window").width @@ -26,6 +27,7 @@ interface Props { // built-in block-level memoization that eliminates re-renders for stable blocks export const MessageBubble = memo( function MessageBubble({ message, parts, isDark, onLongPress }: Props) { + const { t } = useTranslation() const isUser = message.role === "user" const textParts = parts.filter((p) => p.type === "text") @@ -55,7 +57,7 @@ export const MessageBubble = memo( size={14} color={isUser ? (isDark ? "#ffffff" : "#0a0a0a") : "#8b5cf6"} /> - {isUser ? "You" : "Assistant"} + {t("chat.messageBubble.roleYou")} {message.model && {message.model.modelID}} {!isUser && message.modelID && {message.modelID}} @@ -104,8 +106,8 @@ export const MessageBubble = memo( {/* Tokens/cost for assistant messages */} {!isUser && message.tokens && ( - {message.tokens.input + message.tokens.output} tokens - {message.cost ? ` · $${message.cost.toFixed(4)}` : ""} + {message.tokens.input + message.tokens.output}{t("chat.messageBubble.tokensSuffix")} + {message.cost ? ` · ${t("chat.messageBubble.costPrefix")}${message.cost.toFixed(4)}` : ""} )} diff --git a/src/components/markdown/CodeBlock.tsx b/src/components/markdown/CodeBlock.tsx index 8265309..53fc165 100644 --- a/src/components/markdown/CodeBlock.tsx +++ b/src/components/markdown/CodeBlock.tsx @@ -1,6 +1,7 @@ import { useState } from "react" import { View, Text, TouchableOpacity, StyleSheet, useColorScheme, Platform, ScrollView } from "react-native" import * as Clipboard from "expo-clipboard" +import { useTranslation } from "react-i18next" import { WIDE_CONTENT_SCROLL_CONFIG } from "../../lib/scroll-config" interface Props { @@ -9,6 +10,7 @@ interface Props { } export function CodeBlock({ code, language }: Props) { + const { t } = useTranslation() const isDark = useColorScheme() === "dark" const [copied, setCopied] = useState(false) @@ -23,9 +25,9 @@ export function CodeBlock({ code, language }: Props) { return ( - {language || "code"} + {language || t("common.fallbackLanguage")} - {copied ? "Copied!" : "Copy"} + {copied ? t("common.copied") : t("common.copy")} diff --git a/src/lib/i18n/en.json b/src/lib/i18n/en.json index 13519f2..e091d4e 100644 --- a/src/lib/i18n/en.json +++ b/src/lib/i18n/en.json @@ -6,7 +6,10 @@ "delete": "Delete", "retry": "Retry", "save": "Save", - "shareReport": "Share report" + "shareReport": "Share report", + "copy": "复制", + "copied": "已复制", + "fallbackLanguage": "代码" }, "settings": { "sections": { @@ -123,7 +126,8 @@ "imageFailedMessage": "One or more images could not be processed. Please try a different photo.", "speechErrorTitle": "Voice input failed", "speechErrorMessage": "Could not use voice input. Check your microphone permission and try again." - } + }, + "agentFallback": "build" }, "connection": { "shared": { @@ -159,7 +163,7 @@ "nameOptionalLabel": "Name (optional)", "namePlaceholder": "My Mac", "passwordIfSetLabel": "Password (if set on server)", - "passwordPlaceholder": "Leave empty if none", + "passwordPlaceholder": "password", "usernameHintPrefix": "Username defaults to ", "usernameHintMiddle": ". Custom username? Use ", "usernameHintSuffix": ".", @@ -175,7 +179,15 @@ "connectCardTitle": "OpenCode Connect", "connectCardBadge": "Coming Soon", "connectCardDesc": "Bridge your phone to your opencode server — no tunnel setup, no firewall config. One-tap connect from anywhere.", - "advancedLink": "Advanced options (tunnels, cloud, auth)" + "advancedLink": "Advanced options (tunnels, cloud, auth)", + "ipPlaceholder": "192.168.1.100", + "portPlaceholder": "4096", + "emailPlaceholder": "your@email.com", + "urlPlaceholderLocal": "http://192.168.1.100:4096", + "urlPlaceholderTunnel": "https://your-tunnel.trycloudflare.com", + "urlPlaceholderCloud": "https://api.opencode.ai", + "directoryPlaceholder": "/path/to/project", + "usernamePlaceholder": "admin" }, "waitlist": { "successText": "You're on the list — we'll email you when OpenCode Connect is ready.", @@ -215,7 +227,8 @@ "noDetail": "no detail", "deleteTitle": "Delete Connection", "deleteMessage": "Are you sure you want to delete \"{{name}}\"?" - } + }, + "urlPlaceholder": "http://192.168.1.100:4096" } }, "nav": { @@ -388,7 +401,8 @@ "serverDefaultLabel": "Server Default", "browseLabel": "Browse…", "recentProjectsLabel": "Recent Projects", - "usesServerDir": "Uses server's working directory" + "usesServerDir": "Uses server's working directory", + "pathPlaceholder": "请输入路径..." }, "directoryBrowserSheet": { "noActiveConnection": "No active connection", @@ -434,6 +448,12 @@ "hoursAgo": "{{count}}h ago", "daysAgo": "{{count}}d ago" } + }, + "messageBubble": { + "roleYou": "你", + "roleAssistant": "AI 助手", + "tokensSuffix": " tokens", + "costPrefix": "$" } }, "demo": { @@ -452,4 +472,4 @@ "dismiss": "Not now", "upToDate": "Up to date" } -} +} \ No newline at end of file diff --git a/src/lib/i18n/zh-Hans.json b/src/lib/i18n/zh-Hans.json index 9ba906f..91678f9 100644 --- a/src/lib/i18n/zh-Hans.json +++ b/src/lib/i18n/zh-Hans.json @@ -6,7 +6,10 @@ "delete": "删除", "retry": "重试", "save": "保存", - "shareReport": "分享报告" + "shareReport": "分享报告", + "copy": "复制", + "copied": "已复制", + "fallbackLanguage": "代码" }, "settings": { "sections": { @@ -123,7 +126,8 @@ "imageFailedMessage": "一张或多张图片无法处理。请尝试其他照片。", "speechErrorTitle": "语音输入失败", "speechErrorMessage": "无法使用语音输入。请检查麦克风权限后重试。" - } + }, + "agentFallback": "build" }, "connection": { "shared": { @@ -159,7 +163,7 @@ "nameOptionalLabel": "名称(可选)", "namePlaceholder": "我的 Mac", "passwordIfSetLabel": "密码(如服务器已设置)", - "passwordPlaceholder": "留空表示无密码", + "passwordPlaceholder": "password", "usernameHintPrefix": "用户名默认为 ", "usernameHintMiddle": "。需要自定义用户名?请使用", "usernameHintSuffix": "。", @@ -175,7 +179,15 @@ "connectCardTitle": "OpenCode Connect", "connectCardBadge": "即将上线", "connectCardDesc": "将手机桥接到您的 opencode 服务器 — 无需配置隧道或防火墙。一键连接,随时随地。", - "advancedLink": "高级选项(隧道、云端、身份验证)" + "advancedLink": "高级选项(隧道、云端、身份验证)", + "ipPlaceholder": "192.168.1.100", + "portPlaceholder": "4096", + "emailPlaceholder": "your@email.com", + "urlPlaceholderLocal": "http://192.168.1.100:4096", + "urlPlaceholderTunnel": "https://your-tunnel.trycloudflare.com", + "urlPlaceholderCloud": "https://api.opencode.ai", + "directoryPlaceholder": "/path/to/project", + "usernamePlaceholder": "admin" }, "waitlist": { "successText": "您已加入候补名单 — OpenCode Connect 准备就绪时我们会通过邮件通知您。", @@ -215,7 +227,8 @@ "noDetail": "无详细信息", "deleteTitle": "删除连接", "deleteMessage": "确定要删除 \"{{name}}\" 吗?" - } + }, + "urlPlaceholder": "http://192.168.1.100:4096" } }, "nav": { @@ -388,7 +401,8 @@ "serverDefaultLabel": "服务器默认", "browseLabel": "浏览…", "recentProjectsLabel": "最近项目", - "usesServerDir": "使用服务器的工作目录" + "usesServerDir": "使用服务器的工作目录", + "pathPlaceholder": "请输入路径..." }, "directoryBrowserSheet": { "noActiveConnection": "没有活动连接", @@ -434,6 +448,12 @@ "hoursAgo": "{{count}} 小时前", "daysAgo": "{{count}} 天前" } + }, + "messageBubble": { + "roleYou": "你", + "roleAssistant": "AI 助手", + "tokensSuffix": " tokens", + "costPrefix": "$" } }, "demo": { @@ -452,4 +472,4 @@ "dismiss": "暂不", "upToDate": "已是最新" } -} +} \ No newline at end of file