feat(i18n): extract remaining screens into en/zh-Hans catalogs; full Simplified Chinese coverage (closes #68) (#103)

Extends the i18n infra from #97 (Settings screen only) to the rest of the
app: session chat screen, connection add/edit/list screens, sessions list
(incl. directory grouping from #96), navigation titles, notifications
category metadata, error boundary, telemetry consent modal, auth gate, and
every chat UI component (permission/question prompts, status indicator,
model/variant pickers, directory switcher/browser, reasoning block, tool
call card, session info).

- 244 new keys added to en.json/zh-Hans.json with reviewed, natural
  Simplified Chinese (not machine-garbage), keeping key sets identical.
- User content, server URLs, code snippets, log/error-detail text, and
  diagnostics-classify.ts (pure dependency-free module feeding Sentry/
  support reports) are intentionally left untranslated per scope.
- Interpolation used for counts/names (e.g. reconnect attempt, files
  count, connection name in delete confirmations); categoryMeta/
  CONNECTION_TYPES switched to labelKey indirection since they're
  module-level constants evaluated before i18next is guaranteed ready.
- Added src/lib/i18n/catalog-parity.test.ts (node --test) asserting
  en.json/zh-Hans.json expose identical key sets and no empty values,
  to catch future locale drift.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Den
2026-07-17 10:52:41 -07:00
committed by GitHub
parent 0cac46cb36
commit f945cab5c6
26 changed files with 1249 additions and 337 deletions

View File

@@ -1,6 +1,7 @@
import { View, Text, FlatList, TouchableOpacity, StyleSheet, useColorScheme, Alert } from "react-native"
import { router } from "expo-router"
import { Ionicons } from "@expo/vector-icons"
import { useTranslation } from "react-i18next"
import { useConnections } from "../../src/stores/connections"
import { useSettings } from "../../src/stores/settings"
import type { ServerConnection } from "../../src/lib/types"
@@ -22,13 +23,14 @@ function ConnectionItem({
onEdit: () => void
onDelete: () => void
}) {
const { t } = useTranslation()
const typeIcon = connection.type === "local" ? "wifi" : connection.type === "tunnel" ? "globe" : "cloud"
const handleLongPress = () => {
Alert.alert(connection.name, "What would you like to do?", [
{ text: "Cancel", style: "cancel" },
{ text: "Edit", onPress: onEdit },
{ text: "Delete", style: "destructive", onPress: onDelete },
Alert.alert(connection.name, t("connectionsList.actionsAlert.message"), [
{ text: t("common.cancel"), style: "cancel" },
{ text: t("connectionsList.actionsAlert.edit"), onPress: onEdit },
{ text: t("common.delete"), style: "destructive", onPress: onDelete },
])
}
@@ -60,7 +62,9 @@ function ConnectionItem({
</Text>
{isActive && (
<View style={[styles.activeBadge, isDark && styles.activeBadgeDark]}>
<Text style={[styles.activeBadgeText, isDark && styles.activeBadgeTextDark]}>Active</Text>
<Text style={[styles.activeBadgeText, isDark && styles.activeBadgeTextDark]}>
{t("connectionsList.activeBadge")}
</Text>
</View>
)}
</View>
@@ -72,7 +76,7 @@ function ConnectionItem({
</Text>
{connection.lastConnected && (
<Text style={[styles.connectionMeta, isDark && styles.metaDark]}>
Last connected: {new Date(connection.lastConnected).toLocaleDateString()}
{t("connectionsList.lastConnected", { date: new Date(connection.lastConnected).toLocaleDateString() })}
</Text>
)}
</View>
@@ -86,19 +90,24 @@ function ConnectionItem({
export default function ConnectionsScreen() {
const colorScheme = useColorScheme()
const isDark = colorScheme === "dark"
const { t } = useTranslation()
const { connections, activeConnection, setActiveConnection, removeConnection } = useConnections()
const { pageSize, setPageSize } = useSettings()
const handleDelete = (connection: ServerConnection) => {
Alert.alert("Delete Connection", `Are you sure you want to delete "${connection.name}"?`, [
{ text: "Cancel", style: "cancel" },
{
text: "Delete",
style: "destructive",
onPress: () => removeConnection(connection.id),
},
])
Alert.alert(
t("connection.edit.alerts.deleteTitle"),
t("connection.edit.alerts.deleteMessage", { name: connection.name }),
[
{ text: t("common.cancel"), style: "cancel" },
{
text: t("common.delete"),
style: "destructive",
onPress: () => removeConnection(connection.id),
},
],
)
}
return (
@@ -119,24 +128,28 @@ export default function ConnectionsScreen() {
ListEmptyComponent={
<View style={styles.emptyContainer}>
<Ionicons name="server-outline" size={64} color={isDark ? "#444444" : "#cccccc"} />
<Text style={[styles.emptyTitle, isDark && styles.textDark]}>No Connections</Text>
<Text style={[styles.emptyTitle, isDark && styles.textDark]}>{t("connectionsList.empty.title")}</Text>
<Text style={[styles.emptySubtitle, isDark && styles.metaDark]}>
Add a connection to your OpenCode server
{t("connectionsList.empty.subtitle")}
</Text>
</View>
}
ListHeaderComponent={
<View style={[styles.header, isDark && styles.headerDark]}>
<Text style={[styles.headerText, isDark && styles.metaDark]}>Tap to switch, long press for options</Text>
<Text style={[styles.headerText, isDark && styles.metaDark]}>{t("connectionsList.header")}</Text>
</View>
}
ListFooterComponent={
<View style={[styles.settingsSection, isDark && styles.settingsSectionDark]}>
<Text style={[styles.settingsTitle, isDark && styles.textDark]}>Preferences</Text>
<Text style={[styles.settingsTitle, isDark && styles.textDark]}>
{t("connectionsList.preferences.title")}
</Text>
<View style={styles.settingRow}>
<View style={styles.settingLabel}>
<Ionicons name="layers-outline" size={18} color={isDark ? "#888888" : "#666666"} />
<Text style={[styles.settingText, isDark && styles.textDark]}>Messages per page</Text>
<Text style={[styles.settingText, isDark && styles.textDark]}>
{t("connectionsList.preferences.pageSizeLabel")}
</Text>
</View>
<View style={styles.pagePicker}>
{PAGE_SIZE_OPTIONS.map((size) => (
@@ -164,7 +177,7 @@ export default function ConnectionsScreen() {
</View>
</View>
<Text style={[styles.settingHint, isDark && styles.metaDark]}>
How many messages to load when opening a session. Lower = faster.
{t("connectionsList.preferences.pageSizeHint")}
</Text>
</View>
}