Repository: intlify/eslint-plugin-vue-i18n
Pull request #583 (2024-12-09 00:32:05)

docs!: Add Versioning Policy

changeset-bot[bot]: ###  ⚠️  No Changeset found

Latest commit: fbd8fff1abbcac5181a0cb4a8ae3707e508c2992

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/intlify/eslint-plugin-vue-i18n/new/versioning?filename=.changeset/dirty-meals-run.md&value=---%0A%22%40intlify%2Feslint-plugin-vue-i18n%22%3A%20patch%0A---%0A%0Adocs!%3A%20Add%20Versioning%20Policy%0A)


cloudflare-workers-and-pages[bot]: ## Deploying eslint-plugin-vue-i18n with &nbsp;<a href="https://pages.dev"><img alt="Cloudflare Pages" src="https://user-images.githubusercontent.com/23264/106598434-9e719e00-654f-11eb-9e59-6167043cfa01.png" width="16"></a> &nbsp;Cloudflare Pages

<table><tr><td><strong>Latest commit:</strong> </td><td>
<code>6c81335</code>
</td></tr>
<tr><td><strong>Status:</strong></td><td>⚡️&nbsp; Build in progress...</td></tr>
</table>

[View logs](https://dash.cloudflare.com/?to=/:account/pages/view/eslint-plugin-vue-i18n/95b07e6d-4859-45fb-b775-03fe649f0fd1)


Patch:
diff --git a/.changeset/afraid-queens-shave.md b/.changeset/afraid-queens-shave.md
new file mode 100644
index 0000000..25fd455
--- /dev/null
+++ b/.changeset/afraid-queens-shave.md
@@ -0,0 +1,5 @@
+---
+"@intlify/eslint-plugin-vue-i18n": major
+---
+
+fix(deps): update dependency @intlify/core-base to v10
diff --git a/.changeset/chilly-bugs-hear.md b/.changeset/chilly-bugs-hear.md
new file mode 100644
index 0000000..b7f27ed
--- /dev/null
+++ b/.changeset/chilly-bugs-hear.md
@@ -0,0 +1,5 @@
+---
+"@intlify/eslint-plugin-vue-i18n": major
+---
+
+fix(deps): update dependency @intlify/message-compiler to v11
diff --git a/.changeset/dirty-meals-run.md b/.changeset/dirty-meals-run.md
new file mode 100644
index 0000000..1aaabb5
--- /dev/null
+++ b/.changeset/dirty-meals-run.md
@@ -0,0 +1,5 @@
+---
+"@intlify/eslint-plugin-vue-i18n": major
+---
+
+docs!: Add Versioning Policy
diff --git a/.changeset/new-ligers-tan.md b/.changeset/new-ligers-tan.md
new file mode 100644
index 0000000..393c531
--- /dev/null
+++ b/.changeset/new-ligers-tan.md
@@ -0,0 +1,5 @@
+---
+"@intlify/eslint-plugin-vue-i18n": major
+---
+
+fix(deps): update dependency @intlify/message-compiler to v10
diff --git a/.changeset/old-islands-admire.md b/.changeset/old-islands-admire.md
new file mode 100644
index 0000000..7aee643
--- /dev/null
+++ b/.changeset/old-islands-admire.md
@@ -0,0 +1,5 @@
+---
+"@intlify/eslint-plugin-vue-i18n": major
+---
+
+ feat!: update config
diff --git a/.changeset/pre.json b/.changeset/pre.json
index 6283743..1141631 100644
--- a/.changeset/pre.json
+++ b/.changeset/pre.json
@@ -4,5 +4,9 @@
   "initialVersions": {
     "@intlify/eslint-plugin-vue-i18n": "3.2.0"
   },
-  "changesets": []
+  "changesets": [
+    "afraid-queens-shave",
+    "new-ligers-tan",
+    "old-islands-admire"
+  ]
 }
diff --git a/.changeset/seven-boxes-call.md b/.changeset/seven-boxes-call.md
new file mode 100644
index 0000000..6938c82
--- /dev/null
+++ b/.changeset/seven-boxes-call.md
@@ -0,0 +1,5 @@
+---
+"@intlify/eslint-plugin-vue-i18n": major
+---
+
+feat!: rename configs
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ae6bcf6..3fbe31e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,15 @@
 # @intlify/eslint-plugin-vue-i18n
 
