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:
1 parent
46c8d1be44
commit
e7bef83655
7 files changed
+89
-17
No files matched your search
@@ -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
|
||||
|
||||
@@ -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("未分类");
|
||||
});
|
||||
});
|
||||
@@ -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>
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in new issue
Block a user