Skip to content

feat: i18n coverage test suite (key coverage, source coverage, hardcoded strings) - #6

Closed
saiththerobo wants to merge 6 commits into
mainfrom
feat/i18n-coverage-test
Closed

feat: i18n coverage test suite (key coverage, source coverage, hardcoded strings)#6
saiththerobo wants to merge 6 commits into
mainfrom
feat/i18n-coverage-test

Conversation

@saiththerobo

@saiththerobo saiththerobo commented Jun 3, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds frontend/src/locales/i18n.test.js — a zero-dependency test (Node built-in node:test) with four suites that run with node --test src/locales/i18n.test.js
  • Fixes all violations the test catches: missing translations, untranslated strings, dead keys, hardcoded UI strings

Suites (in fix-priority order)

Suite 1 — source coverage: every locale key must appear at least once in the frontend source. Handles i18next plural suffixes (_one/_other) by searching for the base key. Run this first — dead keys should be deleted before adding missing translations.

Suite 2 — key coverage: every key in any locale file must exist in all locale files. Catches missing translations when a new feature adds keys to en.json without updating the others.

Suite 3 — value uniqueness: no two locales may share the same translated string unless explicitly whitelisted. Catches copy-pasted English values left untranslated. The SAME_VALUE_ALLOWED map documents every permitted duplicate with the exact language pairs and a comment explaining why (brand names, borrowed words, Romance-language convergences, etc.).

Suite 4 — hardcoded strings: scans .jsx files for user-visible string literals not wrapped in t(). Checks title, placeholder, aria-label, alt attributes and JSX text nodes. False-positive filters exclude JS operators, parenthesised expressions, and a small HARDCODED_OK set for intentional non-translations.

Changes alongside the test

  • 51 missing translation keys added across de/es/fr/it/ru/zhCN (inbox rules UI, default-reply prefs, security activity log, SSO password-login toggle)
  • 11 dead keys removed from all 7 locale files
  • 18 new locale keys added for previously hardcoded UI strings: compose toolbar tooltips, message list empty states, aria-labels, iframe title — all translated in all 7 locales
  • Source fixes in AdminPanel, ComposeModal, MessageList, MessagePane, NotificationToasts

Test result

758 tests, 0 failures

- Add frontend/src/locales/i18n.test.js: two-suite test (node --test)
  that checks (1) every key present in every locale file and (2) no two
  locales share the same value for a key unless explicitly whitelisted.
  Whitelist maps each key to the exact language pairs allowed to be
  identical (brand names, technical placeholders, legitimate shared words
  across Romance languages).
- de.json: add 47 missing keys — inbox rules UI and default-reply prefs.
- es/fr/it/ru/zhCN: add ~50 missing keys each — security activity log,
  SSO password-login toggle, and default-reply prefs. Also translate
  sidebar.folderMenu.renameFavorite, admin.sso.allowInsecure, and
  admin.sso.allowInsecureDesc which were left as English in all locales.
Explains the two failure types (missing keys, duplicate values), how to
fix each, and when to translate vs whitelist.
…ys to en/de

- Remove I18N_TEST_HELP.md, fold the guide into a JSDoc block at the top
  of i18n.test.js so it stays co-located with the test logic.
- Add admin.security.allow* and mailPolicy* keys to en.json and de.json
  (forward-ported from feat/allow-private-host; the other locales already
  had these from the earlier translation pass).
- Suite 3: every locale key must appear in frontend source at least once.
  Handles i18next plural suffixes (_one/_other/etc.) by searching for the
  base key. Dynamic keys referenced via variables (t(tab.labelKey)) are
  listed in DYNAMIC_KEYS.
- Remove 11 dead keys confirmed absent from all source files:
  admin.about.title, admin.accounts.presetMicrosoft,
  admin.rules.enabledToggle, common.close, common.copied,
  compose.minimize, messageList.markAllRead, notifications.undo,
  thread.collapse, thread.messages_one, thread.messages_other
- 739 tests, 0 failures
…trings

Suite 4 scans JSX source for user-visible string literals not wrapped in
t(). Two patterns are checked: (a) title/placeholder/aria-label/alt
attributes with plain string values, and (b) text nodes between JSX tags.
Filters exclude JS code patterns (parens, || / &&) and a small HARDCODED_OK
set covers intentional non-translations (brand name, email format markers).

Violations fixed — 18 new locale keys added across all 7 locales:
  compose.toolbar.*  (minimize, close, attachFile, textColor, insertLink,
                      insertImage, insertTable, removeColor, apply, removeTable)
  messageList.emptyInbox/emptyInboxDesc/noAccounts/noAccountsDesc/composeAriaLabel
  common.dismiss
  admin.security.qrCodeAlt
  message.emailFrameTitle

Source changes: AdminPanel (Loading…, QR code alt), ComposeModal (toolbar
tooltips and popup labels), MessageList (empty states, aria-labels, delete
title), MessagePane (iframe title), NotificationToasts (dismiss aria-label).

Also removes the now-redundant I18N_TEST_HELP.md (content is in the test).
758 tests, 0 failures.
Source coverage (dead keys) now runs before key coverage so a developer
cleans up dead keys before adding missing translations. Order: source
coverage → key coverage → value uniqueness → hardcoded strings.
@saiththerobo
saiththerobo deleted the feat/i18n-coverage-test branch June 3, 2026 16:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant