diff --git a/apps/mobile/src/screens/WorkspaceScreen.tsx b/apps/mobile/src/screens/WorkspaceScreen.tsx index c6ffb4cf..5698fadf 100644 --- a/apps/mobile/src/screens/WorkspaceScreen.tsx +++ b/apps/mobile/src/screens/WorkspaceScreen.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useRef, useState, type ReactNode } from "react"; +import { createContext, useContext, useEffect, useMemo, useRef, useState, type ReactNode } from "react"; import { useMutation, useQuery, useQueryClient, type UseMutationResult } from "@tanstack/react-query"; import * as Clipboard from "expo-clipboard"; import Constants from "expo-constants"; @@ -226,6 +226,8 @@ type NotebookOption = { }; type MobileNotebookSortMode = MobileNotebookSortPreference; type MobileLocaleMode = MobileLocalePreference; +const MobileLocaleContext = createContext("system"); +const useMobileLocalePreference = () => useContext(MobileLocaleContext); type TextSelection = { start: number; end: number; @@ -827,8 +829,9 @@ export const WorkspaceScreen = () => { }, [client, lastAutoSyncAt]); return ( - - + + + {activeView === "notes" ? ( { onPress={() => setActiveView("settings")} /> - + + ); }; @@ -2254,6 +2258,7 @@ const NotebookManagerModal = ({ const TagsManagerModal = ({ onClose, visible }: { onClose: () => void; visible: boolean }) => { const { client } = useSession(); const queryClient = useQueryClient(); + const localePreference = useMobileLocalePreference(); const [editingTagName, setEditingTagName] = useState(null); const [editingTagValue, setEditingTagValue] = useState(""); @@ -2367,7 +2372,7 @@ const TagsManagerModal = ({ onClose, visible }: { onClose: () => void; visible: #{tag.name} - {tag.memoCount} 条笔记 · {tag.updatedAt ? formatDate(tag.updatedAt) : "未更新"} + {tag.memoCount} 条笔记 · {tag.updatedAt ? formatDate(tag.updatedAt, localePreference) : "未更新"} )} @@ -2636,6 +2641,7 @@ const ApiTokenRow = ({ const tokenCopyLabel = `token-${token.id}`; const configCopyLabel = `config-${token.id}`; const canCopyToken = Boolean(token.token && !token.isRevoked); + const localePreference = useMobileLocalePreference(); return ( @@ -2646,7 +2652,7 @@ const ApiTokenRow = ({ {token.scopes.map(getTokenScopeLabel).join("、") || "无权限"} - {token.lastUsedAt ? `最近使用 ${formatDate(token.lastUsedAt)}` : "从未使用"} + {token.lastUsedAt ? `最近使用 ${formatDate(token.lastUsedAt, localePreference)}` : "从未使用"} token.token && onCopy(token.token, tokenCopyLabel)}> @@ -2805,6 +2811,7 @@ const SyncQueueModal = ({ }) => { const [items, setItems] = useState([]); const [loading, setLoading] = useState(false); + const localePreference = useMobileLocalePreference(); const refreshItems = async () => { setLoading(true); @@ -2887,7 +2894,7 @@ const SyncQueueModal = ({ {item.memoId} - 更新于 {formatDate(item.updatedAt)} · 尝试 {item.attemptCount} 次 + 更新于 {formatDate(item.updatedAt, localePreference)} · 尝试 {item.attemptCount} 次 {item.lastError ? {item.lastError} : null} @@ -2925,6 +2932,8 @@ const SystemInfoModal = ({ visible: boolean; }) => { const [copied, setCopied] = useState(false); + const localePreference = useMobileLocalePreference(); + const resolvedLocale = getResolvedMobileLocale(localePreference); const expoConfig = Constants.expoConfig; const expoExtra = expoConfig?.extra as { eas?: { projectId?: string } } | undefined; const nativeIdentifier = Platform.select({ @@ -2946,7 +2955,7 @@ const SystemInfoModal = ({ { label: "笔记本数量", value: String(notebookCount) }, { label: "笔记总数", value: String(memoCount) }, { label: "时区", value: Intl.DateTimeFormat().resolvedOptions().timeZone || "未知" }, - { label: "语言", value: Intl.DateTimeFormat().resolvedOptions().locale || "未知" }, + { label: "语言", value: localePreference === "system" ? `${resolvedLocale}(跟随系统)` : resolvedLocale }, ]; const copySystemInfo = async () => { @@ -3299,6 +3308,7 @@ const ResourceCard = ({ }) => { const source = resource.memoDeleted ? "已删除笔记" : resource.memoTitle || resource.memoExcerpt || resource.memoId; const isImage = resource.kind === "image"; + const localePreference = useMobileLocalePreference(); return ( @@ -3325,7 +3335,7 @@ const ResourceCard = ({ ) : ( <> - {formatBytes(resource.byteSize)} · {resource.mimeType?.split("/")[1] || resource.kind} · {formatDate(resource.createdAt)} + {formatBytes(resource.byteSize)} · {resource.mimeType?.split("/")[1] || resource.kind} · {formatDate(resource.createdAt, localePreference)} 来源:{source} @@ -3404,6 +3414,7 @@ const RevisionHistoryModal = ({ }) => { const { client } = useSession(); const queryClient = useQueryClient(); + const localePreference = useMobileLocalePreference(); const [selectedRevisionId, setSelectedRevisionId] = useState(null); const revisionsQuery = useQuery({ @@ -3506,7 +3517,7 @@ const RevisionHistoryModal = ({ > 修订 {revision.revision} - {formatDate(revision.createdAt)} · {formatRevisionActor(revision.createdBy)} + {formatDate(revision.createdAt, localePreference)} · {formatRevisionActor(revision.createdBy)} ))} @@ -3605,6 +3616,7 @@ const MemoDetailModal = ({ const [searchOpen, setSearchOpen] = useState(false); const [searchQuery, setSearchQuery] = useState(""); const [activeMatchIndex, setActiveMatchIndex] = useState(0); + const localePreference = useMobileLocalePreference(); const detailText = memo?.contentMarkdown || memo?.contentText || "没有正文内容"; const searchMatches = useMemo(() => getTextSearchMatches(detailText, searchQuery), [detailText, searchQuery]); const searchMatchLabel = searchQuery.trim() ? `${searchMatches.length > 0 ? activeMatchIndex + 1 : 0}/${searchMatches.length}` : "0/0"; @@ -3649,7 +3661,7 @@ const MemoDetailModal = ({ {memo.title?.trim() || DEFAULT_MEMO_TITLE} - {formatDate(memo.updatedAt)} + {formatDate(memo.updatedAt, localePreference)} 修订 {memo.revision} @@ -4631,36 +4643,40 @@ const MemoCard = ({ onPress: () => void; selected?: boolean; selectionMode?: boolean; -}) => ( - - - {selectionMode ? ( - - {selected ? : null} - - ) : ( - - )} - - {memo.title?.trim() || DEFAULT_MEMO_TITLE} - - {memo.isPinned ? 置顶 : null} - - {listDensity === "preview" ? ( - - {memo.excerpt || "没有正文预览"} - - ) : null} - - {formatDate(memo.updatedAt)} - {memo.tags.slice(0, 2).map((tag) => ( - - #{tag} +}) => { + const localePreference = useMobileLocalePreference(); + + return ( + + + {selectionMode ? ( + + {selected ? : null} + + ) : ( + + )} + + {memo.title?.trim() || DEFAULT_MEMO_TITLE} - ))} - - -); + {memo.isPinned ? 置顶 : null} + + {listDensity === "preview" ? ( + + {memo.excerpt || "没有正文预览"} + + ) : null} + + {formatDate(memo.updatedAt, localePreference)} + {memo.tags.slice(0, 2).map((tag) => ( + + #{tag} + + ))} + + + ); +}; const PanelRow = ({ label, value }: { label: string; value: string }) => ( @@ -5084,8 +5100,11 @@ const isNotebookDescendant = (notebooks: Notebook[], candidateNotebookId: string return false; }; -const formatDate = (value: string) => - new Intl.DateTimeFormat("zh-CN", { +const getResolvedMobileLocale = (localePreference: MobileLocaleMode) => + localePreference === "system" ? Intl.DateTimeFormat().resolvedOptions().locale || "zh-CN" : localePreference; + +const formatDate = (value: string, localePreference: MobileLocaleMode = "system") => + new Intl.DateTimeFormat(getResolvedMobileLocale(localePreference), { month: "2-digit", day: "2-digit", hour: "2-digit",