Skip to content

[Web][i18n] API route paths live in the locale files as translatable values — one translator edit silently breaks a feature in that locale #3426

Description

@ToddHebebrand

Description

The #2340 extraction codemod swept API route paths and one filesystem path into the locale files as if they were display copy. They now sit in all seven locales as translatable values:

Key Value
devices.jsondeviceGroupsPage.deviceGroups /device-groups
policies.jsonconfigurationPolicies.configPolicyCreatePage.configurationPolicies2 /configuration-policies
policies.jsonconfigurationPolicies.configPolicyDetailPage.configurationPolicies2 /configuration-policies
policies.jsonconfigurationPolicies.featureTabs.automationTab.scriptsLimit200 /scripts?limit=200
policies.jsonconfigurationPolicies.featureTabs.automationTab.alertsChannelsLimit200 /alerts/channels?limit=200
policies.jsonconfigurationPolicies.featureTabs.automationTab.softwareCatalogLimit100 /software/catalog?limit=100
policies.jsonsoftware.builtinPackageDetail.integrations /integrations
policies.jsonconfigurationPolicies.featureTabs.complianceTab.etcSshSshdConfig /etc/ssh/sshd_config

Enumerate them with:

python3 - <<'PY'
import json,glob,os
for f in sorted(glob.glob('apps/web/src/locales/en/*.json')):
    d=json.load(open(f))
    def walk(o,p=''):
        if isinstance(o,dict):
            for k,v in o.items(): walk(v,f"{p}.{k}" if p else k)
        elif isinstance(o,str) and o.startswith('/') and len(o)>4 and ' ' not in o:
            print(os.path.basename(f), p, repr(o))
    walk(d)
PY

Why this is a bug and not just untidy

Nothing is broken today, because every locale happens to carry the verbatim English value. It works by luck. The failure mode is that a translator — human or machine — localizes one of these entries, and the feature breaks only in that locale, at runtime, with no test or type error to catch it. /etc/ssh/sshd_config is especially inviting to "translate".

deviceGroupsPage.deviceGroups was the live one: DeviceGroupsPage.tsx built its group-create POST URL from t("deviceGroupsPage.deviceGroups"). That call site is already being fixed in #3423, which inlines the literal and removes that key from all seven locales.

Remaining work

The other seven entries have no call sites — the codemod left them behind as dead keys after the URLs were inlined back. So this is cleanup plus one judgement call:

  1. Delete the six dead route/query-string keys from all seven locales (must be all seven, or a locale parity gate reds main — see fix(api): scope snmpWorker DB contexts to DB statements only (#3215) #3220-class breakage).
  2. complianceTab.etcSshSshdConfig does have a live call site (ComplianceTab.tsx:948) where it renders as display text — an example path in help copy. It should be a literal in the component, not a translatable string.
  3. Consider a guard: a small test asserting no en locale value matches ^/[a-z0-9-]+ (excluding legitimately-translatable unit suffixes like perMonth: "/mo", perYear: "/yr"). That makes this class mechanical instead of a future grep. This is the same shape of defect as i18n: #2340 extraction replaced English copy with humanized key names across all five locales #2649 and would have caught both.

Affected files

Related: #2649 (same codemod, different symptom — humanized key names replacing English copy), #3423 (fixes the one live route key), #3424 (same codemod, different symptom — a sentence split into four unspaced JSX expressions).

Reported By: internal Playwright UI QA sweep 2026-08-11, evidence in docs/testing/FEATURE_TEST_LOG.md.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingtech-debtRefactor / cleanup / internal quality

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions