DEV Community

Cover image for Using eslint to fix wrong i18n usages
Sibelius Seraphini for Woovi

Posted on

Using eslint to fix wrong i18n usages

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 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

t(`CHARGE_STATUS.${charge.status}`)
Enter fullscreen mode Exit fullscreen mode

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

t(result.error)
Enter fullscreen mode Exit fullscreen mode

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

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: [],
});
Enter fullscreen mode Exit fullscreen mode

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

ast-t

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 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!

Top comments (1)

Collapse
 
samuelstroschein profile image
Samuel Stroschein

You should take a look at github.com/inlang/inlang/tree/main.... We just implemented "ESLint for translations". No need for custom eslint rules.

PS it is early though :)