---
createdAt: 2026-08-12
updatedAt: 2026-08-13
title: ESLint 插件 | Intlayer 的 Lint 规则
description: 使用 eslint-plugin-intlayer 捕获硬编码字符串、Intlayer 编译器无法优化的动态调用以及未使用的字典内容。支持 ESLint 和 oxlint,适用于 React、Vue、Svelte、Angular 和 Astro。
keywords:
- Intlayer
- ESLint
- oxlint
- 代码检查
- i18n
- 国际化
- no-raw-text
- 硬编码字符串
- 未使用的翻译
- 死内容
- React
- Vue
- Svelte
- Angular
slugs:
- doc
- eslint
history:
- version: 9.3.1
date: 2026-08-12
changes: "初始化历史"
author: aymericzip
---
# ESLint x OXLint 插件
`eslint-plugin-intlayer` 能够捕获 TypeScript 无法发现的几类 i18n 错误:
1. **硬编码文本**:从未写入字典中的文本。
2. **动态调用**:能够通过类型检查并正常运行,但 Intlayer 编译器无法进行优化的调用。
3. **死内容(Dead content)**:项目中没有任何地方读取的字典和字段(需手动开启)。
未知的字典键、未知的字段路径和缺失的语言环境本身已是编译错误,因此插件不会重复报告它们。
## 安装
```bash packageManager="npm"
npm install --save-dev eslint-plugin-intlayer
```
```bash packageManager="pnpm"
pnpm add --save-dev eslint-plugin-intlayer
```
```bash packageManager="yarn"
yarn add --dev eslint-plugin-intlayer
```
需要 ESLint 9 或更高版本(Flat config)。支持 ESLint 10。
## 使用方法
该插件可在 ESLint 和 [oxlint](https://oxc.rs) 中运行 — 拥有相同的规则和配置选项。
```javascript fileName="eslint.config.mjs" codeFormat="esm"
import intlayer from "eslint-plugin-intlayer";
export default [...intlayer.configs.recommended];
```
或者展开某个配置并自行设置严重级别:
```javascript fileName="eslint.config.mjs" codeFormat="esm"
import intlayer from "eslint-plugin-intlayer";
export default [
...intlayer.configs.recommended,
{
rules: {
"intlayer/no-raw-text": "warn",
"intlayer/static-dictionary-key": "error",
"intlayer/no-dynamic-field-access": "error",
"intlayer/enforce-adapter-import": "warn",
"intlayer/no-unused-content": "warn",
},
},
];
```
```json fileName=".oxlintrc.json"
{
"jsPlugins": ["eslint-plugin-intlayer"],
"rules": {
"intlayer/no-raw-text": "warn",
"intlayer/static-dictionary-key": "error",
"intlayer/no-dynamic-field-access": "error",
"intlayer/enforce-adapter-import": "warn"
}
}
```
两点注意事项:oxlint 对 JS 插件的支持仍处于 Alpha 阶段,且 oxlint 不支持自定义解析器 — 因此 `.vue`、`.svelte`、`.astro` 和 Angular 模板无法在此处进行 lint。请在 JS/TS/JSX 文件上运行 oxlint,其余文件保留使用 ESLint。
上面特意排除了 `no-unused-content`:它需要从规则上下文中获取工作目录和被检查文件的路径,而 Alpha 阶段的 JS 插件桥接层无法保证提供这些信息。请在 ESLint 下运行该规则。
### 预设配置
| 配置 | `no-raw-text` | `static-dictionary-key` | `no-dynamic-field-access` | `enforce-adapter-import` | `no-unused-content` |
| --------------- | ----------------------- | ----------------------- | ------------------------- | ------------------------ | ------------------- |
| `recommended` | warn | error | error | off | off |
| `strict` | error (+ 非 JSX 字面量) | error | error | error | off |
| `contract-only` | off | error | error | off | off |
`recommended` 特意将 `no-raw-text` 设为 `warn`:将其指向现有代码库会一次性暴露所有未翻译的字符串,这不应该在第一天就导致构建中断。
`enforce-adapter-import` 默认关闭 — 如果需要请显式启用。
`no-unused-content` 在所有配置中均默认关闭(包括 `strict`)。这是唯一一个需要读取 Intlayer 配置并从磁盘遍历源文件的规则,因此启用它应当是一项经过深思熟虑的选择,而非预设自动执行的行为。
## 规则列表
### `no-raw-text`
报告未在字典中声明的面向用户的文本。它使用与 `intlayer extract` 相同的检测逻辑,因此品牌名称、CSS 类名和技术标识符都会被忽略。
```jsx
// ✗ 报告错误
Welcome to our documentation
// ✓ 正常
const { title } = useIntlayer("home");
{title}
```
内容声明文件(`*.content.ts`, …)会被跳过。
若要一次性修复整个文件,运行 `npx intlayer extract`,让编译器自动将字符串移入字典。
**配置选项**
```javascript fileName="eslint.config.mjs" codeFormat="esm"
{
"intlayer/no-raw-text": [
"warn",
{
// 值为面向用户文本的属性列表。
// 默认值: title, placeholder, alt, aria-label, label
attributes: ["title", "placeholder", "alt", "aria-label", "label"],
// 内容绝非面向用户文本的元素列表。
// 默认值: code, pre, script, style
ignoreElements: ["code", "pre", "script", "style"],
// 绝不报告的文本正则表达式。
ignorePatterns: ["^Powered by"],
// 是否同时报告标记语言之外的字符串字面量。默认值: false
includeStringLiterals: false,
},
],
}
```
### `static-dictionary-key`
要求字典键必须是字符串字面量。
编译器只有在调用位置直接读取到键时,才能预加载字典。使用计算键会静默跳过优化,转而打包所有字典。
```typescript
// ✗ 报告错误
useIntlayer(dictionaryKey);
useIntlayer(`home-${suffix}`);
getTranslations({ namespace: page });
// ✗ 变量仍然不是字面量
const key = "home";
useIntlayer(key);
// ✓ 正常
useIntlayer("home");
getTranslations({ namespace: "home" });
```
这适用于 `useIntlayer`、`getIntlayer` 以及所有兼容适配器(`useTranslation`、`useTranslations`、`formatMessage`、``、`` 等)。
### `no-dynamic-field-access`
要求从字典中读取的字段必须是静态已知的。
编译器会移除它未检测到使用的字段。动态计算访问对其不可见,因此该读取在运行时可能会返回 `undefined`。
```typescript
// ✗ 报告错误
const content = useIntlayer("home");
content[fieldName];
const t = useTranslations("home");
t(messageKey);
// ✓ 正常
content.title;
content["title"];
content.items[0];
t("hero.title");
```
### `enforce-adapter-import`
优先使用 `@intlayer/*` 兼容适配器而非原始包。原始包仅在配置了打包工具别名时才会解析为 Intlayer;而适配器始终生效。可通过 `--fix` 自动修复。
```typescript
// ✗ 报告错误
import { useTranslation } from "react-i18next";
import { getTranslations } from "next-intl/server";
// ✓ 正常
import { useTranslation } from "@intlayer/react-i18next";
import { getTranslations } from "@intlayer/next-intl/server";
```
### `no-unused-content`
**默认关闭。** 报告项目中没有任何地方读取的内容,以及在多个位置声明的字典键。
```typescript fileName="src/home.content.ts"
export default {
key: "home", // ✗ 当项目中没有任何调用方请求 "home" 时报告
content: {
title: t({ zh: "标题", en: "Title" }),
// ✗ 当没有任何地方读取 `hero` 时报告
hero: {
subtitle: t({ zh: "副标题", en: "Subtitle" }),
},
},
};
```
与其他规则不同,此规则无法仅凭眼前的文件给出判断 — 字段是否未使用仅相对于整个项目而言。在一次 lint 运行的首次内容声明时,它会加载你的 Intlayer 配置,匹配该配置声明的源文件(`build.traversePattern`、`compiler.transformPattern`),并运行驱动 `@intlayer/lsp` 和 VS Code 扩展中“未使用”删除线的同一套使用情况分析器。结果会缓存 `cacheTtl` 毫秒,因此每次运行只会扫描一次,而不是每个文件扫描一次。
**配置选项**
```javascript fileName="eslint.config.mjs" codeFormat="esm"
{
"intlayer/no-unused-content": [
"warn",
{
// 报告没有任何地方引用的字典键。默认值: true
reportUnusedDictionaries: true,
// 报告没有任何地方读取的内容字段。默认值: true
reportUnusedFields: true,
// 报告在多处声明的重复键。默认值: true
reportDuplicateKeys: true,
// 绝不报告的字段路径正则表达式。
ignoreFields: ["^meta"],
// 扫描起始的项目根目录。默认值: ESLint 的工作目录
baseDir: process.cwd(),
// 项目扫描结果复用时长(毫秒)。默认值: 30000
cacheTtl: 30000,
},
],
}
```
如果你在长期运行的编辑器服务中进行 lint 且希望更快看到修改结果,可以降低 `cacheTtl`;当单次 lint 运行跨越 monorepo 中的多个 Intlayer 项目时,请设置 `baseDir`。
> **倾向于保持沉默。** 此处的误报会导致翻译被误删,因此当字典以分析器无法跟踪的方式被使用时,不会报告任何内容:内容对象被整体传递、从中绑定的翻译函数(`const t = useTranslations("home")`)、通过直接导入访问的声明(`useDictionary(myDictionary)`)、来自另一个字典的 `nest()`、或者因 spread 展开而不详尽的字段列表。单文件组件(`.vue`、`.svelte`、`.astro`)计为使用了它们提及的字典中的所有字段,因为它们的脚本块在此处不会被解析。
`reportDuplicateKeys` 读取构建时写入 `.intlayer/` 下的未合并字典,因此在项目至少构建过一次之前它会保持静默。共享一个键的两个声明会被合并,这是一种合法的模式 — 该报告之所以存在,是因为在两边同时定义的字段会静默保留两个值中的一个。
分析器从以 ESM 形式分发的 `@intlayer/lsp` 中加载。因此该规则需要能够 `require()` ES 模块的 Node 版本 — Node 20.19+ 或 22.12+。在更低版本上,它不会报错中断 lint 运行,而是什么都不报告。
## 框架支持
每条规则均适用于所有 Intlayer 集成,包括 Vue、Svelte 和 Angular 模板内部。你只需告诉 ESLint 哪个解析器负责读取对应的文件类型即可。
| 框架 | 文件 | 解析器 |
| ------------------------- | ----------------- | --------------------------------- |
| React, Preact, Solid, Lit | `.jsx` `.tsx` | `typescript-eslint` |
| Next.js | `.jsx` `.tsx` | `typescript-eslint` |
| Vue, Nuxt | `.vue` | `vue-eslint-parser` |
| Svelte, SvelteKit | `.svelte` | `svelte-eslint-parser` |
| Angular | `.ts` | `typescript-eslint` |
| Angular 模板 | `.component.html` | `@angular-eslint/template-parser` |
| Astro | `.astro` | `astro-eslint-parser` |
```javascript fileName="eslint.config.mjs" codeFormat="esm"
import intlayer from "eslint-plugin-intlayer";
import tseslint from "typescript-eslint";
import vueParser from "vue-eslint-parser";
import svelteParser from "svelte-eslint-parser";
import angularTemplateParser from "@angular-eslint/template-parser";
export default [
...intlayer.configs.recommended,
{
files: ["**/*.{ts,tsx,jsx}"],
languageOptions: { parser: tseslint.parser },
},
{
files: ["**/*.vue"],
languageOptions: {
parser: vueParser,
parserOptions: { parser: tseslint.parser },
},
},
{
files: ["**/*.svelte"],
languageOptions: {
parser: svelteParser,
parserOptions: { parser: tseslint.parser },
},
},
{
files: ["**/*.component.html"],
languageOptions: { parser: angularTemplateParser },
},
];
```
请仅安装项目所需的解析器。
> **已知局限性。** 在 Vue 和 Angular 模板中,类似于 `{{ content[key] }}` 的表达式不会被 `no-dynamic-field-access` 检查。写在 script 块中的动态读取仍会被正常捕获。