# General Translation Knowledge Base: Comment trouver les chaînes codées en dur qui ne sont pas traduites ?
URL: https://generaltranslation.com/fr/kb/find-hardcoded-strings.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: Exécutez dans votre CI un linter qui signale les littéraux de chaîne de caractères visibles par l’utilisateur, comme la règle no-literal-string d’eslint-plugin-i18next pour JSX, la vérification HardcodedText d’Android Lint ou le linter HardCodedString d’erb_lint pour Rails. Parcourez ensuite l’application avec un pseudo-paramètre régional pour repérer le texte que les vérifications statiques ne peuvent pas détecter.
Category: Internationalisation
Last updated: 2026-10-20
Authors: Kevin Wu, Jackie Chen
Published: 2026-10-20

Une chaîne codée en dur est un texte visible par l’utilisateur, écrit directement dans le code au lieu de passer par votre bibliothèque d’i18n : elle s’affiche donc dans la langue source, quel que soit le paramètre régional. En ajouter une ne casse rien : l’application paraît terminée en anglais, les tests passent, et le problème ne se révèle que lorsque quelqu’un ouvre la version française.

## Pourquoi des chaînes codées en dur passent-elles entre les mailles du filet ?

* **La langue source masque le problème.** Une traduction manquante ressemble en tout point à une traduction correcte, jusqu’à ce que vous changiez de paramètre régional.
* **Le texte se trouve en dehors des éléments enfants JSX.** Les attributs `placeholder`, `title`, `alt` et `aria-label`, les notifications (toasts) et messages de validation générés en JavaScript, ainsi que les étiquettes stockées dans des objets de configuration passent facilement inaperçus lors de la revue de code.
* **Chaque fonctionnalité ajoute du texte.** Pour écrire un bouton, le plus rapide est d’utiliser un littéral, et personne ne revient l’encapsuler ensuite, sauf si une vérification échoue.

La solution consiste donc à mettre en place une vérification qui échoue : un linter dans la CI pour le code, complété par une passe visuelle pour tout ce qui échappe au linter.

## Comment trouver les chaînes codées en dur dans chaque stack ?

