feat(i18n): add Spanish (es) locale

Full European Spanish translation — frontend/src/i18n/locales/es.ts
  covers all 4899 keys with placeholders, plural forms, and inline
  markup preserved. Registered in i18n/index.ts (resources,
  supportedLngs, availableLanguages) and selectable as "Español".

  check-i18n-parity.mjs auto-discovers the new file; added an
  ES_COGNATES allow-list for genuine Spanish cognates and brand/format
  tokens so Check 4 does not flag them as untranslated leaks. Brings the
  supported-language count to 9.
This commit is contained in:
maziggy
2026-05-21 13:13:55 +02:00
parent 76e327f4a1
commit a51d59eabf
6 changed files with 6419 additions and 445 deletions
+1
View File
@@ -5,6 +5,7 @@ All notable changes to Bambuddy will be documented in this file.
## [0.2.5b1] - Unreleased
### Added
- **Spanish (es) translation (#1243, requested by @MiguelAngelLV)** — Bambuddy now ships a full European Spanish locale. New `frontend/src/i18n/locales/es.ts` translates all 4899 keys with placeholders, plural forms, and inline markup preserved; registered in `frontend/src/i18n/index.ts` and selectable as "Español" in the language picker. The parity checker auto-discovers the file — `frontend/scripts/check-i18n-parity.mjs` gained an `ES_COGNATES` allow-list for genuine Spanish cognates and brand/format tokens. Brings the supported-language count to 9 (en / de / es / fr / it / ja / pt-BR / zh-CN / zh-TW). Parity green, frontend build clean.
- **Currency: Belize Dollars (BZD) added to the Settings → Cost currency dropdown (#1454, requested by @PLGuerraDesigns)** — Reporter accurately tracks 3D-printing filament costs in his local currency and BZD wasn't selectable, forcing a manual 2:1 mental conversion from USD. Added `BZD: 'BZ$'` to `frontend/src/utils/currency.ts` next to MXN (Americas dollar-prefix grouping); `getCurrencySymbol('BZD')` returns `'BZ$'` and the SUPPORTED_CURRENCIES list now has 30 entries. Unit test added in `frontend/src/__tests__/utils/currency.test.ts` covering the symbol lookup and presence in SUPPORTED_CURRENCIES; entry-count assertion bumped to 30 so any future additions/removals are caught immediately. 14 currency tests green; frontend build clean.
- **Connection Diagnostic — self-service triage for "printer won't connect / won't print"** — A triage review of recently-closed issues found roughly a third were user-side setup errors (printer not in LAN developer mode, blocked ports, Docker bridge networking, wrong access code, printer on a different subnet), each costing a multi-round-trip "enable debug logging → build a support bundle → upload it" exchange. A new diagnostic (`backend/app/services/printer_diagnostic.py`) runs those checks automatically: TCP reachability of MQTT 8883 / FTPS 990 / RTSPS 322, LAN developer mode, Docker network mode, printer/host subnet match, and MQTT credential class — each returning a pass / fail / warn / skip status with a localized plain-language fix. Exposed via `GET /printers/{id}/diagnostic` (saved printer) and `POST /printers/diagnostic` (pre-save Add-Printer flow), and surfaced as a one-click "Run diagnostic" from the printer card actions menu (plus a quick button on the card when a printer is offline), the Add-Printer dialog, and a new Connection Diagnostic section on the System page. The in-app bug reporter scans configured printers when the report form opens and always shows the result — a healthy confirmation when nothing's wrong, or the detected problem and its fix inline — so setup mistakes get self-resolved instead of becoming GitHub issues. The GitHub `config.yml` troubleshooting link was repointed from the wiki source repo to the rendered troubleshooting page. Backend service unit tests (15) and frontend modal tests (3) added; all diagnostic strings translated across the 8 locales. Backend ruff clean, frontend build clean, i18n parity green.
+17
View File
@@ -285,11 +285,28 @@ const ZH_TW_COGNATES = [
'EC984C,#6CD4BC,A66EB9,D87694',
];
// Spanish cognates — words/phrases that are genuinely identical in Spanish.
const ES_COGNATES = [
'Error', 'Firmware', 'General', 'Control', 'Total', 'total', 'Material',
'Material:', 'Color', 'Hex', 'Local', 'Global', 'China', 'Editable',
'Normal', 'Metal', 'Multicolor', 'Proxy', 'Host', 'Factor', 'Original',
'Sport (124%)', 'Ludicrous (166%)', 'MakerWorld: {{designer}}',
'{{printer}}: {{error}}', 'Base: {{name}}',
'{{name}} — {{stage}} ({{percent}}%) — {{elapsed}}', 'total: {{minutes}} min',
'({{count}}/8)', 'Hex: #{{hex}}', '(25%, 50%, 75%)',
'EC984C,#6CD4BC,A66EB9,D87694', 'Est.',
'ntfy, Pushover, Discord, etc.',
'Box label (62 × 29 mm)',
'Avery L7160 — A4 sheet (38.1 × 63.5 mm × 21)',
'Avery 5160 — US Letter sheet (25.4 × 66.7 mm × 30)',
];
const IDENTICAL_TO_EN_ALLOWED = {
de: new Set(DE_COGNATES),
fr: new Set(FR_COGNATES),
it: new Set(IT_COGNATES),
ja: new Set(JA_COGNATES),
es: new Set(ES_COGNATES),
'pt-BR': new Set(PT_BR_COGNATES),
'zh-CN': new Set(ZH_CN_COGNATES),
'zh-TW': new Set(ZH_TW_COGNATES),
+4 -1
View File
@@ -5,6 +5,7 @@ import LanguageDetector from 'i18next-browser-languagedetector';
// Import translations directly for bundling
import en from './locales/en';
import de from './locales/de';
import es from './locales/es';
import fr from './locales/fr';
import ja from './locales/ja';
import it from './locales/it';
@@ -15,6 +16,7 @@ import zhTW from './locales/zh-TW';
const resources = {
en: { translation: en },
de: { translation: de },
es: { translation: es },
fr: { translation: fr },
ja: { translation: ja },
it: { translation: it },
@@ -29,7 +31,7 @@ i18n
.init({
resources,
fallbackLng: 'en',
supportedLngs: ['en', 'de', 'fr', 'ja', 'it', 'pt-BR', 'zh-CN', 'zh-TW'],
supportedLngs: ['en', 'de', 'es', 'fr', 'ja', 'it', 'pt-BR', 'zh-CN', 'zh-TW'],
detection: {
// Order of detection methods
@@ -55,6 +57,7 @@ export default i18n;
export const availableLanguages = [
{ code: 'en', name: 'English', nativeName: 'English' },
{ code: 'de', name: 'German', nativeName: 'Deutsch' },
{ code: 'es', name: 'Spanish', nativeName: 'Español' },
{ code: 'fr', name: 'French', nativeName: 'Français' },
{ code: 'ja', name: 'Japanese', nativeName: '日本語' },
{ code: 'it', name: 'Italian', nativeName: 'Italiano' },
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-lFgKj9FJ.js"></script>
<script type="module" crossorigin src="/assets/index-Cv5ljbAV.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-QwBJJHPy.css">
</head>
<body>