Catch Hardcoded React Text Before It Reaches Another Locale

M

Michal Žídek

Guest
A React screen can use a translation library and still contain English text in a button, a placeholder, or an accessibility label. These strings are easy to miss when a review focuses on behavior. A source check can make that class of mistake visible before the change reaches users.


I maintain localization-drift, an MIT-licensed TypeScript tool for this check. This tutorial was prepared with AI assistance. I ran the example against the published npm package, version 0.2.0, on October 15, 2026.

Start With a Small Screen​


Create src/Search.tsx with four hardcoded strings:

Code:
export function ContactSearch() {
  return (
    <section>
      <h1>Contacts</h1>
      <input placeholder="Search contacts" aria-label="Contact search" />
      <button>Search</button>
    </section>
  );
}


The heading and button are visible. The placeholder is visible when the input is empty. The accessibility label supplies a name for assistive technology. Each needs deliberate treatment when the screen supports another language.


This is a source-analysis fixture. You do not need to set up a running React application to scan it.

Run the Published Package​


Use Node.js 22 or later. In the directory that contains src, install a fixed version so that the example remains reproducible:

Code:
npm install --save-dev --save-exact [email protected]
npx localization-drift


The default scan root is src. For a project with different directories, pass the roots explicitly:

Code:
npx localization-drift --root app --root components


The example produces four findings: the heading on line 4, the placeholder and accessibility label on line 5, and the button on line 6. The process exits with code 1. A clean scan exits with 0; a usage or configuration error exits with 2.


These exit codes make the command usable as a build step. The scanner reports the source location, so the developer can fix the text in the same change that introduced it.

Move the Strings Into Your Translation Layer​


Replace the literals with calls to the application's translation function:

Code:
declare const t: (key: string) => string;

export function ContactSearch() {
  return (
    <section>
      <h1>{t("contacts.title")}</h1>
      <input
        placeholder={t("contacts.searchPlaceholder")}
        aria-label={t("contacts.searchLabel")}
      />
      <button>{t("contacts.search")}</button>
    </section>
  );
}


The declaration makes the fixture's assumed function clear. In a real application, use the function supplied by your translation library and add the keys to the relevant locale files.


Run the JSON report:

Code:
npx localization-drift --json


For this changed fixture, the output is [] and the exit code is 0. That result establishes that the scanner no longer sees those hardcoded literals. It does not establish that the keys exist or that the translations are correct.


Version 0.2.0 checks JSX text and configured attributes. A dynamic expression can pass without calling a translation function. Keep locale coverage checks, translation review, and a rendered UI check as separate steps.

Keep Reports Useful in CI​


For a simple pipeline, install dependencies from the lockfile and run the same command as the developer:

Code:
npm ci
npx localization-drift


The tool can also produce JSON or SARIF 2.1.0:

Code:
npx localization-drift --json > localization-findings.json
npx localization-drift --sarif > localization-findings.sarif


Choose one format per invocation. Findings still produce exit code 1. If a later pipeline step uploads the report, configure that step to run when the scan reports findings. Otherwise, the failure that protects the build can also prevent the report from being collected.


I checked the SARIF document from the original fixture: it contains four results with file and line information. A CI platform still needs a separate report upload step; writing the file does not publish annotations by itself.

Review Exceptions as Debt​


Some text, such as a registered brand name, may intentionally remain literal. Put a reason immediately before the finding:

Code:
{/* localization-drift: allow-next-line -- registered brand name */}
<strong>WhatsApp</strong>


Use narrow exceptions that a reviewer can assess in context. The configuration also supports message-based allowlists, but a broad pattern can suppress more findings than intended.


Be careful with --fix. In this package, it adds exception comments with a TODO: localize reason. It does not translate the text. I ran it on the original fixture: the scan became clean while the English heading remained in the source.


That behavior can help teams adopt a check while tracking existing debt. It is a poor automatic response to newly introduced text: a green result would then mean that the text was exempted, rather than moved into the translation layer.


Start with one source directory, review its findings, and add the scan to CI once the team understands the exceptions. A small, predictable check can keep new hardcoded text visible while translation quality remains part of the application's own review process.


Disclosure: I maintain localization-drift. This article and its source fixtures were prepared with AI assistance. The reported CLI results came from local runs of npm version 0.2.0; I did not test a deployed multilingual application.
 

Thread statistics

Created
Michal Žídek,
Replies
0
Views
1
Back
Top