ESLint 规则参考

本页面向需要自定义 ESLint 行为的项目。常规接入请先阅读 ESLint

规则总览

官方预设已经注册适用规则。需要调整默认值时,先引入对应预设,再覆盖目标规则。下面以 recommended 为例:

import aiI18n from '@ai-i18n/eslint-plugin';
import { defineConfig } from 'eslint/config';

export default defineConfig([
  ...aiI18n.configs.recommended,
  {
    rules: {
      'ai-i18n/static-candidate-limit': ['warn', { maxStaticCandidates: 2000 }],
    },
  },
]);
规则默认级别范围用途
t-static-argserror通用拒绝无法静态提取或不推荐的翻译调用参数。
no-embedded-markupwarning通用避免让静态 HTML/SVG 结构进入翻译源文。
no-eager-translationwarning通用,Vue 扩展避免在初始化阶段长期保存译后字符串。
no-unsubscribed-runtime-statewarning通用,框架扩展避免缓存或渲染非响应式语言状态快照。
no-unsubscribed-twarningVue、React检查组件渲染和 Vue 响应式翻译 API 的生命周期。
static-candidate-limitwarning通用文案候选数量过大时发出警告。
no-redundant-auto-import默认关闭自动导入项目删除已经由自动导入提供的同名显式导入。

通用规则

t-static-args

检查 t()、Vue tRef()tComputed(),以及 useI18n() 返回的 t。source 必须能在构建 阶段确定;options 必须是仅包含 comment 的静态对象。动态字符串、不支持的调用形式和不推荐的 集合成员引用会报错。

no-embedded-markup

检查 t()、tagged template、Vue tRef()tComputed() 以及 useI18n() 返回的 t。规则分析最终可提取 source;直接字符串、静态 const、条件候选和文案树共用同一口径。

下列写法会收到 warning:

t`<div>温度:<span style="color:${color}">${temp}℃</span></div>`;

处理原则、推荐写法和 HTML 插值的安全边界见 通用文案写法。规则不限制长文案、多行文案、 占位符数量、条件候选或文案树规模,也不检查仅在插值运行时值中出现的 HTML。 第一版不识别 JSON、URL、SQL 或 Shell,也不把纯 Markdown 语法视为 markup。

no-eager-translation

检查模块初始化或组件初始化期间保存的 t() 结果。长期保存的译文不会随语言切换更新。普通 函数和 getter 在执行时重新调用 t(),因此可以使用。Vue setup() 与 Options data() 的 额外检查见下方 Vue Tab。

这条规则不代表文案无法提取,而是提醒“提取成功”和“切换语言后刷新”是两件事。例如图表配置、 路由标题等长生命周期对象,不要在模块加载时保存译文快照:

// warning:模块加载时只翻译一次
export const chartOptions = {
  title: { text: t('销量') },
};

// 允许:在创建或重建图表时读取当前语言
export function createChartOptions() {
  return {
    title: { text: t('销量') },
  };
}

工厂函数需要在首次渲染和语言变化后重新调用;如果调用方仍只在初始化时执行一次,结果仍然不会 刷新。有限的集中式标题集合可以先声明文案,再在实际取值时翻译:

const ROUTE_TITLES = defineI18nMessages({
  detail: '详情',
  edit: '编辑',
});

export function getRouteTitle(name: keyof typeof ROUTE_TITLES) {
  return t(ROUTE_TITLES[name]);
}

Vue setup 中需要长期持有单个译文时使用 tRef();纯 Options API 在根 computed 中使用 tComputed()。模板、render、computed getter、事件处理器和普通延迟函数可以在执行时直接 调用 t()

部分第三方配置允许把文案写成函数,例如表单校验规则。此时优先使用第三方 API 提供的延迟 回调,不要为了消除 warning 改回固定的源语言字面量:

const passwordRules = {
  userPassword: {
    required: true,
    // warning:初始化时保存译文快照
    message: t('请输入旧密码'),
  },
};

const lazyPasswordRules = {
  userPassword: {
    required: true,
    // 允许:校验产生错误时读取当前语言
    message: () => t('请输入旧密码'),
  },
};

只有第三方 API 明确支持函数值时才能使用这种写法;否则使用工厂函数或框架提供的响应式 API。

no-unsubscribed-runtime-state

检查 getLang()getLangLoadState() 返回的快照。模块顶层不能缓存这些值,组件渲染也应 改用框架提供的响应式状态。事件处理器、action、普通工具函数和即时日志可以按需读取。

static-candidate-limit