| Stack                                      | Outil                                                                                                                                                                                                                           | Ce qu&#39;il détecte                                                                                                                | Ce qui lui échappe                                                                                 |
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| React et JSX                               | [`eslint-plugin-i18next`](https://github.com/edvardchen/eslint-plugin-i18next) `no-literal-string`                                                                                                                              | Le texte JSX par défaut ; les attributs et les éléments enfants `{'...'}` en mode `jsx-only` ; tous les littéraux en mode `all`     | Le texte qui parvient à l&#39;IU via des constantes, à moins d&#39;accepter le bruit du mode `all` |
| React et JSX                               | [`eslint-plugin-react`](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-no-literals.md) `jsx-no-literals`                                                                                          | Le texte JSX non encapsulé par défaut ; les chaînes encapsulées et les props avec `noStrings`                                       | Les chaînes en dehors du JSX                                                                       |
| Android                                    | Android Lint [`HardcodedText`](https://googlesamples.github.io/android-custom-lint-rules/checks/HardcodedText.md.html) et [`SetTextI18n`](https://googlesamples.github.io/android-custom-lint-rules/checks/SetTextI18n.md.html) | Le texte littéral dans les fichiers XML de layout ; `TextView.setText` avec des littéraux, une concaténation ou `Number.toString()` | Les appels Jetpack Compose `Text("...")`                                                           |
| iOS et macOS                               | L&#39;extraction du String Catalog de Xcode et l&#39;option de schéma **Show non-localized strings**                                                                                                                            | Les chaînes absentes du catalogue après une compilation ; le texte non localisé affiché en majuscules à l&#39;exécution             | Les écrans que personne n&#39;ouvre                                                                |
| Rails                                      | [erb&#95;lint](https://github.com/Shopify/erb-lint) `HardCodedString`                                                                                                                                                           | Le texte visible dans le balisage ERB                                                                                               | Les chaînes à l&#39;intérieur des balises JavaScript                                               |
| Toute base de code                         | ripgrep                                                                                                                                                                                                                         | Le texte entre balises sur une seule ligne                                                                                          | Le texte sur plusieurs lignes ; aucune distinction entre texte encapsulé et non encapsulé          |
| Toute application en cours d&#39;exécution | [Pseudo-localisation](/kb/pseudo-localization)                                                                                                                                                                                  | Tout texte affiché qui a échappé à la couche i18n                                                                                   | Les écrans que personne n&#39;ouvre                                                                |

Utilisez un linter en CI si vous voulez que toute nouvelle chaîne codée en dur fasse échouer la compilation. Utilisez ripgrep pour auditer ponctuellement une base de code que vous vous apprêtez à internationaliser. Utilisez la [pseudo-localisation](/kb/pseudo-localization) avant un lancement : le texte qui a échappé à la couche i18n reste inchangé alors que tout le reste est accentué, y compris le texte provenant d&#39;API, de constantes et de réponses serveur que l&#39;analyse statique ne peut pas suivre.

## Analyser le JSX avec ESLint

`eslint-plugin-i18next` comporte une seule règle, `no-literal-string`. Son mode par défaut, `jsx-text-only`, signale uniquement le texte situé entre les balises JSX. Définissez `mode: 'jsx-only'` pour signaler également les attributs et les expressions de type chaîne de caractères dans le JSX. Cette configuration au format « flat config » fonctionne avec ESLint 9 et 10, et prend en charge les fichiers TypeScript :

```bash
npm install --save-dev eslint eslint-plugin-i18next typescript typescript-eslint
```

```js title="eslint.config.mjs"
import i18next from 'eslint-plugin-i18next';
import tseslint from 'typescript-eslint';

export default [
  {
    files: ['src/**/*.{js,jsx,ts,tsx}'],
    languageOptions: {
      parser: tseslint.parser,
      parserOptions: { ecmaFeatures: { jsx: true } },
    },
    plugins: { i18next },
    rules: {
      // Signale le texte JSX, les attributs de type chaîne de caractères et les enfants {'string'}
      'i18next/no-literal-string': ['error', { mode: 'jsx-only' }],
    },
  },
];
```

Voici le composant que nous avons analysé avec le linter :

```jsx title="src/Checkout.jsx"
export function Checkout({ total, user }) {
  const error = 'Payment failed';
  return (
    <section className="checkout">
      <h2>Order summary</h2>
      <p>Hello, {user.name}</p>
      <input placeholder="Promo code" type="text" />
      <button title="Pay now" onClick={() => alert(error)}>Pay</button>
      <p>{'Free shipping on orders over $50'}</p>
      <p>{`Total: ${total}`}</p>
      <span>+ 2</span>
    </section>
  );
}
```

```text title="npx eslint src (eslint-plugin-i18next 6.1.5, ESLint 10.13.0)"
src/Checkout.jsx
   5:11  error  disallow literal string: <h2>Order summary</h2>                                             i18next/no-literal-string
   6:10  error  disallow literal string: <p>Hello, {user.name}</p>                                          i18next/no-literal-string
   7:26  error  disallow literal string: placeholder="Promo code"                                           i18next/no-literal-string
   8:21  error  disallow literal string: title="Pay now"                                                    i18next/no-literal-string
   8:60  error  disallow literal string: <button title="Pay now" onClick={() => alert(error)}>Pay</button>  i18next/no-literal-string
   9:11  error  disallow literal string: {'Free shipping on orders over $50'}                               i18next/no-literal-string
  11:13  error  disallow literal string: <span>+ 2</span>                                                   i18next/no-literal-string

✖ 7 problems (7 errors, 0 warnings)
```

Ce que montre l&#39;exécution :

* **Le mode par défaut signale 4 problèmes.** Avec la configuration `flat/recommended` du plugin, les attributs et l&#39;enfant `{'...'}` passent ; `jsx-only` ajoute ces 3 signalements.
* **Certains littéraux passent encore.** `className` et `type` sont exclus par défaut, ce qui est le comportement souhaité. Le littéral de gabarit de la ligne 10 passe, sauf si vous ajoutez `'should-validate-template': true`, et la constante `'Payment failed'` passe, sauf si vous utilisez `mode: 'all'`, qui signale aussi toutes les chaînes hors IU du fichier.
* **Attendez-vous à des faux positifs.** `+ 2` est signalé, car les motifs ignorés par défaut ne couvrent que les chaînes composées uniquement de symboles et sans espaces. Ajoutez un motif sous `words.exclude`, en y recopiant les valeurs par défaut du fichier `lib/options/defaults.js` du plugin : toute option que vous passez remplace les valeurs par défaut correspondantes.

Si vous utilisez déjà `eslint-plugin-react`, sa règle `jsx-no-literals` constitue une alternative. Sans options, elle exige seulement que le texte JSX se trouve dans un conteneur d&#39;expression : `<h2>Order summary</h2>` échoue donc, mais `<h2>{'Order summary'}</h2>` passe. Définissez `{ noStrings: true, ignoreProps: true }` pour signaler également les chaînes encapsulées et les littéraux de gabarit dans les éléments enfants ; sans `ignoreProps`, elle signale en outre chaque prop de type chaîne, y compris `className="checkout"`.

## Autres stacks

**Android.** Exécutez `./gradlew lint` ou, dans Android Studio, choisissez **Code &gt; Inspect Code**, puis consultez la catégorie Internationalization. Par défaut, ces deux vérifications produisent des avertissements ; [`lint.xml`](https://developer.android.com/studio/write/lint) permet de les transformer en erreurs avec `<issue id="HardcodedText" severity="error" />`. Aucune de ces vérifications ne signale les appels `Text("...")` de Compose : recherchez donc dans les fichiers Compose les littéraux de chaîne passés à `Text(`.

**iOS et macOS.** Xcode procède dans l’autre sens. Lors de la compilation, Xcode ajoute à votre String Catalog les chaînes passées aux [API localisables](https://developer.apple.com/documentation/xcode/preparing-your-apps-text-for-translation). Un littéral de chaîne dans `Text("Title")`, `Button("Checkout")` ou `String(localized:)` de SwiftUI est extrait. En revanche, une variable `String` passée à `Text` est [affichée sans localisation](https://developer.apple.com/documentation/swiftui/text), tout comme `Text(verbatim:)`. Après une compilation, vérifiez donc que le catalogue contient bien les écrans attendus : une entrée manquante signifie que le texte n’est jamais passé par une API localisable, souvent parce qu’il était stocké comme simple `String` au lieu de `LocalizedStringResource`. Pour repérer ce même texte à l’écran, activez **Show non-localized strings** dans **Product &gt; Scheme &gt; Edit Scheme &gt; Run &gt; Options** : le texte non localisé s’affiche alors en majuscules à l’exécution de l’application ([Apple : Preparing your interface for localization](https://developer.apple.com/documentation/xcode/preparing-your-interface-for-localization)).

**Rails.** Le linter `HardCodedString` d’erb&#95;lint est désactivé par défaut ; activez-le sous `linters:` dans `.erb_lint.yml`. i18n-tasks comble une autre lacune : sa commande `missing` signale les clés appelées par votre code mais absentes d’un fichier de paramètres régionaux, sans pour autant signaler le texte littéral dans les vues. (Consultez [General Translation vs. i18n-tasks](/kb/general-translation-vs-i18n-tasks) pour savoir comment il s’intègre dans un pipeline de traduction.) Avec `config.i18n.raise_on_missing_translations = true` dans l’environnement de test, les clés manquantes [lèvent des erreurs](https://guides.rubyonrails.org/i18n.html) au lieu d’afficher un message « Translation missing ».

**N’importe quelle base de code, avec ripgrep.** Pour un audit rapide avant d’adopter un linter, recherchez le texte situé entre les balises ainsi que les attributs contenant du texte :

```bash
# Texte commençant par une lettre entre > et < sur une même ligne
rg -n --glob '*.{jsx,tsx}' '>\s*[A-Za-z][^<>]*<'

# Attributs contenant du texte avec des valeurs littérales
rg -n --glob '*.{jsx,tsx}' '\b(placeholder|title|alt|aria-label)="[^"]*[A-Za-z]'
```

Dans le composant d’exemple, la première recherche a trouvé le titre, le message d’accueil et le bouton, et la seconde, les deux attributs. Cette approche ne détecte pas le texte placé seul sur sa ligne et renvoie aussi du texte déjà encapsulé pour la traduction : considérez donc les résultats comme une liste à passer en revue, et non comme un critère bloquant.

## Demander à un agent de codage de trouver et d&#39;encapsuler les chaînes

Cursor, Claude Code et les agents du même type excellent dans la partie mécanique : lire un composant, identifier ce qui s&#39;affiche à l&#39;utilisateur et le réécrire avec votre API i18n. Transmettez à l&#39;agent les décisions qu&#39;il ne peut pas deviner seul, et faites du linter votre critère d&#39;achèvement :

```text
Trouve les chaînes codées en dur visibles par l’utilisateur dans src/features/checkout et encapsule-les.

- Par « visible par l’utilisateur », on entend : texte JSX, valeurs placeholder/title/alt/aria-label,
  messages toast et messages de validation, et libellés de constantes affichés dans l’IU.
- Non visible par l’utilisateur : noms de classes, identifiants de test, URL, messages de log, noms
  d’événements analytiques et le nom de produit « Acme ».
- Encapsule le texte JSX avec le composant i18n du projet, et les props de type chaîne de caractères et
  les callbacks avec la fonction renvoyée par son Hook i18n. N’appelle les Hooks qu’au
  niveau supérieur d’un composant React ou d’un Hook personnalisé, jamais depuis un utilitaire ou un
  gestionnaire d’événements.
- Pour les messages au niveau du module ou les utilitaires non React, suis l’API
  documentée pour ce framework. Utilise src/features/cart/CartSummary.tsx comme référence.
- Garde les noms, prix et quantités sous forme de variables, hors du texte.
- Ne désactive pas les règles de lint et n’ajoute pas de commentaires eslint-disable.
- Une fois terminé, exécute `npx eslint src/features/checkout` et corrige toutes les
  erreurs i18next/no-literal-string, puis liste les chaînes que tu n’as pas
  encapsulées, en expliquant pourquoi.
```

Vérifiez ensuite le résultat vous-même : relancez ESLint, y compris vos règles React Hooks, parcourez le diff pour repérer les chaînes encapsulées à tort (identifiants, valeurs d&#39;énumération envoyées à une API), puis ouvrez les écrans modifiés avec un pseudo-paramètre régional ou dans une deuxième langue. Une exécution de lint sans erreur ne couvre que les motifs détectés par les règles activées ; vérifiez séparément les constantes et les littéraux de gabarit. Les [Rules of Hooks](https://react.dev/reference/rules/rules-of-hooks) de React s&#39;appliquent aussi aux hooks d&#39;i18n.

## Analyser et encapsuler les chaînes React avec General Translation

Le plugin ESLint de General Translation, [`@generaltranslation/react-core-linter`](/docs/react/guides/linting-your-code), vérifie le code `gt-react`, `gt-next` et `gt-react-native` déjà encapsulé : [`static-jsx`](/docs/react/reference/lint-rules#static-jsx) exige que le contenu dynamique à l&#39;intérieur de [`<T>`](/docs/react/reference/components/t) soit placé dans un composant variable tel que [`<Var>`](/docs/react/reference/components/var), et [`static-string`](/docs/react/reference/lint-rules#static-string) impose des chaînes statiques dans les fonctions de traduction. Comme il ne détecte pas le texte non encapsulé, combinez-le avec `no-literal-string` en excluant [`<T>`](/docs/react/reference/components/t) de cette règle. Les dépendances pair (peer dependencies) du plugin acceptent ESLint 8 ou 9 : installez donc `eslint@9` avec `eslint-plugin-i18next`, `@generaltranslation/react-core-linter`, `typescript` et `typescript-eslint` en tant que dépendances de développement :

```js title="eslint.config.mjs"
import i18next from 'eslint-plugin-i18next';
import gtLint from '@generaltranslation/react-core-linter';
import tseslint from 'typescript-eslint';

export default [
  {
    files: ['src/**/*.{js,jsx,ts,tsx}'],
    languageOptions: {
      parser: tseslint.parser,
      parserOptions: { ecmaFeatures: { jsx: true } },
    },
    plugins: { i18next },
    rules: {
      // Texte pas encore placé dans <T>
      'i18next/no-literal-string': [
        'error',
        { mode: 'jsx-only', 'jsx-components': { exclude: ['T'] } },
      ],
    },
  },
  // Contenu déjà placé dans <T>
  gtLint.configs.recommended,
];
```

`npx eslint src --fix` encapsule une valeur dynamique telle que `{user.name}` placée dans [`<T>`](/docs/react/reference/components/t) dans un composant [`<Var>`](/docs/react/reference/components/var), et signale le texte non encapsulé pour que vous l&#39;encapsuliez vous-même. L&#39;[injection automatique de JSX](/docs/cli/guides/using-auto-jsx), désactivée tant que vous n&#39;avez pas défini `enableAutoJsxInjection` sous `files.gt.parsingFlags` dans `gt.config.json`, encapsule le texte JSX traduisible dans [`<T>`](/docs/react/reference/components/t) lors de l&#39;exécution du compilateur GT. Suivez la procédure de configuration de la compilation indiquée en lien : Next.js utilise le compilateur Babel avec `next dev --webpack` et `next build --webpack` ; les applications React configurent le compilateur selon leur bundler. Les props de type chaîne de caractères doivent toujours passer par [`useGT`](/docs/react/reference/hooks/use-gt). Pour traiter un dépôt entier, Locadex, l&#39;agent IA de General Translation, exécute une automatisation **Generate code** qui encapsule le contenu d&#39;IU non traduit et ouvre une pull request à relire. (Voir [Configurer les automatisations Locadex](/docs/platform/locadex/guides/configuring-automations).)

## Points clés à retenir

* Sur cette fixture, `no-literal-string` a signalé 4 problèmes dans son mode par défaut et 7 en mode `jsx-only`. Le littéral de gabarit et la constante nécessitent encore des paramètres supplémentaires ou une vérification manuelle.
* La règle `HardcodedText` d&#39;Android Lint couvre le XML des layouts, et `SetTextI18n` couvre `TextView.setText` ; aucune des deux ne signale les appels `Text` de Compose.
* Xcode extrait les chaînes passées aux API localisables : une chaîne absente du String Catalog après une compilation trahit donc la présence de texte codé en dur.
* Donnez à un agent de codage un niveau d&#39;intervention, une définition de ce qui est visible par l&#39;utilisateur et la commande de lint qu&#39;il doit passer avec succès, puis vérifiez l&#39;application en cours d&#39;exécution.
* Dans le code `gt-react` et `gt-next`, `@generaltranslation/react-core-linter` vérifie le contenu placé dans [`<T>`](/docs/react/reference/components/t) : associez-le à `no-literal-string` pour repérer le texte qui n&#39;est pas encore encapsulé.

## Related questions

- [Qu’est-ce que la pseudo-localisation ?](https://generaltranslation.com/fr/kb/pseudo-localization.mdx): La pseudo-localisation est un test qui remplace votre texte source par une version modifiée, accentuée, allongée d’environ 40 % et encadrée de crochets, afin de faire apparaître les chaînes codées en dur, les troncatures, les polices défaillantes et les phrases concaténées avant même qu’une traduction n’existe. Android propose à cet effet les pseudo-paramètres régionaux en-XA et ar-XB.
- [General Translation ou i18n-tasks : lequel choisir ?](https://generaltranslation.com/fr/kb/general-translation-vs-i18n-tasks.mdx): i18n-tasks est une gem Ruby qui détecte les clés Rails I18n manquantes ou inutilisées, fait le ménage dans les fichiers de paramètres régionaux et peut compléter les clés manquantes via Google Translate, DeepL, OpenAI, Yandex ou watsonx. General Translation traduit des fichiers de paramètres régionaux entiers en s’appuyant sur votre glossaire, vos instructions personnalisées et la relecture dans le tableau de bord. Les deux outils remplissent des rôles différents, et de nombreuses équipes peuvent les utiliser ensemble.
- [Liste de contrôle des tests de localisation pour les applications multilingues](https://generaltranslation.com/fr/kb/multilingual-app-launch-checklist.mdx): Testez neuf points dans chaque paramètre régional pris en charge sur une compilation de Production : les parcours complets, y compris les états d’erreur et les e-mails, le changement de langue, les formes plurielles, la devise et les fuseaux horaires, le foisonnement du texte, la mise en page right-to-left, l’accessibilité, la distribution des traductions et des URL de langue explorables. Associez à chaque vérification un critère de réussite explicite.
- [Peut-on faire de l’i18n sans fichiers de traduction ?](https://generaltranslation.com/fr/kb/i18n-without-translation-files.mdx): Oui. Les bibliothèques gt-react et gt-next de General Translation permettent d’internationaliser React et Next.js sans clés de traduction ni fichier JSON par langue à maintenir. Le texte reste intégré au JSX, dans le composant `<T>`, et la commande gt translate génère les traductions de chaque langue lors de la compilation, en s’appuyant sur le contexte de votre code. Locadex peut les tenir à jour via des pull requests.
- [Comment traduire du texte conditionnel en JSX ?](https://generaltranslation.com/fr/kb/translate-conditional-jsx.mdx): Gardez la phrase entière et traduisez chacune de ses versions, car un ternaire la découpe en fragments qui perdent leur sens et leur place dans l’ordre des mots propre à chaque langue. General Translation y parvient avec `<Branch>` à l’intérieur de `<T>`, qui traduit la phrase d’un seul tenant.

## Previous and next questions

- Previous question: [Comment traduire du Markdown et du MDX sans casser la syntaxe ?](https://generaltranslation.com/fr/kb/translate-markdown-files.mdx)
- Next question: [Internationalisation et localisation (i18n et l10n)](https://generaltranslation.com/fr/kb/internationalization-vs-localization.mdx)

## Sitemap

See the [Knowledge Base sitemap](https://generaltranslation.com/fr/kb/sitemap.md) for every article.
