fix(mobile): fill English copy that stayed Chinese on Android

Translate password help, tag explanations, settings descriptions, and the untitled-note fallback so the English Android UI no longer mixes languages.
This commit is contained in:
tianma-if committed 2026-09-28 14:54:35 +08:00
1 parent 46c8d1be44
commit e7bef83655
7 files changed
+89 -17

No files matched your search

+2 -2
View File
@@ -20,7 +20,7 @@ import {
} from "../lib/mobile-editor-controller";
import { clearMobileMemoDraft, writeMobileMemoDraft, type MobileMemoDraft } from "../lib/mobile-drafts";
import { prepareUploadAsset } from "../lib/mobile-image-upload";
import { useMobileLocale } from "../lib/mobile-locale";
import { localizeMissingNotebookName, useMobileLocale } from "../lib/mobile-locale";
import { useSession } from "../lib/session";
import { recordEditorStartup } from "../lib/startup-performance";
import { safeDomCall } from "../lib/safe-dom-call";
@@ -144,7 +144,7 @@ export const useMobileRichEditor = ({
const [error, setError] = useState<string | null>(null);
const [resourceTarget, setResourceTarget] = useState<MobileResourceTarget | null>(null);
const { pickUploadAssets, uploadSourcePicker } = useMobileEditorUploadAsset();
const notebookLabel = notebooks.find((notebook) => notebook.id === notebookId)?.name ?? "未分类";
const notebookLabel = notebooks.find((notebook) => notebook.id === notebookId)?.name ?? localizeMissingNotebookName(resolvedLocale);
const saveLabel = error
? "保存失败"
: saving
+24 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test";
import { translateMobileText } from "./mobile-locale.tsx";
import { localizeMissingNotebookName, localizeUntitledMemoTitle, translateMobileText } from "./mobile-locale.tsx";
describe("mobile locale translation", () => {
test("keeps Chinese copy unchanged for the Chinese locale", () => {
@@ -22,4 +22,27 @@ describe("mobile locale translation", () => {
expect(translateMobileText("永久删除 3 条笔记", "en-US")).toBe("Delete 3 notes permanently");
expect(translateMobileText("当前:Inbox,3 条笔记", "en-US")).toBe("Current: Inbox, 3 notes");
});
test("translates the mixed English explanations from the Android locale report", () => {
expect(translateMobileText("修改后会保留当前设备登录,并退出其他设备上的登录会话。", "en-US"))
.toBe("Keeps this device signed in and signs out other sessions.");
expect(translateMobileText("修改后会保留当前设备登录,并退出其他设备上的登录会话。", "ja"))
.toBe("この端末のログインは維持し、他の端末のセッションは終了します。");
expect(translateMobileText("添加标签", "en-US")).toBe("Add tags");
expect(translateMobileText("没有找到适合这篇笔记的新标签。", "en-US")).toBe("No new tags fit this note.");
expect(translateMobileText("切换产品界面的显示语言。", "en-US")).toBe("Choose the language used in the app.");
expect(translateMobileText("当前密码不正确。", "en-US")).toBe("The current password is incorrect.");
expect(translateMobileText("修改中...", "ja")).toBe("変更中...");
});
test("localizes only the default untitled title and the missing-notebook fallback", () => {
expect(localizeUntitledMemoTitle("", "en-US")).toBe("Untitled note");
expect(localizeUntitledMemoTitle("无标题笔记", "en-US")).toBe("Untitled note");
expect(localizeUntitledMemoTitle("无标题笔记", "ja")).toBe("無題のノート");
expect(localizeUntitledMemoTitle(" 无标题笔记 ", "zh-CN")).toBe("无标题笔记");
expect(localizeUntitledMemoTitle("会议记录", "en-US")).toBe("会议记录");
expect(localizeMissingNotebookName("en-US")).toBe("Uncategorized");
expect(localizeMissingNotebookName("ja")).toBe("未分類");
expect(localizeMissingNotebookName("zh-CN")).toBe("未分类");
});
});
+48
View File
@@ -1,3 +1,4 @@
import { DEFAULT_MEMO_TITLE } from "@edgeever/shared";
import { enUS, ja, zhCN } from "@edgeever/shared/i18n";
import { resolveSupportedLocale } from "@edgeever/shared/i18n/locales";
import { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from "react";
@@ -165,6 +166,21 @@ const mobileOnlyTranslations = new Map<string, string>([
["当前内容将被模板内容替换。", "The current content will be replaced by the template."],
["替换", "Replace"],
["关闭", "Close"],
["修改后会保留当前设备登录,并退出其他设备上的登录会话。", "Keeps this device signed in and signs out other sessions."],
["添加标签", "Add tags"],
["正在生成智能标签", "Generating smart tags"],
["智能标签生成失败", "Could not generate smart tags"],
["智能标签已添加", "Smart tags added"],
["没有找到适合这篇笔记的新标签。", "No new tags fit this note."],
["没有匹配的笔记本", "No matching notebooks"],
["偏好设置", "Preferences"],
["切换产品界面的显示语言。", "Choose the language used in the app."],
["上传大图时在本地压缩,节省资源占用。", "Compress large images on this device before upload."],
["已恢复上次未完成的本地草稿", "Restored your unfinished local draft"],
["连接你的自托管笔记空间", "Connect to your self-hosted notes"],
["首次登录密码", "Password for first sign-in"],
["GitHub 仓库", "GitHub repository"],
["操作失败,请稍后再试", "Something went wrong. Please try again."],
]);
const mobileOnlyJapanese = new Map<string, string>([
@@ -314,6 +330,21 @@ const mobileOnlyJapanese = new Map<string, string>([
["当前内容将被模板内容替换。", "現在の内容はテンプレートの内容に置き換わります。"],
["替换", "置き換え"],
["关闭", "閉じる"],
["修改后会保留当前设备登录,并退出其他设备上的登录会话。", "この端末のログインは維持し、他の端末のセッションは終了します。"],
["添加标签", "タグを追加"],
["正在生成智能标签", "スマートタグを生成しています"],
["智能标签生成失败", "スマートタグを生成できませんでした"],
["智能标签已添加", "スマートタグを追加しました"],
["没有找到适合这篇笔记的新标签。", "このノートに合う新しいタグはありません。"],
["没有匹配的笔记本", "一致するノートブックはありません"],
["偏好设置", "環境設定"],
["切换产品界面的显示语言。", "アプリの表示言語を切り替えます。"],
["上传大图时在本地压缩,节省资源占用。", "大きな画像はアップロード前にこの端末で圧縮し、容量を抑えます。"],
["已恢复上次未完成的本地草稿", "前回の未完成の下書きを復元しました"],
["连接你的自托管笔记空间", "セルフホストのノートスペースに接続"],
["首次登录密码", "初回ログインのパスワード"],
["GitHub 仓库", "GitHub リポジトリ"],
["操作失败,请稍后再试", "操作に失敗しました。しばらくしてから再試行してください。"],
]);
const flattenStrings = (value: unknown, prefix = "", output = new Map<string, string>()) => {
@@ -416,6 +447,23 @@ export const translateMobileText = (value: string, locale: SupportedMobileLocale
let currentResolvedMobileLocale: SupportedMobileLocale = resolveSystemLocale();
export const translateCurrentMobileText = (value: string) => translateMobileText(value, currentResolvedMobileLocale);
export const localizeUntitledMemoTitle = (
title: string | null | undefined,
locale: SupportedMobileLocale,
) => {
const trimmed = title?.trim() ?? "";
if (trimmed && trimmed !== DEFAULT_MEMO_TITLE) {
return trimmed;
}
return translateMobileText(DEFAULT_MEMO_TITLE, locale);
};
export const localizeMissingNotebookName = (locale: SupportedMobileLocale) => {
if (locale === "en-US") return "Uncategorized";
if (locale === "ja") return "未分類";
return "未分类";
};
const MobileLocaleContext = createContext<MobileLocaleContextValue>({
preference: "system",
resolvedLocale: resolveSystemLocale(),
@@ -25,8 +25,8 @@ export const AccountSecurityPanel = ({
const passwordMutation = useMutation({
mutationFn: async () => {
if (!client) throw new Error("Client is not ready");
if (newPassword.length < 8) throw new Error("新密码至少需要 8 个字符");
if (newPassword !== confirmPassword) throw new Error("两次输入的新密码不一致");
if (newPassword.length < 8) throw new Error("新密码至少需要 8 个字符。");
if (newPassword !== confirmPassword) throw new Error("两次输入的新密码不一致。");
return client.changePassword({ currentPassword, newPassword, confirmPassword });
},
onSuccess: () => {
@@ -43,7 +43,7 @@ export const AccountSecurityPanel = ({
}, [active]);
const errorMessage = (error: unknown) => {
if (error instanceof ApiRequestError && error.code === "invalid_current_password") return "当前密码不正确";
if (error instanceof ApiRequestError && error.code === "invalid_current_password") return "当前密码不正确。";
return error instanceof Error ? error.message : "操作失败,请稍后再试";
};
@@ -63,7 +63,7 @@ export const AccountSecurityPanel = ({
{passwordMutation.isSuccess ? <Text accessibilityLiveRegion="polite" style={styles.success}>密码已修改成功。</Text> : null}
<PrimaryButton
disabled={passwordMutation.isPending}
label={passwordMutation.isPending ? "正在修改…" : "修改密码"}
label={passwordMutation.isPending ? "修改中..." : "修改密码"}
onPress={() => passwordMutation.mutate()}
/>
</View>
+2 -2
View File
@@ -22,7 +22,7 @@ import {
writeMobileMemoDraft,
type MobileMemoDraft,
} from "../lib/mobile-drafts";
import { useMobileLocale } from "../lib/mobile-locale";
import { localizeMissingNotebookName, useMobileLocale } from "../lib/mobile-locale";
import { useSession } from "../lib/session";
import { queueMobileMemoCreate, queueMobileMemoUpdate } from "../lib/sync-queue";
import { createMobileDataScope, upsertLocalMemo } from "../lib/local-mirror";
@@ -867,7 +867,7 @@ export const RichEditorModal = ({
const [resourceTarget, setResourceTarget] = useState<MobileResourceTarget | null>(null);
const editorStartup = useMobileEditorStartupGuard({ active: Boolean(memo && baseUrl), ready });
const { pickUploadAssets, uploadSourcePicker } = useMobileEditorUploadAsset();
const notebookLabel = notebooks.find((notebook) => notebook.id === notebookId)?.name ?? "未分类";
const notebookLabel = notebooks.find((notebook) => notebook.id === notebookId)?.name ?? localizeMissingNotebookName(resolvedLocale);
const saveLabel = error ? "保存失败" : saving ? "保存中" : uploading ? "上传中" : dirty ? (draftRestored ? "本地草稿" : "未保存") : ready ? "已保存" : "加载中";
const titleRef = useRef(title);
const tagsTextRef = useRef(tagsText);
@@ -27,7 +27,7 @@ import {
saveMobileResourceAs,
type MobileResourceTarget,
} from "../lib/mobile-attachments";
import { useMobileLocale } from "../lib/mobile-locale";
import { localizeUntitledMemoTitle, useMobileLocale } from "../lib/mobile-locale";
import {
createOnceProtectedResourceFailureNotifier,
isProtectedResourceSource,
@@ -625,7 +625,7 @@ export const MemoDetailModal = ({
}
}, []);
const memoTitle = memo?.title?.trim() || DEFAULT_MEMO_TITLE;
const memoTitle = localizeUntitledMemoTitle(memo?.title, resolvedLocale);
const memoTagsText = memo?.tags.join(", ") ?? "";
const metadataSearchMatches = useMemo(() => ({
tags: getTextSearchMatches(memoTagsText, searchQuery),
@@ -844,8 +844,8 @@ export const MemoDetailModal = ({
safeDomCall(() => viewerRef.current?.exportImage(JSON.stringify({
requestId,
format,
title: memo.title?.trim() || (resolvedLocale !== "zh-CN" ? "Untitled note" : "无标题笔记"),
fallbackTitle: resolvedLocale !== "zh-CN" ? "Untitled note" : "无标题笔记",
title: localizeUntitledMemoTitle(memo.title, resolvedLocale),
fallbackTitle: localizeUntitledMemoTitle(null, resolvedLocale),
notebook: options.showNotebook === false ? "" : notebookName,
tags: options.showTags === false ? [] : memo.tags,
updatedAt: options.showUpdatedAt === false ? "" : new Date(memo.updatedAt).toLocaleString(resolvedLocale),
+5 -4
View File
@@ -30,7 +30,7 @@ import {
type MobileLocalePreference,
type MobileMemoListDensity,
} from "../lib/preferences";
import { useMobileLocale } from "../lib/mobile-locale";
import { localizeUntitledMemoTitle, localizeMissingNotebookName, useMobileLocale } from "../lib/mobile-locale";
import { useSession } from "../lib/session";
import {
clearMobileMemoUpdateQueueItem,
@@ -969,9 +969,10 @@ export const WorkspaceScreen = ({
const response = await client.createMemoShare(memo.id);
const shareUrl = `${session.baseUrl.replace(/\/+$/, "")}/share/${encodeURIComponent(response.share.token)}`;
const shareTitle = localizeUntitledMemoTitle(memo.title, resolvedLocale);
await NativeShare.share({
message: `${memo.title?.trim() || DEFAULT_MEMO_TITLE}\n${shareUrl}`,
title: memo.title?.trim() || DEFAULT_MEMO_TITLE,
message: `${shareTitle}\n${shareUrl}`,
title: shareTitle,
url: shareUrl,
});
},
@@ -1277,7 +1278,7 @@ export const WorkspaceScreen = ({
isSaving={updateMemoMutation.isPending || localUpdateMemoMutation.isPending}
isSharing={shareMemoMutation.isPending}
memo={selectedMemo}
notebookName={notebooks.find((notebook) => notebook.id === selectedMemo?.notebookId)?.name ?? "未分类"}
notebookName={notebooks.find((notebook) => notebook.id === selectedMemo?.notebookId)?.name ?? localizeMissingNotebookName(resolvedLocale)}
notebooks={notebooks}
onClose={closeDetail}
onCloseEditor={closeRichEditor}