Publier en français et en anglais

Plus nos contacts sont cosmopolites, plus on s'appuie sur l'existence de Google Translate et ça ne sera jamais vraiment parfait, en particulier quand on apprécie l'ironie et les jeux de mots. Ma première expérience de traduction automatique a été spectaculaire, je donnais à traduire en français la première page de mon site web (qui était en anglais). Le résultat n'était pas mauvais, on pouvait comprendre ce que je voulais dire, mais j'étais surpris par la conclusion, ça disait "Le séjour est accordé !" et je ne voyais pas ce que j'avais pu écrire qui corresponde, en relisant la version d'origine je trouvais "Stay tuned !", expression radiophonique apparentée à "à suivre", qui pourrait se traduire par "restez sur cette fréquence". Le système, qui ne connaissait pas l'expression choisit un autre sens de stay et choisit accordé comme pour un instrument, ce qui prète à sourire parce qu'on l'entend dans un autre sens, comme si on nous octroyait des vacances...

Chaque fois que je publie quelque chose, ici ou sur les réseaux sociaux, je dois décider si je vais écrire en français ou en anglais. Tout ce qui est technique, je l'écris en anglais, principalement parce que j'y pense en anglais mais aussi parce qu'il est probable que les francophones intéressés soient déjà habitués à consulter ces sujets en anglais. Quand il s'agit de restaurants, de bons produits ou de tourisme, j'écris en français parce que je crois que c'est surtout utile à des français. Tout ces raisonnements ne sont nécessaires que parce qu'il faut choisir, puisqu'il n'est presque jamais possible de poster plusieurs versions. Pourtant, souvent, j'aimerai bien proposer ma propre traduction.

Du coup, j'ai été bien content quand Next.js 10 a intégré le routing internationalisé. Ça peut sembler peu de chose, le routing n'est qu'une petite part du problème, mais c'est décisif, ça permet de démarrer : le problème est déplacé de 'traduire tout le site' à 'traduire des pages'. Il m'a été assez simple de l'intégrer dans la structure que j'ai présenté la dernière fois.

Mon besoin était de pouvoir ajouter une traduction simplement en créant un autre fichier markdown de la forme i18n/fr-FR/{postPath}.md. C'est l'approche la plus simple qu'on puisse imaginer.

La première chose à faire pour que cela fonctionne a été de modifier mon plugin webpack pour qu'il vérifie s'il y a de tels fichiers traduits. Deux choses sont produites à partir de la liste de versions disponibles : une série de commandes important les fichiers dans des variables et une série de 'case' qui permet de faire correspondre ces variables avec les libellés des locales. L'intérêt de passer par des import est qu'ainsi Next.js ajoute nos fichiers à son arbre de dépendance.

const versions = i18nConfig.locales.reduce(
  (cur,locale)=>{
    const translation = path.relative(
      basePath,
      path.join(basePath,'i18n',locale, relPath)
    )
    if (fs.existsSync(translation))
      cur[locale] = '~/'+translation

    return cur
  },
  {}
)

const imports = (
  Object.keys(versions)
  .map(
    locale=>`import ${localeVariableName(locale)}
             from '${versions[locale]}'` )
  .join('\n')
)

const versionSwitch = (
  Object.keys(versions)
  .map(
    locale => `case '${locale}':
                return ${localeVariableName(locale)}`)
  .join('\n')
)

Une fois ces valeurs calculées, je peux générer le code javascript correspondant (seules les parties pertinents sont reproduites ci-dessous). Ce qui est intéressant, c'est que Next.js va générer des versions séparés pour chaque langue qui ne contiendront que les données nécessaires. Noter aussi comment la version traduite n'a besoin dans sa front matter que des éléments nécessitant traduction (ce qui se limite en général au titre)

<<<imports>>>

function getSource(locale){
  switch (locale) {
    <<<versionSwitch>>>
  }
  return null
}

export async function getStaticProps(context) {
  const defaultVersion = <<<escapedSource>>>

  const findVersion = locale=>{
    const src = getSource(locale)
    if (src) return mergeFrontmatter(locale,src,defaultVersion)
    return defaultVersion
  }

  return {
    props: {
      content: findVersion(context.locale),
      threadPosts: getPostThread(
        'posts',
        '${md.data.thread}',
        ['slug', 'title', 'date']
      )
    },
  }
}

L'un dans l'autre, tout cela fut assez facile à implémenter et se révèle tout simplement trivial à mettre en oeuvre. J'adore !

Comme précédemment, n'hésitez pas à aller voir le code sur gitHub !

Jérôme Muffat-Méridol
1 février 2021

Behind the scenes

  1. open-sourcing myself
  2. using Markdown as Next.js pages
  3. Posting both in French and English