Add Dutch to the interface languages (issue #2891)

The translation was contributed as a file on the issue and needed three
    corrections before it could be wired up, all of which the parity gate
    found.

    The nine stats.timeframe.* entries had their keys translated along with
    their values -- 'today' had become 'vandaag'. Code resolves those by the
    English key, so the Statistics timeframe selector would have found
    nothing and rendered raw key names for every Dutch user. The values are
    kept and the keys restored.

    The file was translated against an older en.ts and was 84 leaves short:
    the Filament Track Switch feed prompts, the AI-detection status strings,
    the no-3MF internal-history banner, the batch-order stranded-plate
    notices, the Avery starting-position field, and the whole
    locationHaSensors section from #2824. Rather than splice those in, nl.ts
    is regenerated from the en.ts skeleton with the contributor's strings
    carried over by key, so its structure, key order and section comments
    match the reference exactly and a later diff against en.ts reads as
    content rather than as reordering. The generator fails rather than emit a
    key it has no translation for, so nothing fell back to English silently.

    229 leaves are identical to English. Each was checked and all are kept:
    Dutch takes most technical UI vocabulary verbatim -- printer, filament,
    status, nozzle, timelapse, dashboard -- and Dutch slicer users use the
    English feature names untranslated, so support, ironing, prime tower and
    gap fill stay as they are. The 123 distinct values are enumerated in a
    NL_COGNATES list in check-i18n-parity.mjs, the same shape the other
    twelve locales use, so the exemption is a listed decision per string
    rather than a blanket skip for the locale.

    Backend app/i18n still carries English and German only, so push
    notification text falls back to English for Dutch. That is true of the
    eleven other non-German locales too and is left alone here.

    Parity green at 6264 leaves across 14 locales.
This commit is contained in:
MartinNYHC
2026-08-28 12:26:45 +02:00
6 changed files with 7680 additions and 3142 deletions
-3138
View File
File diff suppressed because one or more lines are too long
+40
View File
@@ -415,6 +415,45 @@ const UK_COGNATES = [
'(25%, 50%, 75%)',
];
// Dutch cognates. Dutch shares most technical UI vocabulary with English
// verbatim -- printer, filament, status, nozzle, timelapse, dashboard --
// and the slicer feature names (support, ironing, prime tower, gap fill)
// are used untranslated by Dutch slicer users. Each entry below was
// checked individually against the Dutch translation in #2891.
const NL_COGNATES = [
'1 week', '(25%, 50%, 75%)', 'Accent', 'AMS Filament Backup',
'{{ams}} Slot {{slot}}', 'Auto', 'Auto Home', 'Bambu Cloud',
'Batch', 'Batches', 'Branch', 'Budget',
'Camera', 'China', 'Client Secret', 'Cloud',
'Commit', 'Compact', '({{count}}/8)', '{{count}} downloads',
'{{count}} filament', '({{count}} item)', '{{count}} item', '({{count}} items)',
'{{count}} items', '{{count}} object', 'Dashboard', 'Diagnose',
'Diameter', 'EC984C,#6CD4BC,A66EB9,D87694', 'Effect', 'email',
'ETA {{minutes}} min', 'Expert', 'Extruder', 'Extruders',
'Factor', 'Filament', 'Filament {{index}} ({{type}})', 'Filament {{n}}',
'{{filament}} @ {{temp}}°C', 'Filters', 'Firmware', 'Flow',
'Galaxy', 'Gap fill', 'Hex', 'Hex: #{{hex}}',
'Host', 'In browser', 'Interval', 'Interval (min)',
'Interval ({{type}})', 'Ironing', 'Jade White, Fire Red...', 'Label',
'Label...', 'LAN Developer Mode', 'Live', 'Long-Lived Access Token',
'Ludicrous (166%)', 'MakerWorld: {{designer}}', 'Metadata', 'Min',
'Model', '{{name}} — {{stage}} ({{percent}}%) — {{elapsed}}', '{{name}} - Timelapse', 'Nozzle',
'Nozzle 2', '{{n}} run', '{{n}} runs', 'Offline',
'Online', 'Open', 'Orca Cloud', 'parallel',
'Personal Access Token', 'Pipeline', 'Pipelines', 'Platform',
'Pressure Advance', 'Prime tower', 'Printer', 'Printer:',
'{{printer}}: {{error}}', '{{printer}} is offline', 'Printer is offline', 'Printer offline',
'Printers', 'Project', 'Project: {{name}}', 'Protocol',
'Providers', 'Provider: {{type}}', 'Proxy', 'Recent',
'Records', 'round robin', 'Scopes', 'Score',
'sets', 'Skirt / brim', 'Slicer', 'SpoolBuddy is offline',
'Sport (124%)', 'Start', 'Status', 'Subtype',
'Support', 'Tag', 'Tags', 'Temp',
'Timelapse', 'Topic', 'Trend', 'Type',
'Type:', 'Updates', 'Uptime', 'Urgent',
'Warm', '{{weight}}g', 'Workflow',
];
const IDENTICAL_TO_EN_ALLOWED = {
de: new Set(DE_COGNATES),
fr: new Set(FR_COGNATES),
@@ -428,6 +467,7 @@ const IDENTICAL_TO_EN_ALLOWED = {
tr: new Set(TR_COGNATES),
ru: new Set(RU_COGNATES),
uk: new Set(UK_COGNATES),
nl: new Set(NL_COGNATES),
};
// Pure comparison logic, exported so tests can verify each failure mode
+4 -1
View File
@@ -9,6 +9,7 @@ import es from './locales/es';
import fr from './locales/fr';
import ja from './locales/ja';
import it from './locales/it';
import nl from './locales/nl';
import ko from './locales/ko';
import ptBR from './locales/pt-BR';
import zhCN from './locales/zh-CN';
@@ -24,6 +25,7 @@ const resources = {
fr: { translation: fr },
ja: { translation: ja },
it: { translation: it },
nl: { translation: nl },
ko: { translation: ko },
'pt-BR': { translation: ptBR },
'zh-CN': { translation: zhCN },
@@ -33,7 +35,7 @@ const resources = {
uk: { translation: uk },
};
const SUPPORTED_LNGS = ['en', 'de', 'es', 'fr', 'ja', 'it', 'ko', 'pt-BR', 'ru', 'tr', 'uk', 'zh-CN', 'zh-TW'];
const SUPPORTED_LNGS = ['en', 'de', 'es', 'fr', 'ja', 'it', 'ko', 'nl', 'pt-BR', 'ru', 'tr', 'uk', 'zh-CN', 'zh-TW'];
const APPLIANCE_CONSUMED_KEY = 'bambuddy_appliance_locale_consumed';
i18n
@@ -107,6 +109,7 @@ export const availableLanguages = [
{ code: 'ja', name: 'Japanese', nativeName: '日本語' },
{ code: 'it', name: 'Italian', nativeName: 'Italiano' },
{ code: 'ko', name: 'Korean', nativeName: '한국어' },
{ code: 'nl', name: 'Dutch', nativeName: 'Nederlands' },
{ code: 'pt-BR', name: 'Portuguese (Brazil)', nativeName: 'Português (Brasil)' },
{ code: 'zh-CN', name: 'Chinese (Simplified)', nativeName: '简体中文' },
{ code: 'zh-TW', name: 'Chinese (Traditional)', nativeName: '繁體中文' },
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -26,7 +26,7 @@
<!-- Splash screens for iOS -->
<link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
<script type="module" crossorigin src="/assets/index-DL1B9Y3L.js"></script>
<script type="module" crossorigin src="/assets/index-DFcvtgAy.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BzJRM4M1.css">
</head>
<body>