检查单个翻译调用展开后的 source 与 options 组合。默认超过 1000 个静态候选时发出 warning。 确实存在大型有限集合时,可以提高阈值:

'ai-i18n/static-candidate-limit': [
  'warn',
  { maxStaticCandidates: 2000 },
],

这项设置只影响 ESLint 警告,不改变 Vite 的翻译结果。

no-redundant-auto-import

自动导入预设默认不启用该规则。启用后,它会报告 virtual:ai-i18n 中已经由当前框架自动注入的 同名值导入,并支持 eslint --fix。改名导入、命名空间导入和类型导入会保留;import 内有注释 时只报告,不自动修改。

框架规则

新项目优先使用与 Vite 框架模式一致的预设。下面按框架说明额外检查,并列出自动导入预设的 完整规则配置。框架自动导入 API 见 自动导入

Vanilla
Vue
React

自动导入项目使用 vanilla-auto-import。该预设的规则配置如下:

{
  rules: {
    'ai-i18n/t-static-args': ['error', { autoImport: ['t'] }],
    'ai-i18n/no-embedded-markup': ['warn', { autoImport: ['t'] }],
    'ai-i18n/no-eager-translation': ['warn', { autoImport: ['t'] }],
    'ai-i18n/no-unsubscribed-runtime-state': [
      'warn',
      { autoImport: ['getLang', 'getLangLoadState'] },
    ],
    'ai-i18n/static-candidate-limit': ['warn', { autoImport: ['t'] }],
  },
}

Vanilla 只使用通用规则。它没有组件渲染生命周期,因此 vanilla-auto-import 不启用 no-unsubscribed-t

启用可选的 no-redundant-auto-import 时,传入 Vanilla 注入的完整 Runtime API:

{
  autoImport: [
    't',
    'setLang',
    'getLang',
    'getLangs',
    'getLangLoadState',
    'subscribe',
  ],
}

规则选项

选项适用规则与行为
autoImport六条检查规则接受布尔值或各自支持的 API 数组;no-redundant-auto-import 只接受非空 API 数组。
frameworkno-eager-translation 接受 vueno-unsubscribed-tno-unsubscribed-runtime-state 接受 vuereact
tsconfigPath仅适用于 t-static-argsno-embedded-markupno-eager-translationno-unsubscribed-tstatic-candidate-limit
maxStaticCandidates仅适用于 static-candidate-limit,必须是正整数。

翻译调用规则中的 autoImport: true 是兼容性简写,只启用 tuseI18n,不包含 Vue 专属的 tReftComputed。状态快照规则中的 autoImport: true 会启用 getLanggetLangLoadStatei18nComputed。框架项目应优先使用预设;手动配置时使用 Tabs 中的明确 API 数组。

no-eager-translationno-unsubscribed-tno-unsubscribed-runtime-state 只判断当前 文件内可以确定的生命周期问题,不追踪跨文件 store 数据流。

路径别名与项目配置

项目可以通过共享 settings 显式提供本地源码 alias。显式 alias 拥有最高优先级,适合只在 Vite resolve.alias 中配置别名的 JavaScript 项目:

import { fileURLToPath } from 'node:url';

export default [
  {
    settings: {
      'ai-i18n': {
        alias: {
          '@': fileURLToPath(new URL('./src', import.meta.url)),
        },
      },
    },
  },
];

第一版支持字符串到字符串的对象形式。replacement 必须使用绝对路径,并指向项目本地源码。 暂不支持 Vite 的数组形式、正则 findcustomResolver 或 resolver plugin。ESLint 不会加载 或执行 vite.config.*;需要与 Vite 保持一致时,建议让两份配置导入同一个 alias 对象。

显式 alias 未匹配当前导入时,规则会从正在检查的文件向上寻找最近的 tsconfig.jsonjsconfig.json。同一目录同时存在两者时,优先使用 tsconfig.json。纯 JavaScript 项目可以 直接使用 jsconfig.json

{
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["src/**/*.js", "src/**/*.jsx"]
}

项目使用非标准配置名、项目引用或特殊工作目录时,可以指定入口:

'ai-i18n/t-static-args': [
  'error',
  { tsconfigPath: './tsconfig.json' },
],

tsconfigPath 路径以 ESLint 的工作目录为基准,并覆盖自动发现入口。无论使用 tsconfig.json 还是 jsconfig.json,插件都会解析 extends、项目 references 以及 filesincludeexclude。该选项沿用现有名称,也可以直接指向 jsconfig.json

Vue SFC 必须被所选项目配置的 filesinclude 显式覆盖。例如:

{
  "include": ["src/**/*.ts", "src/**/*.vue"]
}