+## 4.0.0-next.0
+
+### Major Changes
+
+- [#556](https://github.com/intlify/eslint-plugin-vue-i18n/pull/556) [`ccc4737`](https://github.com/intlify/eslint-plugin-vue-i18n/commit/ccc4737e798758179a51dc152ac6e8dada89c33c) Thanks [@renovate](https://github.com/apps/renovate)! - fix(deps): update dependency @intlify/core-base to v10
+
+- [#557](https://github.com/intlify/eslint-plugin-vue-i18n/pull/557) [`baaa5fe`](https://github.com/intlify/eslint-plugin-vue-i18n/commit/baaa5feb9c7dfcb86c46e63e77d2f7d9cb535738) Thanks [@renovate](https://github.com/apps/renovate)! - fix(deps): update dependency @intlify/message-compiler to v10
+
+- [#585](https://github.com/intlify/eslint-plugin-vue-i18n/pull/585) [`13d8975`](https://github.com/intlify/eslint-plugin-vue-i18n/commit/13d8975a78bbc9c07c0c11476b9e0afefd8862e5) Thanks [@ota-meshi](https://github.com/ota-meshi)! - feat!: update config
+
 ## 3.2.0
 
 ### Minor Changes
diff --git a/README.md b/README.md
index 66c485f..f58da71 100644
--- a/README.md
+++ b/README.md
@@ -23,6 +23,17 @@ See [here](https://eslint-plugin-vue-i18n.intlify.dev)
 
 Details changes for each release are documented in the [CHANGELOG.md](https://github.com/intlify/eslint-plugin-vue-i18n/blob/master/CHANGELOG.md).
 
+## 🚥 Versioning policy
+
+This plugin follows [Semantic Versioning].
+However, please note that we do not follow [ESLint's Semantic Versioning Policy].
+In minor version releases, this plugin may change the sharable configs provided by the plugin or the default behavior of the plugin's rules in order to add features to the plugin. Because we want to add many features to the plugin soon, so that users can easily take advantage of new features in Vue I18n, Vue and Nuxt.
+
+According to our policy, any minor update may report more linting errors than the previous release. As such, we recommend using the [tilde (`~`)](https://semver.npmjs.com/#syntax-examples) in `package.json` to guarantee the results of your builds.
+
+[Semantic Versioning]: https://semver.org/
+[ESLint's Semantic Versioning Policy]: https://github.com/eslint/eslint#semantic-versioning-policy
+
 ## ❗ Issues
 
 Please make sure to read the [Issue Reporting Checklist](https://github.com/intlify/eslint-plugin-vue-i18n/blob/master/CONTRIBUTING.md#issue-reporting-guidelines) before opening an issue. Issues not conforming to the guidelines may be closed immediately.
diff --git a/docs/rules/index.md b/docs/rules/index.md
index 10cb434..8ae409c 100644
--- a/docs/rules/index.md
+++ b/docs/rules/index.md
@@ -13,7 +13,7 @@
 | [@intlify/vue-i18n/<wbr>no-deprecated-i18n-places-prop](./no-deprecated-i18n-places-prop.html) | disallow using deprecated `places` prop (Removed in Vue I18n 9.0.0+) | :star: |
 | [@intlify/vue-i18n/<wbr>no-deprecated-modulo-syntax](./no-deprecated-modulo-syntax.html) | enforce modulo interpolation to be named interpolation | :star::black_nib: |
 | [@intlify/vue-i18n/<wbr>no-deprecated-tc](./no-deprecated-tc.html) | disallow using deprecated `tc` or `$tc` (Deprecated in Vue I18n 10.0.0, removed fully in Vue I18n 11.0.0) | :star: |
-| [@intlify/vue-i18n/<wbr>no-deprecated-v-t](./no-deprecated-v-t.html) | disallow using deprecated `v-t` custom directive (Deprecated in Vue I18n 11.0.0, removed fully in Vue I18n 12.0.0) |  |
+| [@intlify/vue-i18n/<wbr>no-deprecated-v-t](./no-deprecated-v-t.html) | disallow using deprecated `v-t` custom directive (Deprecated in Vue I18n 11.0.0, removed fully in Vue I18n 12.0.0) | :star: |
 | [@intlify/vue-i18n/<wbr>no-html-messages](./no-html-messages.html) | disallow use HTML localization messages | :star: |
 | [@intlify/vue-i18n/<wbr>no-i18n-t-path-prop](./no-i18n-t-path-prop.html) | disallow using `path` prop with `<i18n-t>` | :star::black_nib: |
 | [@intlify/vue-i18n/<wbr>no-missing-keys](./no-missing-keys.html) | disallow missing locale message key at localization methods | :star: |
diff --git a/docs/rules/no-deprecated-v-t.md b/docs/rules/no-deprecated-v-t.md
index 33c4cba..87200b7 100644
--- a/docs/rules/no-deprecated-v-t.md
+++ b/docs/rules/no-deprecated-v-t.md
@@ -8,6 +8,8 @@ since: v3.2.0
 
 > disallow using deprecated `v-t` custom directive (Deprecated in Vue I18n 11.0.0, removed fully in Vue I18n 12.0.0)
 
+- :star: The `"extends": "plugin:@intlify/vue-i18n/recommended"` or `*.configs["flat/recommended"]` property in a configuration file enables this rule.
+
 If you are migrating from Vue I18n v10 to v11, `v-t` custom direcitve should be replaced with `t` or `$t`.
 
 ## :book: Rule Details
diff --git a/docs/started.md b/docs/started.md
index e9195d1..6627fb3 100644
--- a/docs/started.md
+++ b/docs/started.md
@@ -1,6 +1,6 @@
 # Getting Started
 
-## :cd: Installation
+## 💿 Installation
 
 Use [npm](https://www.npmjs.com/) or a compatible tool.
 
@@ -15,7 +15,7 @@ npm install --save-dev eslint @intlify/eslint-plugin-vue-i18n
 
 :::
 
-## :rocket: Usage
+## 🚀 Usage
 
 ### Configuration `eslint.config.[c|m]js`
 
@@ -31,7 +31,7 @@ export default [
   // js.configs.recommended, // '@eslint/js'
   // ...vue.configs['flat/recommended'], // 'eslint-plugin-vue'
 
-  ...vueI18n.configs['flat/recommended'],
+  ...vueI18n.configs.recommended,
   {
     rules: {
       // Optional.
@@ -86,8 +86,8 @@ See the [rule list](./rules/index.md) to get the `configs` & `rules` that this p
 
 This plugin provides some predefined configs. You can use the following configs by adding them to `eslint.config.[c|m]js`. (All flat configs in this plugin are provided as arrays, so spread syntax is required when combining them with other configs.)
 
-- `*.configs["flat/base"]`: Settings and rules to enable correct ESLint parsing.
-- `*.configs["flat/recommended"]`: Above, plus rules to enforce subjective community defaults to ensure consistency.
+- `*.configs.base`: Settings and rules to enable correct ESLint parsing.
+- `*.configs.recommended`: Above, plus rules to enforce subjective community defaults to ensure consistency.
 
 ### Configuration `.eslintrc.*`
 
@@ -98,11 +98,11 @@ Example `.eslintrc.js`:
 For example:
 
 ```js
-module.export = {
+module.exports = {
   extends: [
     'eslint:recommended',
     // Recommended
-    'plugin:@intlify/vue-i18n/recommended'
+    'plugin:@intlify/vue-i18n/recommended-legacy'
   ],
   rules: {
     // Optional.
@@ -156,8 +156,8 @@ See the [rule list](./rules/index.md) to get the `configs` & `rules` that this p
 
 This plugin provides some predefined configs. You can use the following configs by adding them to `.eslintrc.*`.
 
-- `"plugin:@intlify/vue-i18n/base"`: Settings and rules to enable correct ESLint parsing.
-- `"plugin:@intlify/vue-i18n/recommended"`: Above, plus rules to enforce subjective community defaults to ensure consistency.
+- `"plugin:@intlify/vue-i18n/base-legacy"`: Settings and rules to enable correct ESLint parsing.
+- `"plugin:@intlify/vue-i18n/recommended-legacy"`: Above, plus rules to enforce subjective community defaults to ensure consistency.
 
 ### `settings['vue-i18n']`
 
@@ -201,24 +201,21 @@ eslint "src/**/*.{js,vue,json}"
 
 If you want to use custom parsers such as [babel-eslint](https://www.npmjs.com/package/babel-eslint) or [typescript-eslint-parser](https://www.npmjs.com/package/typescript-eslint-parser), you have to use `parserOptions.parser` option instead of `parser` option. Because this plugin requires [vue-eslint-parser](https://www.npmjs.com/package/vue-eslint-parser) to parse `.vue` files, so this plugin doesn't work if you overwrote `parser` option.
 
-Also, `parserOptions` configured at the top level affect `.json` and `.yaml`. This plugin needs to use special parsers to parse `.json` and `.yaml`, so to correctly parse each extension, use the `overrides` option and overwrite the options again.
-
 ```diff
-- "parser": "babel-eslint",
-  "parserOptions": {
-+     "parser": "babel-eslint",
+import vueEslintParser from "vue-eslint-parser"
+import babelEslint from "babel-eslint"
+
+export default {
+  "files": ["**/*.vue"],
+  "languageOptions": {
+-   "parser": babelEslint,
++   "parser": vueEslintParser,
+    "parserOptions": {
++     "parser": babelEslint,
       "sourceType": "module"
-  },
-+ "overrides": [
-+     {
-+         "files": ["*.json", "*.json5"],
-+         "extends": ["plugin:@intlify/vue-i18n/base"],
-+     },
-+     {
-+         "files": ["*.yaml", "*.yml"],
-+         "extends": ["plugin:@intlify/vue-i18n/base"],
-+     }
-+ ]
+    },
+  }
+}
 ```
 
 ### More lint on JSON and YAML in `<i18n>` block
@@ -227,40 +224,40 @@ You can install [eslint-plugin-jsonc](https://ota-meshi.github.io/eslint-plugin-
 
 You can also use [jsonc/vue-custom-block/no-parsing-error](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/vue-custom-block/no-parsing-error.html) and [yml/vue-custom-block/no-parsing-error](https://ota-meshi.github.io/eslint-plugin-yml/rules/vue-custom-block/no-parsing-error.html) rules to find JSON and YAML parsing errors.
 
-## :question: FAQ
+## 🚥 Versioning policy
 
-### What is the "Use the latest vue-eslint-parser" error?
+This plugin follows [Semantic Versioning].
+However, please note that we do not follow [ESLint's Semantic Versioning Policy].
+In minor version releases, this plugin may change the sharable configs provided by the plugin or the default behavior of the plugin's rules in order to add features to the plugin. Because we want to add many features to the plugin soon, so that users can easily take advantage of new features in Vue I18n, Vue and Nuxt.
 
-The most rules of `eslint-plugin-vue-i18n` require `vue-eslint-parser` to check `<template>` ASTs.
+According to our policy, any minor update may report more linting errors than the previous release. As such, we recommend using the [tilde (`~`)](https://semver.npmjs.com/#syntax-examples) in `package.json` to guarantee the results of your builds.
 
-Make sure you have one of the following settings in your **.eslintrc**:
+[Semantic Versioning]: https://semver.org/
+[ESLint's Semantic Versioning Policy]: https://github.com/eslint/eslint#semantic-versioning-policy
 
-- `"extends": ["plugin:@intlify/vue-i18n/recommended"]`
-- `"extends": ["plugin:@intlify/vue-i18n/base"]`
+## ❓ FAQ
 
-If you already use other parser (e.g. `"parser": "babel-eslint"`), please move it into `parserOptions`, so it doesn't collide with the `vue-eslint-parser` used by this plugin's configuration:
+### What is the "Use the latest vue-eslint-parser" error?
 
-```diff
-- "parser": "babel-eslint",
-  "parserOptions": {
-+     "parser": "babel-eslint",
-      "ecmaVersion": 2017,
-      "sourceType": "module"
-  }
-```
+The most rules of `eslint-plugin-vue-i18n` require `vue-eslint-parser` to check `<template>` ASTs.
 
-See also: "[Use together with custom parsers](#use-together-with-custom-parsers)" section.
+Make sure you have one of the following settings in your **eslint.config.js**:
+
+- `plugin.configs.base`
+- `plugin.configs.recommended`
+
+See also: "[How to use custom parser](#how-to-use-custom-parser)" section.
 
 ### Why doesn't it work on .vue file?
 
 1. Make sure you don't have `eslint-plugin-html` in your config. The `eslint-plugin-html` extracts the content from `<script>` tags, but `eslint-plugin-vue` requires `<script>` tags and `<template>` tags in order to distinguish template and script in single file components.
 
-```diff
-  "plugins": [
-    "vue",
--   "html"
-  ]
-```
+   ```diff
+     "plugins": [
+       "vue",
+   -   "html"
+     ]
+   ```
 
 2. Make sure your tool is set to lint `.vue` and `.json` files.
 
diff --git a/lib/configs/flat/recommended.ts b/lib/configs/flat/recommended.ts
index f1e538a..280d1f9 100644
--- a/lib/configs/flat/recommended.ts
+++ b/lib/configs/flat/recommended.ts
@@ -24,6 +24,7 @@ export = [
       '@intlify/vue-i18n/no-deprecated-i18n-places-prop': 'warn',
       '@intlify/vue-i18n/no-deprecated-modulo-syntax': 'warn',
       '@intlify/vue-i18n/no-deprecated-tc': 'warn',
+      '@intlify/vue-i18n/no-deprecated-v-t': 'warn',
       '@intlify/vue-i18n/no-html-messages': 'warn',
       '@intlify/vue-i18n/no-i18n-t-path-prop': 'warn',
       '@intlify/vue-i18n/no-missing-keys': 'warn',
diff --git a/lib/configs/recommended.ts b/lib/configs/recommended.ts
index ababfea..f56b0d2 100644
--- a/lib/configs/recommended.ts
+++ b/lib/configs/recommended.ts
@@ -18,6 +18,7 @@ export = {
     '@intlify/vue-i18n/no-deprecated-i18n-places-prop': 'warn',
     '@intlify/vue-i18n/no-deprecated-modulo-syntax': 'warn',
     '@intlify/vue-i18n/no-deprecated-tc': 'warn',
+    '@intlify/vue-i18n/no-deprecated-v-t': 'warn',
     '@intlify/vue-i18n/no-html-messages': 'warn',
     '@intlify/vue-i18n/no-i18n-t-path-prop': 'warn',
     '@intlify/vue-i18n/no-missing-keys': 'warn',
diff --git a/lib/index.ts b/lib/index.ts
index 75ef61b..cc7d749 100644
--- a/lib/index.ts
+++ b/lib/index.ts
@@ -1,9 +1,9 @@
 /** DON'T EDIT THIS FILE; was created by scripts. */
 // configs
-import base from './configs/base'
-import recommended from './configs/recommended'
-import flatBase from './configs/flat/base'
-import flatRecommended from './configs/flat/recommended'
+import baseLegacy from './configs/base'
+import recommendedLegacy from './configs/recommended'
+import base from './configs/flat/base'
+import recommended from './configs/flat/recommended'
 
 // rules
 import keyFormatStyle from './rules/key-format-style'
@@ -32,12 +32,14 @@ import validMessageSyntax from './rules/valid-message-syntax'
 export = {
   configs: {
     // eslintrc configs
-    base,
-    recommended,
+    'base-legacy': baseLegacy,
+    'recommended-legacy': recommendedLegacy,
 
     // flat configs
-    'flat/base': flatBase,
-    'flat/recommended': flatRecommended
+    base,
+    recommended,
+    'flat/base': base,
+    'flat/recommended': recommended
   },
   rules: {
     'key-format-style': keyFormatStyle,
diff --git a/lib/rules/no-deprecated-modulo-syntax.ts b/lib/rules/no-deprecated-modulo-syntax.ts
index 59e237f..3e5e56c 100644
--- a/lib/rules/no-deprecated-modulo-syntax.ts
+++ b/lib/rules/no-deprecated-modulo-syntax.ts
@@ -13,7 +13,7 @@ import {
   getMessageSyntaxVersions,
   NodeTypes
 } from '../utils/message-compiler/utils'
-import { parse } from '../utils/message-compiler/parser'
+import { parse } from '../utils/message-compiler/parser-v9'
 import { traverseNode } from '../utils/message-compiler/traverser'
 import {
   createRule,
@@ -75,8 +75,6 @@ function create(context: RuleContext): RuleListener {
     }
     if (messageSyntaxVersions.v9) {
       verifyForV9(message, reportNode, getReportOffset)
-    } else if (messageSyntaxVersions.v8) {
-      return
     }
   }
 
diff --git a/lib/rules/no-deprecated-v-t.ts b/lib/rules/no-deprecated-v-t.ts
index f34fae9..9485a30 100644
--- a/lib/rules/no-deprecated-v-t.ts
+++ b/lib/rules/no-deprecated-v-t.ts
@@ -34,7 +34,7 @@ export = createRule({
         'disallow using deprecated `v-t` custom directive (Deprecated in Vue I18n 11.0.0, removed fully in Vue I18n 12.0.0)',
       category: 'Recommended',
       url: 'https://eslint-plugin-vue-i18n.intlify.dev/rules/no-deprecated-v-t.html',
-      recommended: false
+      recommended: true
     },
     fixable: null,
     schema: []
diff --git a/lib/rules/prefer-linked-key-with-paren.ts b/lib/rules/prefer-linked-key-with-paren.ts
index b22685f..4ca8d7f 100644
--- a/lib/rules/prefer-linked-key-with-paren.ts
+++ b/lib/rules/prefer-linked-key-with-paren.ts
@@ -42,7 +42,7 @@ function create(context: RuleContext): RuleListener {
   const sourceCode = getSourceCode(context)
   const messageSyntaxVersions = getMessageSyntaxVersions(context)
 
-  function verifyForV9(
+  function verifyForNewSyntax(
     message: string,
     reportNode: JSONAST.JSONStringLiteral | YAMLAST.YAMLScalar,
     getReportOffset: GetReportOffset
@@ -134,14 +134,16 @@ function create(context: RuleContext): RuleListener {
     if (messageSyntaxVersions.reportIfMissingSetting()) {
       return
     }
-    if (messageSyntaxVersions.v9 && messageSyntaxVersions.v8) {
+    const newSyntax = messageSyntaxVersions.v9 || messageSyntaxVersions.v10
+    const v8Syntax = messageSyntaxVersions.v8
+    if (newSyntax && v8Syntax) {
       // This rule cannot support two versions in the same project.
       return
     }
 
-    if (messageSyntaxVersions.v9) {
-      verifyForV9(message, reportNode, getReportOffset)
-    } else if (messageSyntaxVersions.v8) {
+    if (newSyntax) {
+      verifyForNewSyntax(message, reportNode, getReportOffset)
+    } else if (v8Syntax) {
       verifyForV8(message, reportNode, getReportOffset)
     }
   }
diff --git a/lib/rules/valid-message-syntax.ts b/lib/rules/valid-message-syntax.ts
index f64a5ef..e7b5f4a 100644
--- a/lib/rules/valid-message-syntax.ts
+++ b/lib/rules/valid-message-syntax.ts
@@ -14,6 +14,7 @@ import {
   getReportIndex
 } from '../utils/message-compiler/utils'
 import { parse } from '../utils/message-compiler/parser'
+import { parse as parseForV9 } from '../utils/message-compiler/parser-v9'
 import { parse as parseForV8 } from '../utils/message-compiler/parser-v8'
 import type { CompileError } from '@intlify/message-compiler'
 import { createRule } from '../utils/rule'
@@ -27,9 +28,16 @@ function create(context: RuleContext): RuleListener {
   const messageSyntaxVersions = getMessageSyntaxVersions(context)
 
   function* extractMessageErrors(message: string) {
-    if (messageSyntaxVersions.v9) {
-      yield* parse(message).errors
+    // v10 and v9 generate nearly identical errors so only one of them will be returned.
+    const errorsForV10OrV9: CompileError[] = []
+    if (messageSyntaxVersions.v10) {
+      errorsForV10OrV9.push(...parse(message).errors)
     }
+    if (messageSyntaxVersions.v9 && !errorsForV10OrV9.length) {
+      errorsForV10OrV9.push(...parseForV9(message).errors)
+    }
+    yield* errorsForV10OrV9
+
     if (messageSyntaxVersions.v8) {
       yield* parseForV8(message).errors
     }
diff --git a/lib/utils/collect-linked-keys.ts b/lib/utils/collect-linked-keys.ts
index 9221dee..82fb75a 100644
--- a/lib/utils/collect-linked-keys.ts
+++ b/lib/utils/collect-linked-keys.ts
@@ -6,6 +6,7 @@ import type { ResourceNode } from '@intlify/message-compiler'
 import { traverseNode } from './message-compiler/traverser'
 import type { I18nLocaleMessageDictionary, RuleContext } from '../types'
 import { parse } from './message-compiler/parser'
+import { parse as parseForV9 } from './message-compiler/parser-v9'
 import { parse as parseForV8 } from './message-compiler/parser-v8'
 import type { MessageSyntaxVersions } from './message-compiler/utils'
 import { NodeTypes } from './message-compiler/utils'
@@ -27,9 +28,12 @@ function* extractUsedKeysFromLinks(
     if (typeof value === 'object') {
       yield* extractUsedKeysFromLinks(value, messageSyntaxVersions)
     } else if (typeof value === 'string') {
-      if (messageSyntaxVersions.v9) {
+      if (messageSyntaxVersions.v10) {
         yield* extractUsedKeysFromAST(parse(value).ast)
       }
+      if (messageSyntaxVersions.v9) {
+        yield* extractUsedKeysFromAST(parseForV9(value).ast)
+      }
       if (messageSyntaxVersions.v8) {
         yield* extractUsedKeysFromAST(parseForV8(value).ast)
       }
diff --git a/lib/utils/message-compiler/parser-v8.ts b/lib/utils/message-compiler/parser-v8.ts
index 100cc62..bce8627 100644
--- a/lib/utils/message-compiler/parser-v8.ts
+++ b/lib/utils/message-compiler/parser-v8.ts
@@ -17,6 +17,7 @@ import type {
 } from '@intlify/message-compiler'
 import { sortedLastIndex } from 'lodash'
 import { NodeTypes } from './utils'
+import type { ModuloNamedNode } from './parser-v9'
 
 export function parse(code: string): {
   ast: ResourceNode
@@ -207,7 +208,7 @@ function parseAST(code: string, errors: CompileError[]): ResourceNode {
           node = listNode
         }
         if (!node) {
-          const namedNode: NamedNode = {
+          const namedNode: ModuloNamedNode = {
             type: NodeTypes.Named,
             key: trimmedKeyValue,
             ...ctx.getNodeLoc(endOffset - 1, placeholderEndOffset)
diff --git a/lib/utils/message-compiler/parser-v9.ts b/lib/utils/message-compiler/parser-v9.ts
new file mode 100644
index 0000000..68f1ea6
--- /dev/null
+++ b/lib/utils/message-compiler/parser-v9.ts
@@ -0,0 +1,59 @@
+/**
+ * A simplified version of the message parser that handles messages like vue-i18n v8.
+ * This parser probably has poor performance.
+ */
+import type {
+  CompileError,
+  NamedNode,
+  ResourceNode
+} from '@intlify/message-compiler'
+import { NodeTypes } from './utils'
+import { parse as baseParse } from './parser'
+import type { MessageElementNode } from './traverser'
+import { traverseNode } from './traverser'
+
+// The deprecated Rails i18n format.
+export type ModuloNamedNode = NamedNode & { modulo?: boolean }
+
+export function parse(code: string): {
+  ast: ResourceNode
+  errors: CompileError[]
+} {
+  const { ast, errors } = baseParse(code)
+
+  traverseNode(ast, node => {
+    if (node.type === NodeTypes.Message) {
+      transformModuloNamedNode(node.items)
+    }
+  })
+  return {
+    ast,
+    errors
+  }
+
+  function transformModuloNamedNode(nodes: MessageElementNode[]) {
+    // Converts nodes with a '%' before the brackets into modulo nodes.
+    for (let index = nodes.length - 1; index >= 1; index--) {
+      const node = nodes[index]
+      if (
+        node.type !== NodeTypes.Named ||
+        code[node.loc!.start.offset - 1] !== '%'
+      )
+        continue
+
+      const prev = nodes[index - 1]
+      if (prev.type !== NodeTypes.Text || !prev.value?.endsWith('%')) continue
+
+      node.modulo = true
+
+      prev.loc!.end.offset -= 1
+      prev.loc!.end.column -= 1
+      prev.end! -= 1
+      prev.value = prev.value!.slice(0, -1)
+      if (prev.start === prev.end) {
+        nodes.splice(index - 1, 1)
+        index--
+      }
+    }
+  }
+}
diff --git a/lib/utils/message-compiler/traverser.ts b/lib/utils/message-compiler/traverser.ts
index 84ee1ab..360d558 100644
--- a/lib/utils/message-compiler/traverser.ts
+++ b/lib/utils/message-compiler/traverser.ts
@@ -11,13 +11,15 @@ import type {
   TextNode
 } from '@intlify/message-compiler'
 import { NodeTypes } from './utils'
+import type { ModuloNamedNode } from './parser-v9'
 
-type MessageElementNode =
+export type MessageElementNode =
   | TextNode
   | NamedNode
   | ListNode
   | LiteralNode
   | LinkedNode
+  | ModuloNamedNode
 type MessageASTNode =
   | ResourceNode
   | PluralNode
diff --git a/lib/utils/message-compiler/utils.ts b/lib/utils/message-compiler/utils.ts
index e2bdfa1..24a7e56 100644
--- a/lib/utils/message-compiler/utils.ts
+++ b/lib/utils/message-compiler/utils.ts
@@ -19,6 +19,7 @@ export const NodeTypes = {
 export type MessageSyntaxVersions = {
   v8: boolean
   v9: boolean
+  v10: boolean
   isNotSet: boolean
   reportIfMissingSetting: () => boolean
 }
@@ -37,6 +38,7 @@ export function getMessageSyntaxVersions(
     return {
       v8: true,
       v9: true,
+      v10: true,
       isNotSet: true,
       reportIfMissingSetting: () => {
         if (!puttedSettingsError.has(context)) {
@@ -54,7 +56,8 @@ export function getMessageSyntaxVersions(
   const range = new Range(messageSyntaxVersion)
   return {
     v8: intersects(range, '^8.0.0 || <=8.0.0'),
-    v9: intersects(range, '>=9.0.0-0'),
+    v9: intersects(range, '^9.0.0-0'),
+    v10: intersects(range, '>=10.0.0-0'),
     isNotSet: false,
     reportIfMissingSetting: () => false
   }
diff --git a/package.json b/package.json
index d022bda..42ebb2b 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "@intlify/eslint-plugin-vue-i18n",
   "description": "ESLint plugin for Vue I18n",
-  "version": "3.2.0",
+  "version": "4.0.0-next.0",
   "license": "MIT",
   "homepage": "https://github.com/intlify/eslint-plugin-vue-i18n#readme",
   "keywords": [
@@ -29,7 +29,7 @@
   "engines": {
     "node": ">=18.0.0"
   },
-  "packageManager": "pnpm@9.14.4",
+  "packageManager": "pnpm@9.15.1",
   "type": "commonjs",
   "files": [
     "dist"
@@ -60,13 +60,13 @@
   },
   "dependencies": {
     "@eslint/eslintrc": "^3.0.0",
-    "@intlify/core-base": "^9.12.0",
-    "@intlify/message-compiler": "^9.12.0",
+    "@intlify/core-base": "^11.0.0",
+    "@intlify/message-compiler": "^11.0.0",
     "debug": "^4.3.4",
     "eslint-compat-utils": "^0.6.0",
     "glob": "^10.3.3",
     "globals": "^15.0.0",
-    "ignore": "^6.0.0",
+    "ignore": "^7.0.0",
     "import-fresh": "^3.3.0",
     "is-language-code": "^3.1.0",
     "js-yaml": "^4.1.0",
@@ -95,7 +95,7 @@
     "@types/semver": "^7.5.0",
     "@typescript-eslint/eslint-plugin": "^7.0.0",
     "@typescript-eslint/parser": "^7.0.0",
-    "entities": "^5.0.0",
+    "entities": "^6.0.0",
     "env-cmd": "^10.1.0",
     "eslint": "^9.4.0",
     "eslint-config-prettier": "^9.0.0",
@@ -110,7 +110,7 @@
     "lint-staged": "^15.0.0",
     "mocha": "^11.0.0",
     "monaco-editor": "^0.52.0",
-    "npm-run-all2": "7.0.1",
+    "npm-run-all2": "7.0.2",
     "nyc": "^17.0.0",
     "opener": "^1.5.2",
     "path-scurry": "^1.10.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 508174f..1e88705 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -12,11 +12,11 @@ importers:
         specifier: ^3.0.0
         version: 3.0.0
       '@intlify/core-base':
-        specifier: ^9.12.0
-        version: 9.14.2
+        specifier: ^11.0.0
+        version: 11.0.0
       '@intlify/message-compiler':
-        specifier: ^9.12.0
-        version: 9.12.0
+        specifier: ^11.0.0
+        version: 11.0.0
       debug:
         specifier: ^4.3.4
         version: 4.3.4
@@ -30,8 +30,8 @@ importers:
         specifier: ^15.0.0
         version: 15.0.0
       ignore:
-        specifier: ^6.0.0
-        version: 6.0.1
+        specifier: ^7.0.0
+        version: 7.0.0
       import-fresh:
         specifier: ^3.3.0
         version: 3.3.0
@@ -112,8 +112,8 @@ importers:
         specifier: ^7.0.0
         version: 7.11.0(eslint@9.4.0)(typescript@5.1.6)
       entities:
-        specifier: ^5.0.0
-        version: 5.0.0
+        specifier: ^6.0.0
+        version: 6.0.0
       env-cmd:
         specifier: ^10.1.0
         version: 10.1.0
@@ -157,8 +157,8 @@ importers:
         specifier: ^0.52.0
         version: 0.52.0
       npm-run-all2:
-        specifier: 7.0.1
-        version: 7.0.1
+        specifier: 7.0.2
+        version: 7.0.2
       nyc:
         specifier: ^17.0.0
         version: 17.0.0
@@ -646,24 +646,16 @@ packages:
     resolution: {integrity: sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==}
     engines: {node: '>=18.18'}
 
-  '@intlify/core-base@9.14.2':
-    resolution: {integrity: sha512-DZyQ4Hk22sC81MP4qiCDuU+LdaYW91A6lCjq8AWPvY3+mGMzhGDfOCzvyR6YBQxtlPjFqMoFk9ylnNYRAQwXtQ==}
+  '@intlify/core-base@11.0.0':
+    resolution: {integrity: sha512-sbtC0hVOEr4ZAYSffQoj362inaKpb4sjgtLHVGt4tbfP+YjnHO6eUDlHgAIEESch+Kffmsc7Z+l1l2sV9oJZQQ==}
     engines: {node: '>= 16'}
 
-  '@intlify/message-compiler@9.12.0':
-    resolution: {integrity: sha512-2c6VwhvVJ1nur+2cN2NjdrmrV6vXjvyxYVvtUYMXKsWSUwoNURHGds0xJVJmWxbF8qV9oGepcVV6xl9bvadEIg==}
+  '@intlify/message-compiler@11.0.0':
+    resolution: {integrity: sha512-teYbQMdZisn19KsKj/jvRIFN72Lyxd1VY69TLhhXd9wDgZMlyEVLLLdLb0wDLfbBP8uSda9vKmxYh+q8Xa5sZQ==}
     engines: {node: '>= 16'}
 
-  '@intlify/message-compiler@9.14.2':
-    resolution: {integrity: sha512-YsKKuV4Qv4wrLNsvgWbTf0E40uRv+Qiw1BeLQ0LAxifQuhiMe+hfTIzOMdWj/ZpnTDj4RSZtkXjJM7JDiiB5LQ==}
-    engines: {node: '>= 16'}
-
-  '@intlify/shared@9.12.0':
-    resolution: {integrity: sha512-uBcH55x5CfZynnerWHQxrXbT6yD6j6T7Nt+R2+dHAOAneoMd6BoGvfEzfYscE94rgmjoDqdr+PdGDBLk5I5EjA==}
-    engines: {node: '>= 16'}
-
-  '@intlify/shared@9.14.2':
-    resolution: {integrity: sha512-uRAHAxYPeF+G5DBIboKpPgC/Waecd4Jz8ihtkpJQD5ycb5PwXp0k/+hBGl5dAjwF7w+l74kz/PKA8r8OK//RUw==}
+  '@intlify/shared@11.0.0':
+    resolution: {integrity: sha512-MLKIyFz37qYF1G5UQZMwGoJyp8DD3rtcXDplRHZdSOmbfOYbD4g9kJZ6qCsm1Ru3vee6CTnJcbO4LV1JeIDGbA==}
     engines: {node: '>= 16'}
 
   '@isaacs/cliui@8.0.2':
@@ -1338,6 +1330,10 @@ packages:
     resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
     engines: {node: '>= 8'}
 
+  cross-spawn@7.0.6:
+    resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
+    engines: {node: '>= 8'}
+
   cssesc@3.0.0:
     resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
     engines: {node: '>=4'}
@@ -1453,8 +1449,8 @@ packages:
     resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
     engines: {node: '>=0.12'}
 
-  entities@5.0.0:
-    resolution: {integrity: sha512-BeJFvFRJddxobhvEdm5GqHzRV/X+ACeuw0/BuuxsCh1EUZcAIz8+kYmBp/LrQuloy6K1f3a0M7+IhmZ7QnkISA==}
+  entities@6.0.0:
+    resolution: {integrity: sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==}
     engines: {node: '>=0.12'}
 
   env-cmd@10.1.0:
@@ -1905,10 +1901,9 @@ packages:
     resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
     engines: {node: '>= 4'}
 
-  ignore@6.0.1:
-    resolution: {integrity: sha512-9hCx6FGveEYzwsldacntlq0RdPsTjOAALVL4nqi1O8JU6OIzzchHELMNE9f+6ZMtuHG1vd+owvczaMhu6EM2Xw==}
+  ignore@7.0.0:
+    resolution: {integrity: sha512-lcX8PNQygAa22u/0BysEY8VhaFRzlOkvdlKczDPnJvrkJD1EuqzEky5VYYKM2iySIuaVIDv9N190DfSreSLw2A==}
     engines: {node: '>= 4'}
-    deprecated: this package has been deprecated due to issues
 
   import-fresh@3.3.0:
     resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
@@ -2362,8 +2357,8 @@ packages:
   ms@2.1.3:
     resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
 
-  nanoid@3.3.7:
-    resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
+  nanoid@3.3.8:
+    resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==}
     engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
     hasBin: true
 
@@ -2397,8 +2392,8 @@ packages:
     resolution: {integrity: sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==}
     engines: {node: ^18.17.0 || >=20.5.0}
 
-  npm-run-all2@7.0.1:
-    resolution: {integrity: sha512-Adbv+bJQ8UTAM03rRODqrO5cx0YU5KCG2CvHtSURiadvdTjjgGJXdbc1oQ9CXBh9dnGfHSoSB1Web/0Dzp6kOQ==}
+  npm-run-all2@7.0.2:
+    resolution: {integrity: sha512-7tXR+r9hzRNOPNTvXegM+QzCuMjzUIIq66VDunL6j60O4RrExx32XUhlrS7UK4VcdGw5/Wxzb3kfNcFix9JKDA==}
     engines: {node: ^18.17.0 || >=20.5.0, npm: '>= 9'}
     hasBin: true
 
@@ -2791,10 +2786,6 @@ packages:
     engines: {node: '>=6'}
     hasBin: true
 
-  source-map-js@1.2.0:
-    resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
-    engines: {node: '>=0.10.0'}
-
   source-map-js@1.2.1:
     resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
     engines: {node: '>=0.10.0'}
@@ -3847,24 +3838,17 @@ snapshots:
 
   '@humanwhocodes/retry@0.3.0': {}
 
-  '@intlify/core-base@9.14.2':
-    dependencies:
-      '@intlify/message-compiler': 9.14.2
-      '@intlify/shared': 9.14.2
-
-  '@intlify/message-compiler@9.12.0':
+  '@intlify/core-base@11.0.0':
     dependencies:
-      '@intlify/shared': 9.12.0
-      source-map-js: 1.2.0
+      '@intlify/message-compiler': 11.0.0
+      '@intlify/shared': 11.0.0
 
-  '@intlify/message-compiler@9.14.2':
+  '@intlify/message-compiler@11.0.0':
     dependencies:
-      '@intlify/shared': 9.14.2
+      '@intlify/shared': 11.0.0
       source-map-js: 1.2.1
 
-  '@intlify/shared@9.12.0': {}
-
-  '@intlify/shared@9.14.2': {}
+  '@intlify/shared@11.0.0': {}
 
   '@isaacs/cliui@8.0.2':
     dependencies:
@@ -4558,6 +4542,12 @@ snapshots:
       shebang-command: 2.0.0
       which: 2.0.2
 
+  cross-spawn@7.0.6:
+    dependencies:
+      path-key: 3.1.1
+      shebang-command: 2.0.0
+      which: 2.0.2
+
   cssesc@3.0.0: {}
 
   csstype@3.1.3: {}
@@ -4643,7 +4633,7 @@ snapshots:
 
   entities@4.5.0: {}
 
-  entities@5.0.0: {}
+  entities@6.0.0: {}
 
   env-cmd@10.1.0:
     dependencies:
@@ -5192,7 +5182,7 @@ snapshots:
 
   ignore@5.3.1: {}
 
-  ignore@6.0.1: {}
+  ignore@7.0.0: {}
 
   import-fresh@3.3.0:
     dependencies:
@@ -5660,7 +5650,7 @@ snapshots:
 
   ms@2.1.3: {}
 
-  nanoid@3.3.7: {}
+  nanoid@3.3.8: {}
 
   natural-compare@1.4.0: {}
 
@@ -5685,10 +5675,10 @@ snapshots:
 
   npm-normalize-package-bin@4.0.0: {}
 
-  npm-run-all2@7.0.1:
+  npm-run-all2@7.0.2:
     dependencies:
       ansi-styles: 6.2.1
-      cross-spawn: 7.0.3
+      cross-spawn: 7.0.6
       memorystream: 0.3.1
       minimatch: 9.0.4
       pidtree: 0.6.0
@@ -5880,13 +5870,13 @@ snapshots:
 
   postcss@8.4.38:
     dependencies:
-      nanoid: 3.3.7
+      nanoid: 3.3.8
       picocolors: 1.0.1
       source-map-js: 1.2.1
 
   postcss@8.4.47:
     dependencies:
-      nanoid: 3.3.7
+      nanoid: 3.3.8
       picocolors: 1.1.0
       source-map-js: 1.2.1
 
@@ -6112,8 +6102,6 @@ snapshots:
       wcwidth: 1.0.1
       yargs: 15.4.1
 
-  source-map-js@1.2.0: {}
-
   source-map-js@1.2.1: {}
 
   source-map@0.6.1: {}
diff --git a/scripts/update-index.ts b/scripts/update-index.ts
index 847355a..12bd5e3 100644
--- a/scripts/update-index.ts
+++ b/scripts/update-index.ts
@@ -9,11 +9,11 @@ export async function update() {
   const raw = `/** DON'T EDIT THIS FILE; was created by scripts. */
 // configs
 ${PRESETS.map(
-  preset => `import ${camelCase(preset)} from './configs/${preset}';`
+  preset =>
+    `import ${camelCase(`${preset}-legacy`)} from './configs/${preset}';`
 ).join('\n')}
 ${PRESETS.map(
-  preset =>
-    `import ${camelCase(`flat-${preset}`)} from './configs/flat/${preset}';`
+  preset => `import ${camelCase(`${preset}`)} from './configs/flat/${preset}';`
 ).join('\n')}
 
 // rules
@@ -25,12 +25,12 @@ ${ruleNames
 export = {
   configs: {
     // eslintrc configs
-    ${PRESETS.map(preset => `'${preset}': ${camelCase(preset)},`).join('\n')}
+    ${PRESETS.map(preset => `'${preset}-legacy': ${camelCase(`${preset}-legacy`)},`).join('\n')}
 
     // flat configs
-    ${PRESETS.map(
-      preset => `'flat/${preset}': ${camelCase(`flat-${preset}`)},`
-    ).join('\n')}
+    ${PRESETS.map(preset => `'${preset}': ${camelCase(preset)},`).join('\n')}
+    // flat configs (Backward compatibility)
+    ${PRESETS.map(preset => `'flat/${preset}': ${camelCase(preset)},`).join('\n')}
   },
   rules: {
     ${ruleNames


Test patch:
diff --git a/tests/integrations/flat-config/eslint.config.js b/tests/integrations/flat-config/eslint.config.js
index ae73e5d..1b7ca8f 100644
--- a/tests/integrations/flat-config/eslint.config.js
+++ b/tests/integrations/flat-config/eslint.config.js
@@ -3,7 +3,7 @@ import vueI18n from '@intlify/eslint-plugin-vue-i18n'
 
 export default [
   ...vue.configs['flat/recommended'],
-  ...vueI18n.configs['flat/recommended'],
+  ...vueI18n.configs.recommended,
   {
     rules: {
       'vue/multi-word-component-names': 'off'
diff --git a/tests/integrations/legacy-config/.eslintrc.cjs b/tests/integrations/legacy-config/.eslintrc.cjs
index cfe0e2f..ca9f04f 100644
--- a/tests/integrations/legacy-config/.eslintrc.cjs
+++ b/tests/integrations/legacy-config/.eslintrc.cjs
@@ -2,7 +2,10 @@
 
 module.exports = {
   root: true,
-  extends: ['plugin:vue/recommended', 'plugin:@intlify/vue-i18n/recommended'],
+  extends: [
+    'plugin:vue/recommended',
+    'plugin:@intlify/vue-i18n/recommended-legacy'
+  ],
   rules: {
     'vue/multi-word-component-names': 'off'
   },
diff --git a/tests/lib/rules/no-deprecated-modulo-syntax.ts b/tests/lib/rules/no-deprecated-modulo-syntax.ts
index 448d0b9..bc4fae5 100644
--- a/tests/lib/rules/no-deprecated-modulo-syntax.ts
+++ b/tests/lib/rules/no-deprecated-modulo-syntax.ts
@@ -18,7 +18,7 @@ const tester = new RuleTester({
   languageOptions: { parser: vueParser, ecmaVersion: 2015 }
 })
 
-tester.run('no-deprecated-module-syntax', rule as never, {
+tester.run('no-deprecated-modulo-syntax', rule as never, {
   valid: [
     // text only
     {
diff --git a/tests/lib/rules/valid-message-syntax.ts b/tests/lib/rules/valid-message-syntax.ts
index 40c7da5..4b1195f 100644
--- a/tests/lib/rules/valid-message-syntax.ts
+++ b/tests/lib/rules/valid-message-syntax.ts
@@ -223,24 +223,25 @@ tester.run('valid-message-syntax', rule as never, {
         }
       ]
     },
-    {
-      code: `
-      key: message @:(v8)
-      `,
-      ...options.yaml.default,
-      errors: [
-        {
-          message: `If you want to use '${TEST_RULE_ID_PREFIX}valid-message-syntax' rule, you need to set 'messageSyntaxVersion' at 'settings'. See the 'eslint-plugin-vue-i18n' documentation`,
-          line: 1,
-          column: 1
-        },
-        {
-          message: 'Unexpected empty linked key',
-          line: 2,
-          column: 21
-        }
-      ]
-    },
+    // {
+    //   // The syntax is now allowed.
+    //   code: `
+    //   key: message @:(v8)
+    //   `,
+    //   ...options.yaml.default,
+    //   errors: [
+    //     {
+    //       message: `If you want to use '${TEST_RULE_ID_PREFIX}valid-message-syntax' rule, you need to set 'messageSyntaxVersion' at 'settings'. See the 'eslint-plugin-vue-i18n' documentation`,
+    //       line: 1,
+    //       column: 1
+    //     },
+    //     {
+    //       message: 'Unexpected empty linked key',
+    //       line: 2,
+    //       column: 21
+    //     }
+    //   ]
+    // },
     {
       code: `
       key: message { v9 }
@@ -272,61 +273,65 @@ tester.run('valid-message-syntax', rule as never, {
         }
       ]
     },
-    {
-      code: `
-      key: message @:(v8)
-      `,
-      ...options.yaml.v9,
-      errors: [
-        {
-          message: 'Unexpected empty linked key',
-          line: 2,
-          column: 21
-        }
-      ]
-    },
-    {
-      code: `
-      key: message new line
-        @:(v8)
-      `,
-      ...options.yaml.v9,
-      errors: [
-        {
-          message: 'Unexpected empty linked key',
-          line: 3,
-          column: 10
-        }
-      ]
-    },
-    {
-      code: `
-      key: "message new line
-        @:(v8)"
-      `,
-      ...options.yaml.v9,
-      errors: [
-        {
-          message: 'Unexpected empty linked key',
-          line: 3,
-          column: 10
-        }
-      ]
-    },
-    {
-      code: `
-      key: 'message new line
-        @:(v8)'
-      `,
-      ...options.yaml.v9,
-      errors: [
-        {
-          message: 'Unexpected empty linked key',
-          line: 3,
-          column: 10
-        }
-      ]
-    },
+    // {
+    //   // The syntax is now allowed.
+    //   code: `
+    //   key: message @:(v8)
+    //   `,
+    //   ...options.yaml.v9,
+    //   errors: [
+    //     {
+    //       message: 'Unexpected empty linked key',
+    //       line: 2,
+    //       column: 21
+    //     }
+    //   ]
+    // },
+    // {
+    //   // The syntax is now allowed.
+    //   code: `
+    //   key: message new line
+    //     @:(v8)
+    //   `,
+    //   ...options.yaml.v9,
+    //   errors: [
+    //     {
+    //       message: 'Unexpected empty linked key',
+    //       line: 3,
+    //       column: 10
+    //     }
+    //   ]
+    // },
+    // {
+    //   // The syntax is now allowed.
+    //   code: `
+    //   key: "message new line
+    //     @:(v8)"
+    //   `,
+    //   ...options.yaml.v9,
+    //   errors: [
+    //     {
+    //       message: 'Unexpected empty linked key',
+    //       line: 3,
+    //       column: 10
+    //     }
+    //   ]
+    // },
+    // {
+    //   // The syntax is now allowed.
+    //   code: `
+    //   key: 'message new line
+    //     @:(v8)'
+    //   `,
+    //   ...options.yaml.v9,
+    //   errors: [
+    //     {
+    //       message: 'Unexpected empty linked key',
+    //       line: 3,
+    //       column: 10
+    //     }
+    //   ]
+    // },
     {
       code: `
       <i18n lang="yaml">
@@ -476,16 +481,6 @@ tester.run('valid-message-syntax', rule as never, {
           line: 3,
           column: 22
         },
-        {
-          message: 'Unterminated closing brace',
-          line: 4,
-          column: 27
-        },
-        {
-          message: 'Unbalanced closing brace',
-          line: 4,
-          column: 34
-        },
         {
           message: "Unexpected 'null' message",
           line: 5,
diff --git a/tests/lib/utils/message-compiler/parser-v8.ts b/tests/lib/utils/message-compiler/parser-v8.ts
index f117022..5505029 100644
--- a/tests/lib/utils/message-compiler/parser-v8.ts
+++ b/tests/lib/utils/message-compiler/parser-v8.ts
@@ -3,7 +3,7 @@
  */
 import { deepStrictEqual } from 'assert'
 import { parse } from '../../../../lib/utils/message-compiler/parser-v8'
-import { parse as parseForV9 } from '../../../../lib/utils/message-compiler/parser'
+import { parse as parseForV9 } from '../../../../lib/utils/message-compiler/parser-v9'
 import { errorsFixtures } from './parser-v8-data'
 
 describe('parser-v8', () => {
diff --git a/tests/lib/utils/message-compiler/utils.ts b/tests/lib/utils/message-compiler/utils.ts
index 44cefc0..071a449 100644
--- a/tests/lib/utils/message-compiler/utils.ts
+++ b/tests/lib/utils/message-compiler/utils.ts
@@ -21,31 +21,37 @@ describe('message-compiler utils', () => {
       deepStrictEqual(get('^8.0.0'), {
         v8: true,
         v9: false,
+        v10: false,
         isNotSet: false
       })
       deepStrictEqual(get('^9.0.0'), {
         v8: false,
         v9: true,
+        v10: false,
         isNotSet: false
       })
       deepStrictEqual(get('^7.0.0'), {
         v8: true,
         v9: false,
+        v10: false,
         isNotSet: false
       })
       deepStrictEqual(get('^10.0.0'), {
         v8: false,
-        v9: true,
+        v9: false,
+        v10: true,
         isNotSet: false
       })
       deepStrictEqual(get('>=5.0.0'), {
         v8: true,
         v9: true,
+        v10: true,
         isNotSet: false
       })
       deepStrictEqual(get('^9.0.0-beta.8'), {
         v8: false,
         v9: true,
+        v10: false,
         isNotSet: false
       })
     })