Using eslint to fix wrong i18n usages
Tags: eslint, codemod, pattern, i18n
Published: 2023-03-16 11:25:48

We care a lot about DX at Woovi. We want to find the best workflow to make it easy for developers to work with i18n strings. Manually updating the code and the i18n locale JSON files is slow and error-prone. 

Our current workflow after many iterations is like this:

- Write your i18n strings using t('key in english'), you write the string in English.
- We run [i18next-scanner](https://github.com/i18next/i18next-scanner) to extract all usages of `t` in the codebase
- the developer just needs to fill the Portuguese and Spanish translations (we have some POCs to use Google Translate, ChatGPT or Copilot to autofill translations for us).

## Avoiding i18n that can't be extracted

You can only extract static strings, you can't extract string interpolations or variables.

Here are 2 examples of bad usage of i18n

```js
t(`CHARGE_STATUS.${charge.status}`)
```

We can't know all strings that need to be extracted when using a string interpolation and a variable


```jsx
t(result.error)
```

We can't know all possible strings of `result.error` neither.

To avoid this bad usages in our codebase, we decided to write a custom Eslint rule to catch this mistakes at pre-commit hook.

## noVariableT eslint rule

```jsx
module.exports = createRule({
  create: (context) => ({
    CallExpression(node) {
      if (node.callee.name === 't') {
        const arg = node.arguments[0];

        if (arg.type === 'TemplateLiteral' && arg.expressions.length === 0) {
          return;
        }

        if (!arg || arg.type !== 'Literal' || typeof arg.value !== 'string') {
          context.report({
            node,
            message:
              'Only string literals are allowed as arguments to the t() function.',
          });
        }
      }
    },
    MemberExpression(node) {
      if (node.property.name === 't') {
        if (!node.parent.arguments) {
          return;
        }

        const arg = node.parent.arguments[0];

        if (arg.type === 'TemplateLiteral' && arg.expressions.length === 0) {
          return;
        }

        if (!arg || arg.type !== 'Literal' || typeof arg.value !== 'string') {
          context.report({
            node,
            message:
              'Only string literals are allowed as arguments to the t() function.',
          });
        }
      }
    },
  }),
  name: 'no-variable-t',
  meta: {
    type: 'problem',
    fixable: 'code',
    docs: {
      description: 'Using t(variable) breaks i18next-scanner',
      recommended: 'error',
    },
    schema: [],
  },
  defaultOptions: [],
});
```

To understand what this eslint rule is doing, you need to understand how JavaScript AST works. You can explore it here [https://astexplorer.net/](https://astexplorer.net/)


![ast-t](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zv32d9l5gh8u9tbd80zv.png)

A `CallExpression` represents a function call. The rule searches for all `t` `callExpression` that are not using a string literal, and report an error.


## To sum up
DX is also how do you avoid developers making mistakes.
At Woovi we don't blame a person when an error is made, we try to understand the root cause, improve our process and automation to avoid this happening again.

How are you avoiding mistakes at your Startup? What automation do you have in place?


---
Woovi
[Woovi](https://www.woovi.com) is a Startup that enables shoppers to pay as they like. To make this possible, Woovi provides instant payment solutions for merchants to accept orders.

If you want to work with us, we are [hiring](https://woovi.com/jobs/)!