Skip to content

Commit 4f6ca05

Browse files
chaxusclaude
andcommitted
refactor: one declaration for post, one file per language
Two pieces of duplication, both the kind that makes a rule someone has to get right N times instead of once. `declare function post(...)` appeared in 35 specs, byte-identical. It belongs to the embed demo page, not to any one spec, so it is declared once in test/e2e/lib/embed-demo.d.ts -- ambient, nothing to import. Pure type change; nothing that runs is different. packages/shared/src/i18n.ts was 1263 lines, of which about a thousand were seven message tables in one object literal. Adding one UI string meant editing seven places in one file (which is exactly what happened twice today), and two people touching two different languages touched the same lines. Each table is now its own file under src/i18n/messages/, the interface is in src/i18n/types.ts, and i18n.ts is 371 lines of language resolution and t(). `@ranuts/shared/i18n` is unchanged as an import path, and I18nMessages is re-exported from where it always was. Adding a language is now adding a file rather than finding the right thousand-line seam. Verified: 3422 unit tests, the site build, and the full E2E suite (165 passed) -- the last because every user-visible string in the app comes through this table. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MKNHFQ1kek1cHeqhbH37Pk
1 parent f55514f commit 4f6ca05

46 files changed

Lines changed: 1035 additions & 992 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎CLAUDE.md‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ lib/ # 应用层(纯 TypeScript,只在本站点用)
6161
events.ts # MessageCodec 事件处理(桌面端集成)
6262
file-types.ts # OnlyOffice 文件类型常量映射
6363
loading.ts # 加载状态 UI
64+
confirm-dialog.ts # 确认弹窗(ranui r-modal 封装)
65+
editor-theme.ts # 站点主题 → 编辑器 uiTheme 的跟随
6466
onlyoffice-editor.ts # 编辑器生命周期门面:挂载/重建/loadEditorApi,并对外统一导出下面这些模块
6567
onlyoffice/ # 编辑器周边(2026-08-19 从 1975 行的单文件拆出,公开导出面不变)
6668
iframe-guards.ts # 14 条运行时守卫的编排;每条守卫一个文件在 guards/
@@ -96,7 +98,13 @@ lib/ # 应用层(纯 TypeScript,只在本站点用)
9698
sw-update.ts # SW 更新策略的编辑器一侧(有文档打开时不提升 worker);落地页一侧在 public/sw-register.js
9799
agent-plugin/ # Agent 协同编辑:editor-bridge(直调 window.editor)、tools、ui/
98100
packages/ # pnpm workspace,供 ran 生态三处站点共享(包名 @ranuts/*)
99-
shared/ # document-types / document-utils / i18n(7 种站点语言:en / zh-CN / ja / ko / de / es / pt,词条表都是完整的;编辑器 UI 语言另由 vendor 45 语言包提供)/ store(createSignal)
101+
shared/ # document-types / document-utils / store(createSignal)
102+
src/i18n.ts # 语言解析、`t()`、locale 工具;**词条表不在这里**
103+
src/i18n/types.ts # `I18nMessages` 接口(每个 locale 文件只 import 它)
104+
src/i18n/messages/ # 一个语言一个文件:zh-CN / en / ja / ko / de / es / pt,
105+
# 七张表都是完整的(编辑器 UI 语言另由 vendor 45 语言包提供)。
106+
# 2026-09-12 从 1263 行的单文件拆开:加一条文案是改七个文件而
107+
# 不是同一个文件的七处,加一种语言就是加一个文件
100108
converter/ # 格式转换:CSV↔XLSX(SheetJS)、docx-zip 媒体处理、签名嗅探、PDF 字体清单
101109
agent-core/ # LLM 运行时 + 多 Provider(anthropic/openai/gemini/ollama/webllm)+ key 存储
102110
chat-ui/ # 聊天面板 UI
@@ -265,6 +273,10 @@ HEAD 的提交时间,窗口各留一个周期加一小时的余量。**gate
265273
`sw-warm` 读到 `runtime-e2e-next`、`sw-vendor-cache-first` 找不到自己刚种下的
266274
哨兵,都是这个。两半必须成对,`workflow-contract.test.ts` 钉住。
267275

