Articles
Sep 3, 2024

Simplifiez la gestion des locales avec i18next-parser

Fini les clefs de traduction manquantes avec ce petit outil simple

Simplifiez la gestion des locales avec i18next-parser

Introduction

L'internationalisation (i18n) est le processus de conception d'applications logicielles de manière à ce qu'elles puissent être adaptées à différentes langues et régions sans nécessiter de modifications dans le code. i18next-parser est un outil qui vous aidera grandement dans ce processus. Il permet d'automatiser la gestion des fichiers de traduction, rendant plus robuste votre code et évitant l'affichage de clefs non-traduite en production.

Installation

yarn add -D i18next-parser
npm install --save-dev i18next-parser

Fonctionnalités Clés

Extraction des chaînes

i18next-parser peut analyser votre code source et extraire automatiquement les chaînes à traduire. Cela permet de s'assurer qu'aucune chaîne n'est omise et facilite le processus de localisation.

Vérification des traductions

L'outil peut comparer les fichiers de chaînes de localisation pour s'assurer que toutes les langues ont les mêmes chaînes traduites, évitant ainsi les incohérences et les traductions manquantes.

Génération des fichiers de localisation

i18next-parser  peut créer des fichiers de chaînes de localisation pour différentes langues à partir des clefs renseignées dans votre code. 

Archivage des clefs non utilisées

Si une clef de vos fichiers de langues n'est pas utilisée dans votre code, elle est archivée.

Ma config que j'utilise dans tous mes projets

Créez un fichier /src/i18n/i18next-parser.config.mjs. 
Vous pouvez bien sûr choisir un autre chemin. Il faudra seulement bien le spécifier dans la commande que nous ajouterons au package.json

const config = {
  contextSeparator: ':',
  // Key separator used in your translation keys

  createOldCatalogs: true,
  // Save the \_old files

  // Default namespace used in your i18next config

  defaultValue: 'NOT_TRANSLATED_KEY',
  // Default value to give to keys with no value
  // You may also specify a function accepting the locale, namespace, key, and value as arguments

  indentation: 2,
  // Indentation of the catalog files

  keepRemoved: false,
  // Keep keys from the catalog that are no longer in code

  keySeparator: 'false',
  // Key separator used in your translation keys
  // If you want to use plain english keys, separators such as `.` and `:` will conflict. You might want to set `keySeparator: false` and `namespaceSeparator: false`. That way, `t('Status: Loading...')` will not think that there are a namespace and three separator dots for instance.

  // see below for more details
  lexers: {
    mjs: ['JavascriptLexer'],
    js: ['JavascriptLexer'], // if you're writing jsx inside .js files, change this to JsxLexer
    ts: ['JavascriptLexer'],
    jsx: ['JsxLexer'],
    tsx: ['JsxLexer'],

    default: ['JavascriptLexer'],
  },

  lineEnding: 'auto',
  // Control the line ending. See options at https://github.com/ryanve/eol

  locales: ['fr', 'en'],

  namespaceSeparator: ':',
  // Namespace separator used in your translation keys
  // If you want to use plain english keys, separators such as `.` and `:` will conflict. You might want to set `keySeparator: false` and `namespaceSeparator: false`. That way, `t('Status: Loading...')` will not think that there are a namespace and three separator dots for instance.

  output: 'src/i18n/$LOCALE/$NAMESPACE.json',
  // Supports $LOCALE and $NAMESPACE injection
  // Supports JSON (.json) and YAML (.yml) file formats
  // Where to write the locale files relative to process.cwd()

  pluralSeparator: '_',
  // Plural separator used in your translation keys
  // If you want to use plain english keys, separators such as `_` might conflict. You might want to set `pluralSeparator` to a different string that does not occur in your keys.

  input: [
    '../components/**/!(*.test).{ts,tsx}',
    '../routes/**/!(*.test).{ts,tsx}',
    '../helpers/**/!(*.test).{ts,tsx}',
    '../apis/**/!(*.test).{ts,tsx}',
    '../providers/**/!(*.test).{ts,tsx}',
  ],
  // An array of globs that describe where to look for source files
  // relative to the location of the configuration file

  sort: true,
  // Whether or not to sort the catalog. Can also be a [compareFunction](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#parameters)

  verbose: true,
  // Display info about the parsing including some stats

  failOnWarnings: false,
  // Exit with an exit code of 1 on warnings

  failOnUpdate: false,
  // Exit with an exit code of 1 when translations are updated (for CI purpose)

  customValueTemplate: null,
  // If you wish to customize the value output the value as an object, you can set your own format.
  // ${defaultValue} is the default value you set in your translation function.
  // Any other custom property will be automatically extracted.
  //
  // Example:
  // {
  //   message: "${defaultValue}",
  //   description: "${maxLength}", // t('my-key', {maxLength: 150})
  // }

  i18nextOptions: { compatibilityJSON: 'v3' },
  // If you wish to customize options in internally used i18next instance, you can define an object with any
  // configuration property supported by i18next (https://www.i18next.com/overview/configuration-options).
  // { compatibilityJSON: 'v3' } can be used to generate v3 compatible plurals.

  yamlOptions: null,
  // If you wish to customize options for yaml output, you can define an object here.
  // Configuration options are here (https://github.com/nodeca/js-yaml#dump-object---options-).
  // Example:
  // {
  //   lineWidth: -1,
  // }
};

export default config;

Les valeurs pour "input" et pour "output" sont probablement à modifier en fonction de l'architecture de votre projet.

Dans package.json, ajoutez le script suivant :

"scripts": {
   ...
    "i18n:parse": "i18next --config ./src/i18n/i18next-parser.config.mjs",
  },

Ça fait quoi ?

1 - Chaque fois que vous allez lancer le script "i18n:parse", i18next-parser va analyser tous les fichiers en fonction de ce que vous avez mis dans "input".

2 - Il va ensuite extraire toutes les occurrences de

t("clee-de-trad")
#ou bien 
<Trans i18nKey="clee-de-trad" .../>
...

et en extraire les valeurs des clefs

Consultez la doc pour plus d'infos sur les fonctions à matcher : https://github.com/i18next/i18next-parser

3 - Si la clef n'existe pas encore dans vos fichiers de traduction, il va l'ajouter pour toutes les langues définies dans "locales"

....
"new-key": "NOT_TRANSLATED_KEY" //valeur définie dans "defaultValue"
}

Vous n'avez plus qu'à renseigner sa traduction. 

4 - Si une clef renseignée dans vos fichiers de langues n'est pas présente dans votre code, elle sera déplacée dans un fichier "/$locale/translation_old.json".

Conclusion

L'internationalisation est un aspect crucial du développement web en 2024, et i18next-parser est un outil puissant pour gérer efficacement ce processus.

Ce petit outil m'a fait tout de même gagné un temps non négligeable. Je ne peux donc que vous le conseiller.

Aller plus loin

On peut ne pas s'arrêter là. Voici quelques pistes pour blinder encore plus vos traductions.

1 - Créer un règle ESlint pour lever une erreur lorsque "NOT_TRANSLATED_ KEY" est repéré dans vos fichiers de langues.  Couplé avec des outils tels que husky, cela vous empêcherai de push des clées non traduites en ligne.

2 - Trouver un moyen de traduire automatiquement les clées avec une api de traduction + peut être un peu d'IA