fix(i18n): give Dutch users the Dutch editor UI, and hold the languag… · marktext/marktext@2b4db47 · GitHub
Skip to content

Commit 2b4db47

Browse files
Jocsclaude
andauthored
fix(i18n): give Dutch users the Dutch editor UI, and hold the language registries together (#5502)
* refactor(desktop): move the muya locale map out of editor.vue The map from the desktop language preference to the engine's locale objects lived inside a 2000-line SFC, where no unit test can reach it. Lift it to `util/muyaLocale.ts` unchanged so the next commits can fix and then guard it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(i18n): give Dutch users the Dutch editor UI (#5499) `nl.ts` has been complete since #5137 and `locales/index.ts` exports it, but `nl` never reached the three lists the desktop reads: the `@muyajs/core` export hub, the desktop's type surface for it, and MUYA_LOCALES. Picking Nederlands therefore hit the `?? en` fallback, leaving the inline format toolbar, quick-insert menu, emoji picker, image selector and preview-block strings in English while the rest of the app was Dutch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(desktop): hold the language registries to each other (#5500) Adding a UI language means editing lists in five places that nothing checks against each other, and they have drifted twice under a green CI run: `nl` never reached the engine locale map (#5499) and `ru` never reached `electronLanguages` (#5496). Assert that SUPPORTED_LANGUAGES, the locale JSON files on disk, the Preferences dropdown, MUYA_LOCALES and electronLanguages all describe the same set, and name the file to edit in each failure message. The electronLanguages arm matches on the primary subtag, since Electron ships pt-BR/pt-PT rather than a bare `pt`, and spells macOS bundles with an underscore. The yml block is hand-parsed: no YAML parser is a declared dependency here, and reaching for the one electron-builder hoists into node_modules would be the phantom dependency that broke #5159. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent c754f2c commit 2b4db47

5 files changed

Lines changed: 128 additions & 31 deletions

File tree

‎packages/desktop/src/renderer/src/components/editorWithTabs/editor.vue‎

Lines changed: 2 additions & 30 deletions
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { en, de, es, fr, ja, ko, nl, pt, ru, tr, zhCN, zhTW, type ILocale } from '@muyajs/core'
2+
3+
// Maps the desktop language preference onto the engine's bundled locale
4+
// objects. Keys must cover every tag in SUPPORTED_LANGUAGES (common/i18n.ts):
5+
// a tag with no entry here falls back to English, so the application chrome
6+
// would be translated while the editor's own UI is not (#5499).
7+
export const MUYA_LOCALES: Record<string, ILocale> = {
8+
en,
9+
de,
10+
es,
11+
fr,
12+
ja,
13+
ko,
14+
nl,
15+
pt,
16+
ru,
17+
tr,
18+
'zh-CN': zhCN,
19+
'zh-TW': zhTW
20+
}
21+
22+
export const getMuyaLocale = (language: string): ILocale => MUYA_LOCALES[language] ?? en

‎packages/desktop/src/types/muya-core.d.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ declare module '@muyajs/core' {
3030
export const fr: ILocale
3131
export const ja: ILocale
3232
export const ko: ILocale
33+
export const nl: ILocale
3334
export const pt: ILocale
3435
export const ru: ILocale
3536
export const tr: ILocale
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { describe, expect, it } from 'vitest'
2+
import fs from 'fs'
3+
import path from 'path'
4+
import { getSupportedLanguages } from '../../../src/common/i18n'
5+
import { getLanguageOptions } from '../../../src/renderer/src/prefComponents/general/config'
6+
import { MUYA_LOCALES } from '../../../src/renderer/src/util/muyaLocale'
7+
8+
/**
9+
* Adding a UI language means editing several lists that nothing else ties
10+
* together, and they have drifted twice: `nl` reached the desktop but never the
11+
* engine locale map (#5499, English editor UI for Dutch users), and `ru` reached
12+
* both but not `electronLanguages` (#5496), which strips the Chromium/AppKit
13+
* Russian resources out of packaged builds — the regression #5132 fixed.
14+
*
15+
* Each `it` below names the file to edit when it fails.
16+
*/
17+
18+
const DESKTOP_ROOT = path.join(__dirname, '../../..')
19+
const LOCALES_DIR = path.join(DESKTOP_ROOT, 'static/locales')
20+
const BUILDER_CONFIG = path.join(DESKTOP_ROOT, 'electron-builder.yml')
21+
22+
const localeFileTags = (): string[] =>
23+
fs
24+
.readdirSync(LOCALES_DIR)
25+
.filter(f => f.endsWith('.json') && !f.endsWith('.min.json'))
26+
.map(f => f.replace('.json', ''))
27+
28+
/**
29+
* Reads the `electronLanguages:` block out of electron-builder.yml.
30+
*
31+
* Hand-parsed rather than via js-yaml: no YAML parser is a declared dependency
32+
* of this package, and picking up the copy electron-builder hoists into
33+
* node_modules would be a phantom dependency (the failure mode of #5159).
34+
* The block is a flat list of scalars, so recognising ` - value` lines and
35+
* skipping comments is enough.
36+
*/
37+
const readElectronLanguages = (): string[] => {
38+
const lines = fs.readFileSync(BUILDER_CONFIG, 'utf8').split('\n')
39+
const start = lines.findIndex(line => line.startsWith('electronLanguages:'))
40+
if (start === -1) return []
41+
42+
const values: string[] = []
43+
for (const line of lines.slice(start + 1)) {
44+
if (/^\s*#/.test(line) || line.trim() === '') continue
45+
const entry = /^\s+-\s+(\S+)\s*$/.exec(line)
46+
if (!entry) break
47+
values.push(entry[1]!)
48+
}
49+
return values
50+
}
51+
52+
// macOS .lproj bundles spell the region with an underscore, Windows/Linux .pak
53+
// files with a hyphen; electron-builder.yml carries both spellings.
54+
const primarySubtag = (tag: string): string => tag.replace('_', '-').split('-')[0]!.toLowerCase()
55+
56+
describe('language registries agree', () => {
57+
const supported = getSupportedLanguages()
58+
59+
it('has a non-empty set of supported languages to check', () => {
60+
expect(supported.length).toBeGreaterThan(0)
61+
})
62+
63+
it('ships a locale file for every supported language (static/locales/<tag>.json)', () => {
64+
expect([...localeFileTags()].sort()).toEqual([...supported].sort())
65+
})
66+
67+
it('offers every supported language in Preferences (prefComponents/general/config.ts)', () => {
68+
const offered = getLanguageOptions().map(option => option.value)
69+
expect([...offered].sort()).toEqual([...supported].sort())
70+
})
71+
72+
it('maps every supported language to an engine locale (util/muyaLocale.ts)', () => {
73+
expect(Object.keys(MUYA_LOCALES).sort()).toEqual([...supported].sort())
74+
})
75+
76+
describe('packaged Electron locale resources (electron-builder.yml)', () => {
77+
const electronLanguages = readElectronLanguages()
78+
79+
it('still finds the electronLanguages block', () => {
80+
expect(electronLanguages.length).toBeGreaterThan(0)
81+
})
82+
83+
it('keeps a resource for every supported language', () => {
84+
// `pt` has no bare entry — Electron ships pt-BR/pt-PT — so match on the
85+
// primary subtag rather than requiring the exact tag.
86+
const covered = new Set(electronLanguages.map(primarySubtag))
87+
const missing = supported.filter(tag => !covered.has(primarySubtag(tag)))
88+
expect(
89+
missing,
90+
'\n Add these to electronLanguages in electron-builder.yml, or the packaged' +
91+
'\n app drops their .pak/.lproj resources and OS-provided UI stays English:' +
92+
`\n ${missing.join(', ')}\n`
93+
).toEqual([])
94+
})
95+
96+
it('keeps no resource for a language that was dropped', () => {
97+
const supportedPrimaries = new Set(supported.map(primarySubtag))
98+
const stale = electronLanguages.filter(tag => !supportedPrimaries.has(primarySubtag(tag)))
99+
expect(stale, `\n electronLanguages entries with no matching UI language: ${stale.join(', ')}\n`).toEqual([])
100+
})
101+
})
102+
})

‎packages/muya/src/index.ts‎

Lines changed: 1 addition & 1 deletion

0 commit comments

Comments
 (0)