276+
`post()` 是 embed-demo 页面的协议包装(`public/embed-demo.html` 定义,多数 spec 靠它驱动
277+
真实编辑器),声明在 `test/e2e/lib/embed-demo.d.ts` 里一份就够,**别再在 spec 里重抄**
278+
——曾经 35 个 spec 各抄一遍同样的那一行。
279+
268280
**L0 全局 fixture(`test/e2e/lib/l0.ts`,2026-08-15 起所有 spec 从它
269281
导入 `test`/`expect`)**:自动把 `asc_onError`、厂商致命弹窗、编辑器 iframe
270282
内的 `unhandledrejection`/`error`、pageerror、非白名单 console.error 判为

‎packages/shared/src/i18n.ts‎

Lines changed: 29 additions & 921 deletions
Large diffs are not rendered by default.
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import type { I18nMessages } from '../types';
2+
3+
/**
4+
* The `de` message table.
5+
* Filled in from en.json where a translation is missing, which is what keeps
6+
* an untranslated string showing in English rather than as a raw key.
7+
*
8+
* Interpolation placeholders ({size}, {title}, {when}, {days}, {count},
9+
* {page}, {pages}) have to survive translation verbatim -- the
10+
* message-placeholders cases in test/unit/i18n.test.ts check that.
11+
*/
12+
export const de: Partial<I18nMessages> = {
13+
webOffice: 'Web Office',
14+
uploadDocument: 'Dokument öffnen / bearbeiten',
15+
newWord: 'Neues Word-Dokument',
16+
newExcel: 'Neue Excel-Tabelle',
17+
newPowerPoint: 'Neue PowerPoint-Präsentation',
18+
themeLabel: 'Design',
19+
themeSystem: 'System',
20+
themeLight: 'Hell',
21+
themeDark: 'Dunkel',
22+
fileSavedSuccess: 'Datei gespeichert: ',
23+
documentLoaded: 'Dokument geladen: ',
24+
failedToLoadEditor:
25+
'Der Editor konnte nicht geladen werden. Bitte prüfen, ob die OnlyOffice-API korrekt eingebunden ist.',
26+
unsupportedFileType: 'Nicht unterstützter Dateityp: ',
27+
invalidFileObject: 'Ungültige Datei',
28+
documentOperationFailed: 'Dokumentvorgang fehlgeschlagen: ',
29+
openUrlFailed: 'Dieser Link konnte nicht geöffnet werden: ',
30+
openUrlUnreachable:
31+
'Die Datei konnte nicht abgerufen werden. Die Website erlaubt anderen Seiten möglicherweise nicht, ihre Dateien zu lesen, oder das Netzwerk ist nicht erreichbar. Laden Sie die Datei herunter und öffnen Sie sie von Ihrem Gerät.',
32+
editorErrorToast: 'Dokumentfehler',
33+
editorErrorFormatMismatch:
34+
'Der Inhalt der Datei passt nicht zur Dateiendung – bitte das Format prüfen und erneut versuchen',
35+
editorErrorOpenFailed:
36+
'Die Datei konnte nicht geöffnet werden: möglicherweise beschädigt, in einem nicht unterstützten Format, oder der Inhalt passt nicht zur Endung',
37+
editorErrorOutOfMemory:
38+
'Dieser Browser konnte keinen Speicher für die Konvertierungs-Engine belegen (etwa {mb} MB). Schließen Sie andere Tabs oder Fenster und versuchen Sie es erneut; falls es weiterhin fehlschlägt, verwenden Sie einen 64-Bit-Browser (Edge oder 64-Bit-Chrome).',
39+
editorOpenRetrying:
40+
'Der Editor war beim Öffnen des Dokuments noch nicht bereit; es wird automatisch erneut versucht …',
41+
agentTitle: 'KI-Assistent',
42+
agentOpenTip: 'KI-Assistenten öffnen',
43+
agentSettings: 'Einstellungen',
44+
agentRoleUser: 'Sie',
45+
agentRoleTool: 'Werkzeug',
46+
agentRoleError: 'Fehler',
47+
agentProviderClaude: 'Claude (Cloud, API-Schlüssel erforderlich)',
48+
agentProviderOpenAI: 'OpenAI (Cloud, API-Schlüssel erforderlich)',
49+
agentProviderGemini: 'Gemini (Cloud, API-Schlüssel erforderlich)',
50+
agentProviderLocal: 'Lokal und offline (WebLLM, WebGPU erforderlich)',
51+
agentProviderOllama: 'Ollama (lokaler Server, selbst gestartet)',
52+
agentOllamaModelPlaceholder: 'Modellname, z. B. llama3.2',
53+
agentOllamaHint:
54+
'Verbindet sich mit dem lokalen Ollama (http://localhost:11434); kein API-Schlüssel nötig – achten Sie darauf, dass das Modell läuft.',
55+
agentLoadModel: 'Modell laden',
56+
agentModelLoaded: 'Modell geladen – Sie können loslegen.',
57+
agentCheckingCache: 'Modell-Cache wird geprüft …',
58+
agentModelCached:
59+
'Dieses Modell liegt bereits im Cache – ein Klick auf „Modell laden“ startet sofort (ein Neuladen lädt es nicht erneut herunter).',
60+
agentModelFirstDownload:
61+
'Beim ersten Mal wird das Modell heruntergeladen ({size}); danach liegt es im Cache, ein Neuladen lädt es nicht erneut herunter.',
62+
agentNoWebGPU: 'Dieser Browser unterstützt kein WebGPU; der lokale Modus steht nicht zur Verfügung.',
63+
agentLocalChatOnly:
64+
'Das lokale Modell antwortet und formuliert um – es bearbeitet das Dokument nicht selbst. Für KI-gestütztes Bearbeiten ',
65+
agentSwitchCloud: 'zur Cloud wechseln →',
66+
agentReviewMode: 'Änderungen nachverfolgen',
67+
agentQuote: 'Auswahl zitieren',
68+
agentQuoteTip: 'Den im Dokument, in der Tabelle oder auf der Folie ausgewählten Text in die Eingabe übernehmen',
69+
agentClear: 'Verlauf löschen',
70+
agentInputPlaceholder: 'Die KI um eine Änderung am Dokument bitten … (Enter sendet, Umschalt+Enter für neue Zeile)',
71+
agentSend: 'Senden',
72+
agentStop: 'Anhalten',
73+
agentNeedKey: 'Bitte zuerst einen API-Schlüssel eingeben.',
74+
agentNoSelection: 'Keine Auswahl erkannt – bitte zuerst Text im Dokument markieren.',
75+
agentQuotePrefix: 'Bitte berücksichtige den von mir markierten Inhalt:',
76+
agentStopped: 'Angehalten.',
77+
agentMaxSteps: 'Maximale Anzahl an Schritten erreicht; angehalten.',
78+
agentToolCallPrefix: 'Werkzeugaufruf: ',
79+
agentToolErrorPrefix: 'Werkzeugfehler: ',
80+
autosaveStopped:
81+
'Automatisches Speichern angehalten: In diesem Browser ist kein Speicherplatz mehr frei. Exportieren Sie dieses Dokument und löschen Sie dann einige ältere aus Ihren gespeicherten Dokumenten.',
82+
historyTitle: 'Gespeicherte Dokumente',
83+
historyIntro:
84+
'Kopien der Dokumente, die Sie bearbeitet haben – in diesem Browser auf diesem Gerät, damit ein Neuladen, ein geschlossener Tab oder ein Absturz die Arbeit nicht kostet. Nichts davon wurde hochgeladen.',
85+
historyColDocument: 'Dokument',
86+
historyColEdited: 'Zuletzt bearbeitet',
87+
historyColSize: 'Größe',
88+
historyColExpires: 'Wird gelöscht',
89+
historyNotBackup:
90+
'Diese Kopien sind dazu da, angefangene Arbeit wieder aufzunehmen. Sie sind keine Sicherung – exportieren Sie alles, was Sie behalten möchten.',
91+
historySearchPlaceholder: 'Nach Dateiname suchen',
92+
historyEmpty: 'Noch nichts gespeichert. Dokumente, die Sie hier bearbeiten, erscheinen von selbst in dieser Liste.',
93+
historyEmptySearch: 'Kein Dateiname passt zu dieser Suche.',
94+
historyOpen: 'Öffnen',
95+
historyDelete: 'Löschen',
96+
historyDeleteConfirm:
97+
'„{title}“ und alle gespeicherten Kopien davon löschen? Das lässt sich nicht rückgängig machen.',
98+
historyClearAll: 'Alle löschen',
99+
historyClearConfirm:
100+
'Alle auf diesem Gerät gespeicherten Dokumentkopien löschen? Das lässt sich nicht rückgängig machen.',
101+
historyUnsaved: 'nicht exportiert',
102+
historyUsage: '{size} belegt',
103+
historyCount: '{count} Dokumente',
104+
historyPageInfo: 'Seite {page} von {pages}',
105+
historyPrev: 'Zurück',
106+
historyNext: 'Weiter',
107+
historyBack: 'Editor öffnen',
108+
historyAutosaveLabel: 'Automatisch speichern',
109+
historyAutosaveOff:
110+
'Automatisches Speichern ist aus: Was Sie jetzt bearbeiten, wird nicht gespeichert und geht beim Schließen der Seite verloren.',
111+
historyRetention:
112+
'Jedes Dokument wird 7 Tage nach der letzten Bearbeitung oder Öffnung automatisch gelöscht. Sie können hier auch selbst löschen – das wirkt sofort.',
113+
historyExpiresIn: 'noch {days} Tage',
114+
historyExpiresInOne: 'noch 1 Tag',
115+
historyExpiresToday: 'heute',
116+
historyDownload: 'Herunterladen',
117+
historyDownloadFailed: 'Diese Kopie konnte nicht auf der Festplatte gespeichert werden.',
118+
historyOnlyUnsaved: 'Nicht exportiert',
119+
historyOpenFile: 'Datei öffnen',
120+
historyRailSettings: 'Einstellungen',
121+
historyRailRetention: 'Aufbewahrung',
122+
historyCancel: 'Abbrechen',
123+
historyChip: 'Auf diesem Gerät · nie hochgeladen',
124+
historyEmptyTitle: 'Noch nichts gespeichert',
125+
historyEmptySearchTitle: 'Keine Treffer',
126+
historyClearSearch: 'Suche zurücksetzen',
127+
historyDeleteTitle: 'Dieses Dokument löschen',
128+
historyClearTitle: 'Alles löschen',
129+
};
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import type { I18nMessages } from '../types';
2+
3+
/**
4+
* The `en` message table. *
5+
* Interpolation placeholders ({size}, {title}, {when}, {days}, {count},
6+
* {page}, {pages}) have to survive translation verbatim -- the
7+
* message-placeholders cases in test/unit/i18n.test.ts check that.
8+
*/
9+
export const en: I18nMessages = {
10+
webOffice: 'Web Office',
11+
uploadDocument: 'View/Edit Document',
12+
newWord: 'New Word',
13+
newExcel: 'New Excel',
14+
newPowerPoint: 'New PowerPoint',
15+
themeLabel: 'Theme',
16+
themeSystem: 'System',
17+
themeLight: 'Light',
18+
themeDark: 'Dark',
19+
fileSavedSuccess: 'File saved successfully: ',
20+
documentLoaded: 'Document loaded: ',
21+
failedToLoadEditor: 'Failed to load editor component. Please ensure OnlyOffice API is properly installed.',
22+
unsupportedFileType: 'Unsupported file type: ',
23+
invalidFileObject: 'Invalid file object',
24+
documentOperationFailed: 'Document operation failed: ',
25+
openUrlFailed: 'Could not open that link: ',
26+
openUrlUnreachable:
27+
'That file could not be fetched. The site may not allow other pages to read its files, or the network is down. Download it and open the file from your device instead.',
28+
editorErrorToast: 'Document error',
29+
editorErrorFormatMismatch: 'The file content does not match its extension; check the file format and try again',
30+
editorErrorOpenFailed:
31+
'The file could not be opened: it may be corrupted, in an unsupported format, or not what its extension says',
32+
editorErrorOutOfMemory:
33+
'This browser could not allocate memory for the document conversion engine (about {mb} MB). Close other tabs or windows and try again; if it keeps failing, use a 64-bit browser (Edge, or 64-bit Chrome).',
34+
editorOpenRetrying: 'The editor was not ready when the document opened; retrying automatically...',
35+
agentTitle: 'AI Assistant',
36+
agentOpenTip: 'Open AI Assistant',
37+
agentSettings: 'Settings',
38+
agentRoleUser: 'You',
39+
agentRoleTool: 'Tool',
40+
agentRoleError: 'Error',
41+
agentProviderClaude: 'Claude (cloud, needs API Key)',
42+
agentProviderOpenAI: 'OpenAI (cloud, needs API Key)',
43+
agentProviderGemini: 'Gemini (cloud, needs API Key)',
44+
agentProviderLocal: 'Local offline (WebLLM, needs WebGPU)',
45+
agentProviderOllama: 'Ollama (local server, run it yourself)',
46+
agentOllamaModelPlaceholder: 'Model name, e.g. llama3.2',
47+
agentOllamaHint: 'Connects to local Ollama (http://localhost:11434); no API Key — make sure the model is running.',
48+
agentLoadModel: 'Load model',
49+
agentModelLoaded: 'Model loaded — you can start chatting.',
50+
agentCheckingCache: 'Checking model cache…',
51+
agentModelCached: 'This model is cached — click "Load model" for an instant start (a refresh won\'t re-download).',
52+
agentModelFirstDownload: "First use downloads the model ({size}); it is then cached, so a refresh won't re-download.",
53+
agentNoWebGPU: 'This browser does not support WebGPU; local mode is unavailable.',
54+
agentLocalChatOnly:
55+
'The local model only answers and rewrites — it will not edit the document directly. For AI-driven editing, ',
56+
agentSwitchCloud: 'switch to cloud →',
57+
agentReviewMode: 'Review mode',
58+
agentQuote: 'Quote selection',
59+
agentQuoteTip: 'Quote the text currently selected in the document/spreadsheet/slide into the input',
60+
agentClear: 'Clear chat',
61+
agentInputPlaceholder: 'Ask AI to edit the document… (Enter to send, Shift+Enter for newline)',
62+
agentSend: 'Send',
63+
agentStop: 'Stop',
64+
agentNeedKey: 'Please enter an API Key first.',
65+
agentNoSelection: 'No selection detected — please select text in the document first.',
66+
agentQuotePrefix: 'Please consider my selected content:',
67+
agentStopped: 'Stopped.',
68+
agentMaxSteps: 'Reached the maximum number of steps; stopped.',
69+
agentToolCallPrefix: 'Tool call: ',
70+
agentToolErrorPrefix: 'Tool error: ',
71+
72+
autosaveStopped:
73+
'Autosave stopped: this browser is out of storage room. Export this document, then delete a few old ones from your saved documents.',
74+
historyChip: 'On this device · never uploaded',
75+
historyTitle: 'Saved documents',
76+
historyIntro:
77+
'Copies of the documents you edited, kept in this browser on this device, so a refresh, a closed tab or a crash cannot cost you the work. None of this was uploaded.',
78+
historyColDocument: 'Document',
79+
historyColEdited: 'Last edited',
80+
historyColSize: 'Size',
81+
historyColExpires: 'Auto-deletes',
82+
historyNotBackup:
83+
'These copies exist so you can pick up work you were in the middle of. They are not a backup -- export anything you want to keep.',
84+
historySearchPlaceholder: 'Search by file name',
85+
historyEmptyTitle: 'Nothing saved yet',
86+
historyEmpty: 'Documents you edit here show up in this list on their own -- there is nothing to save by hand.',
87+
historyEmptySearchTitle: 'No matches',
88+
historyEmptySearch: 'No file name contains that text.',
89+
historyClearSearch: 'Clear search',
90+
historyOpen: 'Open',
91+
historyDelete: 'Delete',
92+
historyCancel: 'Cancel',
93+
historyDeleteTitle: 'Delete this document',
94+
historyClearTitle: 'Delete everything',
95+
historyDeleteConfirm: 'Delete "{title}" and every saved copy of it? This cannot be undone.',
96+
historyClearAll: 'Delete all',
97+
historyClearConfirm: 'Delete every document copy saved on this device? This cannot be undone.',
98+
historyUnsaved: 'not exported',
99+
historyUsage: '{size} used',
100+
historyCount: '{count} documents',
101+
historyPageInfo: 'Page {page} of {pages}',
102+
historyPrev: 'Previous',
103+
historyNext: 'Next',
104+
historyBack: 'Open the editor',
105+
historyAutosaveLabel: 'Autosave',
106+
historyAutosaveOff: 'Autosave is off: what you edit now is not being saved, and closing the page will lose it.',
107+
historyRetention:
108+
'Each document is deleted automatically 7 days after you last edited or opened it. You can also delete anything here yourself, and that takes effect immediately.',
109+
historyExpiresIn: '{days} days left',
110+
historyExpiresInOne: '1 day left',
111+
historyExpiresToday: 'today',
112+
historyDownload: 'Download',
113+
historyDownloadFailed: 'That copy could not be saved to disk.',
114+
historyOnlyUnsaved: 'Not exported',
115+
historyOpenFile: 'Open a file',
116+
historyRailSettings: 'Settings',
117+
historyRailRetention: 'Retention',
118+
};

0 commit comments

Comments
 (0)