From db5a522197cb37b6f08c6aabadd411240cebbc36 Mon Sep 17 00:00:00 2001 From: Kyle Hensel Date: Wed, 14 Jan 2026 22:10:06 +1100 Subject: [PATCH 1/2] feat: initial draft --- .../README.md | 349 ++++++++++++++++++ 1 file changed, 349 insertions(+) create mode 100644 designs/2026-localisation-of-rule-messages/README.md diff --git a/designs/2026-localisation-of-rule-messages/README.md b/designs/2026-localisation-of-rule-messages/README.md new file mode 100644 index 00000000..c8448d54 --- /dev/null +++ b/designs/2026-localisation-of-rule-messages/README.md @@ -0,0 +1,349 @@ +- Repo: [eslint/eslint](https://github.com/eslint/eslint) +- Start Date: 2026-01-14 +- RFC PR: [eslint/rfcs#141](https://github.com/eslint/rfcs/pull/141) +- Authors: [Kyle Hensel](https://github.com/k-yle) + +# Support Localisation of Rule Messages + +## 1. Summary + + + +This RFC proposes adding localisation/internationalisation support for rule messages. This allows rules to define messages in multiple langauges, not just English. +Translations could be defined by plugin authors, or in a third-party package, similar to [the `@types/*` ecosystem](http://github.com/definitelyTyped/DefinitelyTyped). + +For example, instead of: + +```handlebars +{{left}} can be removed because it is already included by {{right}} +``` + +Someone who prefers Japanese would see: + +
+ +```handlebars +{{left}}は既に{{right}}に含まれているため、不要です。 +``` + +
+ +(example from [eslint-plugin-regexp](https://ota-meshi.github.io/eslint-plugin-regexp/rules/optimal-quantifier-concatenation.html)) + +## 2. Motivation + + + +Internationalisation would make Eslint more accessible to non-English speakers. +We're already seeing [eslint plugins using non-English languages](https://github.com/LLLaiYoung/eslint-plugin-aegis/blob/9780cd87240e2bbe0c134b97b8776782fe9016e1/lib/rules/no-implicit-complex-object.js#L28) in messages, and there have already been attempts to [translate core rules](https://github.com/mysticatea/eslint-plugin-ja). + +In some regions, learning English is [seen as](https://www.hanselman.com/blog/do-you-have-to-know-english-to-be-a-programmer) a [prerequisite](https://www.youtube.com/watch?v=fsn6buk-BtE) for [learning](https://www.reddit.com/r/learnprogramming/comments/tpyvn3/comment/i2go8sc/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button) to [code](https://antirez.com/news/61). +In other regions, it's [considered](https://www.quora.com/Is-it-true-that-Chinese-and-Russian-programmers-generally-dont-speak-English-How-do-they-then-name-variables-and-write-comments-given-that-in-most-compilers-they-have-to-do-it-in-Latin-alphabet) normal [to write](https://temochka.com/blog/posts/2017/06/28/the-language-of-programming.html) code [with only](https://www.dywt.com.cn) a [minimal](https://pg.ucsd.edu/publications/non-native-english-speakers-learning-programming_CHI-2018.pdf) understanding of English. + +It it possible to build JavaScript products with minimal English knowledge, since many tools are already translated, including [VSCode](https://code.visualstudio.com/docs/configure/locales), [browser de](https://learn.microsoft.com/en-us/microsoft-edge/devtools/customize/localization)[v-tools](https://developer.chrome.com/docs/devtools/customize#language), the [TypeScript compiler](https://github.com/microsoft/TypeScript/blob/dba6c9b824852d3333f2314eca7ad995935227f4/src/loc/lcl/deu/diagnosticMessages/diagnosticMessages.generated.json.lcl#L37), etc. + +[Vue](https://github.com/vuejs) is a great example where [translated docs](https://cn.vuejs.org) (amoung other things) have helped Vue to [foster a community](https://www.reddit.com/r/vuejs/comments/8dhguz/why_are_there_so_many_chinese_vue_ui_frameworks/) of non-English users, and [gain traction](https://www.quora.com/Why-is-Vue-js-widely-used-in-Asia-but-not-in-the-United-States) in places [like China](https://jiyinyiyong.medium.com/the-chinese-part-of-react-community-f783c5e2d0f9), clearly showing the benefit of internationalisation. + +## 3. Detailed Design + +It would be possible to translate messages for core rules (like `@eslint/js`) and rules from plugins. + +There would be two ways of defining translations, inspired by the [DefinitelyTyped (`@types/*`)](http://github.com/DefinitelyTyped/DefinitelyTyped) ecosystem. + +### 3.1. Case 1: The plugin wants to maintain its own translations + +If a plugin author wants to maintain translations themselves, they can define these translations in the rule's code, alongside the existing English messages: + +```diff + module.exports = { + meta: { + messages: { + avoidName: 'Avoid using variables named {{name}}', + }, ++ message_translations: { ++ 'es': { ++ avoidName: 'Evite utilizar variables llamadas {{name}}', ++ }, ++ 'zh-Hans': { ++ avoidName: '避免使用名为{{name}}的变量', ++ }, ++ }, + }, + create: (context) => ({ + Identifier(node) { + if (node.name == 'foo') { + context.report({ node, messageId: 'avoidName', data: { name: node.name } }); + } + }, + }), + }; +``` + +This would work for messages used by `context.report()`, and for messages used by [suggestions](https://eslint.org/docs/latest/extend/custom-rules#suggestion-messageids). + +### 3.2. Case 2: The plugin does NOT want to maintain translations + +Plugin authors may not want to maintain translations themselves. +This is quite reasonable, maintaining translations increases the workload for maintainers, and can be difficult to review without other experts in each language. + +This is a similar situation to JavaScript libraries which don't want to maintain TypeScript definitions. +In this case, contributors can maintain the types in [DefinitelyTyped](http://github.com/DefinitelyTyped/DefinitelyTyped). + +This RFC proposes an equivalent monorepo, tentatively called "_eslint-translations_". +It would be setup very similarly to [DefinitelyTyped](http://github.com/DefinitelyTyped/DefinitelyTyped). Specifically: + +- A folder for each library, like `eslint-plugin-example`. +- Anyone could submit a PR to create or update translations +- A bot would automatically publish packages to npm, perhaps under a scope like `@eslint-translations/*`. + This repository would be heavily automated, see [this flowchart](https://github.com/microsoft/DefinitelyTyped-tools/blob/main/packages/mergebot/docs/dt-mergebot-lifecycle.svg) from [DefinitelyTyped](http://github.com/DefinitelyTyped/DefinitelyTyped) for inspiration. + +Potential repository structure: + +```ini +├── packages +| ├── eslint-plugin-example1 +| | ├── de.json +| | └── zh-Hant.json +| └── my-org__eslint-plugin # for @my-org/eslint-plugin +| ├── pt-BR.json +| └── fr.json +# only showing interesting files for brevity +``` + +### 3.3. Implementation for _eslint core_ + +#### 3.3.1. Determining the user's locale + +Eslint would use the OS' locale. Obtaining the locale used to be a [complicated, platform-specific process](https://github.com/sindresorhus/os-locale/blob/main/index.js), that required a lot of [hardcoded data](https://github.com/sindresorhus/lcid/blob/main/lcid.json). Thanks to [`Intl`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl), it's now possible with 1 line of code (requires [NodeJS v13 or newer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/resolvedOptions#browser_compatibility)): + +```js +Intl.DateTimeFormat().resolvedOptions().locale; // -> 'es-419' +``` + +Eslint could also support overriding the locale with a CLI argument like `--locale=de-DE` (further discussion in [§10.3](#10-3)). +This CLI argument would be a comma-separated string, where each value is a valid [BCP 47 language tag](https://developer.mozilla.org/en-US/docs/Glossary/BCP_47_language_tag). +If specified, the CLI argument will be the preferred locale, falling back to the OS' locale. + +The locale would be exposed to rules via [`context`](https://eslint.org/docs/latest/extend/custom-rules#the-context-object)`.locale`, in case they need it for their own formatting (see [§8.1](#81-limitations-of-message-placeholders) for an example). +This attribute would be an array of strings, to match [`navigator.languages`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/languages). +Using [`context.settings`](https://eslint.org/docs/latest/use/configure/configuration-files#configuring-shared-settings)`.locale` is an alternative, but it is not safe, because plugins might already be using this attribute for other purposes. + +#### 3.3.2. Matching the user's locale to available translations + +If the user's device reports the locale as [`zh-CN`](https://r12a.github.io/app-subtags/index?lookup=zh-CN), we should first call [`Intl.Locale#maximize`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/maximize) to expand the locale to include [likely subtags](https://www.unicode.org/reports/tr35/#Likely_Subtags). +This would give us [`zh-Hans-CN`](https://r12a.github.io/app-subtags/index?lookup=zh-Hans-CN) (or [`en-Latn-US`](https://r12a.github.io/app-subtags/index?lookup=en-Latn-US), [`ru-Cyrl-RU`](https://r12a.github.io/app-subtags/index?lookup=ru-Cyrl-RU), etc.). + +We would then follow the [lookup algorithm from RFC-4647](https://datatracker.ietf.org/doc/html/rfc4647#section-3.4) to match the expanded code ([`zh-Hans-CN`](https://r12a.github.io/app-subtags/index?lookup=zh-Hans-CN)) to the most appropriate language for which we have translations. + +#### 3.3.3. Resolving translations + +> **Assumption:** The default language of [`meta.messages`](https://eslint.org/docs/latest/extend/custom-rules#placeholders-in-suggestion-messages) is English. If the user's language is [`en-*`](https://r12a.github.io/app-subtags/index?lookup=en), translations will not take effect. +> This ensures that eslint is not slowed down by this feature in CI environments, nor for users who expect English to be used. +> This assumption is not always correct ([counterexample: `messages` in Chinese](https://github.com/LLLaiYoung/eslint-plugin-aegis/blob/9780cd87240e2bbe0c134b97b8776782fe9016e1/lib/rules/no-implicit-complex-object.js#L28)). + +**Case 1:** Resolving translations from the rule's source is trivial. [`computeMessageFromDescriptor()`](https://github.com/eslint/eslint/blob/23490b266276792896a0b7b43c49a1ce87bf8568/lib/linter/file-report.js#L440) would simply check `message_translations[locale][messageId]` before falling back to `messages[messageId]`. + +**Case 2:** If translations cannot be found in the rule itself, we need to: + +- check if `@eslint-translations/{packageName}` exists with `require.resolve` +- if yes, read that package and check if it contains a matching language +- return the `messages` from that package. + +> [!NOTE] +> Searching `node_modules` doesn't align with the [flat config](https://eslint.org/blog/2022/08/new-config-system-part-2/)'s approach to configuration. +> Nowadays, it might be preferred to define the external source of translations in `eslint.config.js`? +> Needs discussion… + +### 3.4. Prior Art + +- [microsoft/vscode-l10n](https://github.com/microsoft/vscode-l10n) is used for localising vscode extensions. Translations can contain placeholders like `Hello, {0}`, but [only literals are allowed](https://github.com/microsoft/vscode-l10n/blob/3fc1f7b7abfa6fc16e9ec9398ec3f2108112796b/l10n/src/main.ts#L161) (`string | number | boolean`). +- The TypeScript compiler [has translations](https://github.com/microsoft/TypeScript/tree/dba6c9b824852d3333f2314eca7ad995935227f4/src/loc/lcl) for all their [diagnostics messages](https://github.com/microsoft/TypeScript/blob/f5ccf4345d6ac01f47be84db99fde50b98accbb5/src/compiler/diagnosticMessages.json#L3268). + +## 4. Documentation + + + +Blog announcement. +Update documentation and provide examples for: + +- a plugin that wants to maintain translations themselves +- a plugin where translations are maintained in a third-party package (like `@eslint-translations/*`). + +## 5. Drawbacks + + + +1. Increased maintenance burden, if translators choose to maintain translations themselves. +2. Increased download size, if translators choose to maintain translations themselves. +3. Slower execution time when translation is enabled. + For each plugin without 1st-party translations, _eslint core_ needs to check `node_modules` if 3rd-party translations are available (from a package called `@eslint-translations/{pluginName}`) + +4. The presence of translations may subconsciously discourage maintainers from tweaking the wording or structure of a rule message. + For example, renaming a `{{placeholder}}` in a message would be a breaking change, because 3rd-party translations would also need to rename the placeholder. + +## 6. Backwards Compatibility Analysis + + + +- For core- and plugin- maintainers: `messageId`s are now a public API, since they could be referenced in 3rd-party translations. + The names of `{{placeholders}}` in messages are also public. + Changing either `messageId`s or placeholders would be a breaking change if the package has 3rd-party translations. +- Some tools which [hardcode eslint error messages](https://github.com/microsoft/vscode-eslint/blob/fd82f23df1d0e2b7e5040ad3532444d0f66cf8dd/server/src/eslint.ts#L184) will break. +- No impact on the minimum supported NodeJS version; [the relev](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/resolvedOptions#browser_compatibility)[ant `Intl` APIs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/maximize#browser_compatibility) have been supported since Node v13 +- `context.locale` is a new property. + Rules must expect this property to be undefined in older eslint versions. +- `--locale` is a new CLI argument. + Using `--locale` with an older eslint version will cause eslint to exit with a non-zero status code, like any other unsupported CLI argument. + +## 7. Alternatives + + + +1. Maintain the status-quo; do not suppport localisation. + Those who use AI tools in their IDE could continue to ask the LLM to translate the error. + Everyone else would be required to read the error in English. + Disadvantageous because natural language comprehension may be slower in English compared to people's native language. + +2. Implement translation completely in userland, no official support from eslint. + Anyone who wants translations would need to use a patched version of eslint (which [has been attempted](https://github.com/mysticatea/eslint-plugin-ja)). Or alternatively, patch `rule.messages` yourself in your `eslint.config.js` file. + This is obviously less reliable and error prone. + +## 8. Open Questions + + + +### 8.1. Limitations of message placeholders + +Currently, Eslint [requires](https://github.com/eslint/rewrite/blob/7abc05147e2b6d29cb5170867c2172d25c563454/packages/core/src/types.ts#L166) message placeholders to be a literal. +Message placeholders also cannot reference other messages. + +This creates several limitations: + +1. Some rules need to include an array as a placeholder, so they must convert the array into a string themselves. [For example, using `array.join(' and ')`](https://github.com/typescript-eslint/typescript-eslint/blob/dd90da8f442a651ab0dbaead80735f9259260569/packages/eslint-plugin/src/util/misc.ts#L187). +2. Some rules [include English text in message placeholders](https://github.com/typescript-eslint/typescript-eslint/blob/dd90da8f442a651ab0dbaead80735f9259260569/packages/eslint-plugin/src/rules/no-unnecessary-type-conversion.ts#L284-L287), or even [entire sentances](https://github.com/ota-meshi/eslint-plugin-regexp/blob/2524e3e6e57d4e852245a16938e021846f43f25b/lib/rules/optimal-quantifier-concatenation.ts#L654-L668), because it's more concise than creating many `messageId`s. +3. Some major plugins like `eslint-plugin-react-hooks` do [not use `messageId`s](https://github.com/facebook/react/blob/5aec1b2a8d1bcafb43a7ddb64dc37eacad081bea/packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts#L627-L632), so translations would not be possible. Suggestions also don't use `messageId`s. +4. Some rules produce messages that are [far too co](https://github.com/facebook/react/blob/5aec1b2a8d1bcafb43a7ddb64dc37eacad081bea/packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts#L924-L938)[mplicated](https://github.com/facebook/react/blob/5aec1b2a8d1bcafb43a7ddb64dc37eacad081bea/packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts#L1052-L1053) to model with `messageId`s + +In all cases, the result is that there are English words scattered across the code, which can't be translated. + +Issue #1 is solvable: the plugin should replace their hardcoded `array.join(' and ')` with [`Intl.ListFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat) (available since NodeJS v13). +This would allow an English speaker to see `"A", "B", and "C"`, while a Chinese speaker would see `A、B和C`. + +The other issues can't be trivially solved, and would require eslint to support a more complex syntax for message placeholders. +There are several options: + +1. Wait for the [TC39 proposal for `Intl.MessageFormat`](https://github.com/tc39/proposal-intl-messageformat), which is currently [stuck](https://github.com/tc39/proposal-intl-messageformat/issues/49) in Stage-1. + Once supported natively without a polyfill, allow messages to use the [ICU Message Syntax](https://unicode-org.github.io/icu/userguide/format_parse/messages/). + This would be a breaking change, but conveniently, the ICU Message Syntax uses single curly braces (`{ }`) for placeholders, while eslint currently uses double curly braces (`{{ }}`), so there is no syntax clash. +2. Support the [ICU Message Syntax](https://unicode-org.github.io/icu/userguide/format_parse/messages/) with an npm dependency (like [this one](https://github.com/messageformat/messageformat)), at least until the [TC39 proposal for `Intl.MessageFormat`](https://github.com/tc39/proposal-intl-messageformat) is natively supported. + This increases the bundle size of eslint for very minimal benefit. +3. Support **only** recursively nested messages, no other complex syntax. For example: + ```js + messages: { + banned: '{{name}} is not allowed{{extra}}', + suffix1: ' because it could be confusing.', + suffix2: ' because it is a reserved word.', + } + // … + context.report({ + messageId: 'banned', + data: { name: 'foo', extra: '{{suffix1}}' } + }); + ``` +4. Do nothing, make no change to the format of message placeholders. + Accept that some translations will be imperfect, and will contain a mix of English and the other language. + +### 8.2. governance of the `eslint-translations` monorepo + +- How would this be maintained, would it be an [eslint-community](https://github.com/eslint-community) project? +- Does it make sense to publish an npm package for each plugin (like `@eslint-translations/eslint-plugin-example`), or a single npm package with translations for every plugin? +- Would IDE extensions like [vscode-eslint](https://github.com/microsoft/vscode-eslint) be willing to automatically download `@eslint-translations/*` packages, even if they're not specified in package.json? + This is [what the TypeScript Language Server does](https://github.com/microsoft/vscode-docs/blob/71c6970d906cf2a9174d0c4223d759cf2c64a7bf/docs/nodejs/working-with-javascript.md?plain=1#L30) in VSCode, for `@types/*` packages (in `.js` files only) + +## 9. Help Needed + + + +- Opinions from people who speak other languages are always valuable. +- Opinions from eslint maintainers, and maintainers of plugins and eslint IDE integrations. +- Opinions from plugin maintainers – is there a general preference for maintaining translations in the repo itself, or in a different translations project? + +## 10. Frequently Asked Questions + + + +> 10.1. Should translations be version-controlled or stored in a Translation Management System (TMS)? + +I would suggest **not** using a Translation Management System because: + +1. Version-controlling translations using JSON files is significantly simpler than integrating with a third-party commercial tool. +2. Translators are almost certainly software developers themselves, so storing translations in a git repository would not be a barrier. +3. Open-source tools exist to make it more convenient to manage translations in git (like [i18n-ally](https://github.com/antfu/i18n-ally)) + +--- + +> 10.2. What about translations for the docs and [internal error messages](https://github.com/eslint/eslint/tree/main/messages)? + +This is considered out of scope. +Docs translations are tracked by [eslint/eslint#11512](https://github.com/eslint/eslint/issues/11512), and internal error messages are not a public API, so translations can be implemented much more easily. + +--- + +> 10.3. Should it be possible to define your preferred language in `eslint.config.js`? + +I believe **no**. Conceptually, it doesn't make sense to store your preferred language in version-control. +TypeScript also only allows [`--locale` as a CLI argument](https://www.typescriptlang.org/docs/handbook/compiler-options.html#:~:text=locale), it can't be defined in [tsconfig.json](https://typescriptlang.org/tsconfig). + +## 11. Related Discussions + +- Rule message translation: + - 2026: [eslint/rfcs#141](https://github.com/eslint/rfcs/pull/141) (this RFC) + - 2024: [eslint/rfcs#128](https://github.com/eslint/rfcs/pull/128) + - 2024: [eslint/eslint#19210](https://github.com/eslint/eslint/issues/19210) + - 2018: [eslint/eslint#9870](https://github.com/eslint/eslint/issues/9870) +- Docs translations: + - 2025: [eslint/zh-hans.docs.eslint.org#130](https://github.com/eslint/zh-hans.docs.eslint.org/issues/130) + - 2019: [eslint/eslint#11512](https://github.com/eslint/eslint/issues/11512) + - 2015: [eslint/archive-website#161](https://github.com/eslint/archive-website/issues/161) From 24b5d54167302afa9a80cd5d7ea11ceabccff538 Mon Sep 17 00:00:00 2001 From: Kyle Hensel Date: Thu, 22 Jan 2026 20:22:36 +1100 Subject: [PATCH 2/2] feat: [v2] wording tweaks --- .../README.md | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/designs/2026-localisation-of-rule-messages/README.md b/designs/2026-localisation-of-rule-messages/README.md index c8448d54..8af1efd9 100644 --- a/designs/2026-localisation-of-rule-messages/README.md +++ b/designs/2026-localisation-of-rule-messages/README.md @@ -61,7 +61,7 @@ If a plugin author wants to maintain translations themselves, they can define th messages: { avoidName: 'Avoid using variables named {{name}}', }, -+ message_translations: { ++ messageTranslations: { + 'es': { + avoidName: 'Evite utilizar variables llamadas {{name}}', + }, @@ -111,6 +111,17 @@ Potential repository structure: # only showing interesting files for brevity ``` +The files could be JSON or JS, and would export an object with the following structure: + +```js +// es.json or es.js: +export default { + example_rule_name: { + avoidName: 'Evite utilizar variables llamadas {{name}}', + }, +}; +``` + ### 3.3. Implementation for _eslint core_ #### 3.3.1. Determining the user's locale @@ -123,7 +134,7 @@ Intl.DateTimeFormat().resolvedOptions().locale; // -> 'es-419' Eslint could also support overriding the locale with a CLI argument like `--locale=de-DE` (further discussion in [§10.3](#10-3)). This CLI argument would be a comma-separated string, where each value is a valid [BCP 47 language tag](https://developer.mozilla.org/en-US/docs/Glossary/BCP_47_language_tag). -If specified, the CLI argument will be the preferred locale, falling back to the OS' locale. +If specified, the CLI argument will be the preferred locale, falling back to the OS' locale, and using the matching strategy in [§3.3.2](#332-matching-the-users-locale-to-available-translations). The locale would be exposed to rules via [`context`](https://eslint.org/docs/latest/extend/custom-rules#the-context-object)`.locale`, in case they need it for their own formatting (see [§8.1](#81-limitations-of-message-placeholders) for an example). This attribute would be an array of strings, to match [`navigator.languages`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/languages). @@ -142,19 +153,14 @@ We would then follow the [lookup algorithm from RFC-4647](https://datatracker.ie > This ensures that eslint is not slowed down by this feature in CI environments, nor for users who expect English to be used. > This assumption is not always correct ([counterexample: `messages` in Chinese](https://github.com/LLLaiYoung/eslint-plugin-aegis/blob/9780cd87240e2bbe0c134b97b8776782fe9016e1/lib/rules/no-implicit-complex-object.js#L28)). -**Case 1:** Resolving translations from the rule's source is trivial. [`computeMessageFromDescriptor()`](https://github.com/eslint/eslint/blob/23490b266276792896a0b7b43c49a1ce87bf8568/lib/linter/file-report.js#L440) would simply check `message_translations[locale][messageId]` before falling back to `messages[messageId]`. +**Case 1:** Resolving translations from the rule's source is trivial. [`computeMessageFromDescriptor()`](https://github.com/eslint/eslint/blob/23490b266276792896a0b7b43c49a1ce87bf8568/lib/linter/file-report.js#L440) would simply check `messageTranslations[locale][messageId]` before falling back to `messages[messageId]`. **Case 2:** If translations cannot be found in the rule itself, we need to: -- check if `@eslint-translations/{packageName}` exists with `require.resolve` +- check if `@eslint-translations/{packageName}` exists with [`require.resolve`](https://nodejs.org/api/modules.html#requireresolverequest-options), similar to the method used [to load formatters](https://github.com/eslint/eslint/blob/b34b93852d014ebbcf3538d892b55e0216cdf681/lib/eslint/eslint.js#L1197). - if yes, read that package and check if it contains a matching language - return the `messages` from that package. -> [!NOTE] -> Searching `node_modules` doesn't align with the [flat config](https://eslint.org/blog/2022/08/new-config-system-part-2/)'s approach to configuration. -> Nowadays, it might be preferred to define the external source of translations in `eslint.config.js`? -> Needs discussion… - ### 3.4. Prior Art - [microsoft/vscode-l10n](https://github.com/microsoft/vscode-l10n) is used for localising vscode extensions. Translations can contain placeholders like `Hello, {0}`, but [only literals are allowed](https://github.com/microsoft/vscode-l10n/blob/3fc1f7b7abfa6fc16e9ec9398ec3f2108112796b/l10n/src/main.ts#L161) (`string | number | boolean`). @@ -324,10 +330,10 @@ I would suggest **not** using a Translation Management System because: --- -> 10.2. What about translations for the docs and [internal error messages](https://github.com/eslint/eslint/tree/main/messages)? +> 10.2. What about translations for the docs and internal strings (such as [messages](https://github.com/eslint/eslint/tree/main/messages), [services](https://github.com/eslint/eslint/blob/43677de07ebd6e14bfac40a46ad749ba783c45f2/lib/services/warning-service.js#L33), and [inline configuration directives](https://github.com/eslint/eslint/blob/43677de07ebd6e14bfac40a46ad749ba783c45f2/lib/linter/linter.js#L1074))? This is considered out of scope. -Docs translations are tracked by [eslint/eslint#11512](https://github.com/eslint/eslint/issues/11512), and internal error messages are not a public API, so translations can be implemented much more easily. +Docs translations are tracked by [eslint/eslint#11512](https://github.com/eslint/eslint/issues/11512), and internal strings are not exposed as a public API, so translations can be implemented much more easily. ---