From bbc15c6def7ef773e3f1b43ee71342406d704c13 Mon Sep 17 00:00:00 2001 From: kazupon <72989+kazupon@users.noreply.github.com> Date: Wed, 23 Jul 2025 06:26:28 +0000 Subject: [PATCH 1/3] chore: sync changelog --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60c63a12e..118a60b8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +# v11.1.11 (2025-07-23T06:26:19Z) + +This changelog is generated by [GitHub Releases](https://github.com/intlify/vue-i18n/releases/tag/v11.1.11) + + + +## What's Changed + +### ⚡ Improvement Features + +- fix: change warning from error for mutiple useI18n local scope calling by @kazupon in https://github.com/intlify/vue-i18n/pull/2235 + +**Full Changelog**: https://github.com/intlify/vue-i18n/compare/v11.1.10...v11.1.11 + # v11.1.10 (2025-07-16T04:09:03Z) This changelog is generated by [GitHub Releases](https://github.com/intlify/vue-i18n/releases/tag/v11.1.10) From fe3f7165f54503230d278052d547242a085d8332 Mon Sep 17 00:00:00 2001 From: kazuya kawaguchi Date: Fri, 5 Sep 2025 16:51:45 +0900 Subject: [PATCH 2/3] fix: Emit INVALID_TOKEN_IN_PLACEHOLDER instead of UNTERMINATED_CLOSING_BRACE when invalid token is in placeholder and update docs (#2255) * Emit INVALID_TOKEN_IN_PLACEHOLDER instead of UNTERMINATED_CLOSING_BRACE when invalid token is in placeholder and update docs (#2252) * chore: drop node v18 --------- Co-authored-by: lutejka --- .github/workflows/ci.yml | 8 +- .github/workflows/nightly-release.yml | 2 +- .github/workflows/release.yml | 2 +- docs/guide/essentials/syntax.md | 71 ++++++++++++ packages/message-compiler/src/tokenizer.ts | 20 ++++ .../test/tokenizer/named.test.ts | 108 +++++++++++++----- 6 files changed, 175 insertions(+), 36 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e5981e70b..2ab86cd45 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - node: [18] + node: [20] runs-on: ${{ matrix.os }} @@ -47,7 +47,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] - node: [18] + node: [20] runs-on: ${{ matrix.os }} @@ -83,7 +83,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] - node: [18.19, 20, 22] + node: [20, 22, 24] runs-on: ${{ matrix.os }} @@ -116,7 +116,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] - node: [18.19, 20, 22] + node: [20, 22, 24] runs-on: ${{ matrix.os }} diff --git a/.github/workflows/nightly-release.yml b/.github/workflows/nightly-release.yml index 8c3b9df44..76ed8570d 100644 --- a/.github/workflows/nightly-release.yml +++ b/.github/workflows/nightly-release.yml @@ -23,7 +23,7 @@ jobs: - name: Setup node uses: actions/setup-node@v4 with: - node-version: 18.18 + node-version: 20 cache: pnpm - name: Install dependencies diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c5999e0ce..e3b1fa397 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20 - name: Install dependencies run: pnpm install --no-frozen-lockfile diff --git a/docs/guide/essentials/syntax.md b/docs/guide/essentials/syntax.md index e5c2dc5b8..91329ff17 100644 --- a/docs/guide/essentials/syntax.md +++ b/docs/guide/essentials/syntax.md @@ -26,6 +26,10 @@ The locale messages is the resource specified by the `messages` option of `creat Named interpolation allows you to specify variables defined in JavaScript. In the locale message above, you can localize it by giving the JavaScript defined `msg` as a parameter to the translation function. +The variable name inside `{}` must starts with a letter (a-z, A-Z) or an underscore (`_`), followed by any combination of letters, digits, underscores (`_`), hyphens (`-`), or dollar signs (`$`). + +Examples: `{msg}`, `{_userName}`, `{user-id}`, `{total$}` + The following is an example of the use of `$t` in a template: ```html @@ -241,6 +245,73 @@ You can use the interpolations (Named, List, and Literal) for the key of Linked ::: +This example shows the use of modifiers (`@.lower`, `@.upper`, `@.capitalize`) combined with named, list, and literal interpolations. + + +```js +const messages = { + en: { + message: { + greeting: "Hello, @.lower:{'message.name'}! You have {count} new messages.", + name:"{name}" + }, + + welcome: "Welcome, @.upper:{'name'}! Today is @.capitalize:{'day'}.", + name: '{0}', + day: '{1}', + + literalMessage: "This is an email: foo{'@'}@.lower:domain", + domain: 'SHOUTING' + } +} +``` +### Named interpolation with modifier + +In `message.greeting`, we use a named interpolation for `{count}` and link to `message.name`, applying the .lower modifier. + +The key `message.name` contains `{name}`, which will be interpolated with the passed `name` param. + +The `message.greeting` is linked to the locale message key `message.name`. + +```html +

{{ $t('message.greeting', { name: 'Alice', count: 5 }) }}

+``` +As result, the below + +```html +

Hello, alice! You have 5 new messages.

+``` + +### List interpolation with modifier + +In this case, the values for `{0}` and `{1}` are passed as an array. The keys `name` and `day` are resolved using list interpolation and transformed with modifiers. + +```html +

{{ $t('welcome', ['bob', 'MONDAY']) }}

+``` + +As result, the below + +```html +

Welcome, BOB! Today is Monday.

+``` + +### Literal interpolation with modifier + +In this example, we use a literal string inside the message and apply the `.lower` modifier. + +```html +

{{ $t('literalMessage') }}

+``` + +Here, the modifier is applied to the content inside `domain`, and the `@` is preserved as literal output. + +As result, the below + +```html +

This is an email: foo@shouting

+``` + ## Special Characters The following characters used in the message format syntax are processed by the compiler as special characters: diff --git a/packages/message-compiler/src/tokenizer.ts b/packages/message-compiler/src/tokenizer.ts index 0ef63bf88..6f94bfe9b 100644 --- a/packages/message-compiler/src/tokenizer.ts +++ b/packages/message-compiler/src/tokenizer.ts @@ -484,6 +484,26 @@ export function createTokenizer( name += ch } + // Check if takeNamedIdentifierChar stoped because of invalid characters + const currentChar = scnr.currentChar() + if ( + currentChar && + currentChar !== '}' && + currentChar !== EOF && + currentChar !== SPACE && + currentChar !== NEW_LINE && + currentChar !== '\u3000' + ) { + const invalidPart = readInvalidIdentifier(scnr) + emitError( + CompileErrorCodes.INVALID_TOKEN_IN_PLACEHOLDER, + currentPosition(), + 0, + name + invalidPart + ) + return name + invalidPart + } + if (scnr.currentChar() === EOF) { emitError( CompileErrorCodes.UNTERMINATED_CLOSING_BRACE, diff --git a/packages/message-compiler/test/tokenizer/named.test.ts b/packages/message-compiler/test/tokenizer/named.test.ts index c7c3bb3c6..7405000a8 100644 --- a/packages/message-compiler/test/tokenizer/named.test.ts +++ b/packages/message-compiler/test/tokenizer/named.test.ts @@ -2,13 +2,13 @@ import { format } from '@intlify/shared' import { CompileErrorCodes, errorMessages } from '../../src/errors' import { createTokenizer, - TokenTypes, ERROR_DOMAIN, - parse + parse, + TokenTypes } from '../../src/tokenizer' -import type { TokenizeOptions } from '../../src/options' import type { CompileError } from '../../src/errors' +import type { TokenizeOptions } from '../../src/options' test('basic', () => { const tokenizer = createTokenizer('hi {name} !') @@ -645,32 +645,80 @@ describe('errors', () => { } ] as CompileError[]) }) - const items = [`$`, `-`] - for (const ch of items) { - test(`invalid '${ch}' in placeholder`, () => { - parse(`hi {${ch}} !`, options) - expect(errors).toEqual([ - { - code: CompileErrorCodes.INVALID_TOKEN_IN_PLACEHOLDER, - domain: ERROR_DOMAIN, - message: format( - errorMessages[CompileErrorCodes.INVALID_TOKEN_IN_PLACEHOLDER], - ch - ), - location: { - start: { - line: 1, - offset: 4, - column: 5 - }, - end: { - line: 1, - offset: 5, - column: 6 - } - } + + test.each([ + [ + '$', + { + start: { + line: 1, + offset: 4, + column: 5 + }, + end: { + line: 1, + offset: 5, + column: 6 + } + } + ], + [ + '-', + { + start: { + line: 1, + offset: 4, + column: 5 + }, + end: { + line: 1, + offset: 5, + column: 6 + } + } + ], + [ + 'àaa', + { + start: { + line: 1, + offset: 4, + column: 5 + }, + end: { + line: 1, + offset: 7, + column: 8 } - ] as CompileError[]) - }) - } + } + ], + [ + 'aàa', + { + start: { + line: 1, + offset: 4, + column: 5 + }, + end: { + line: 1, + offset: 7, + column: 8 + } + } + ] + ])(`invalid '%s' in placeholder`, (ch, location) => { + parse(`hi {${ch}} !`, options) + expect(errors).toEqual([ + { + code: CompileErrorCodes.INVALID_TOKEN_IN_PLACEHOLDER, + domain: ERROR_DOMAIN, + message: format( + errorMessages[CompileErrorCodes.INVALID_TOKEN_IN_PLACEHOLDER], + ch + ), + location + } + ] as CompileError[]) + }) }) From 7a2c4ee45658d1e63365b240818b8f3653783c1b Mon Sep 17 00:00:00 2001 From: kazuya kawaguchi Date: Fri, 5 Sep 2025 17:29:00 +0900 Subject: [PATCH 3/3] release: v11.1.12 --- package.json | 2 +- packages/core-base/package.json | 2 +- packages/core/package.json | 2 +- packages/devtools-types/package.json | 2 +- packages/format-explorer/package.json | 2 +- packages/message-compiler/package.json | 2 +- packages/petite-vue-i18n/package.json | 2 +- packages/shared/package.json | 2 +- packages/size-check-core/package.json | 2 +- packages/size-check-petite-vue-i18n/package.json | 2 +- packages/size-check-vue-i18n/package.json | 2 +- packages/vue-i18n-core/package.json | 2 +- packages/vue-i18n/package.json | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 9b09f9872..1a653538f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vue-i18n-root", - "version": "11.1.11", + "version": "11.1.12", "license": "MIT", "author": { "name": "kazuya kawaguchi", diff --git a/packages/core-base/package.json b/packages/core-base/package.json index 3e23432d6..724d9e1bb 100644 --- a/packages/core-base/package.json +++ b/packages/core-base/package.json @@ -1,6 +1,6 @@ { "name": "@intlify/core-base", - "version": "11.1.11", + "version": "11.1.12", "description": "@intlify/core-base", "keywords": [ "core", diff --git a/packages/core/package.json b/packages/core/package.json index 0a799ea69..15db8d5ac 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@intlify/core", - "version": "11.1.11", + "version": "11.1.12", "description": "@intlify/core", "keywords": [ "core", diff --git a/packages/devtools-types/package.json b/packages/devtools-types/package.json index 8f48a5327..bc0814daa 100644 --- a/packages/devtools-types/package.json +++ b/packages/devtools-types/package.json @@ -1,6 +1,6 @@ { "name": "@intlify/devtools-types", - "version": "11.1.11", + "version": "11.1.12", "description": "@intlify/devtools-types", "keywords": [ "devtools", diff --git a/packages/format-explorer/package.json b/packages/format-explorer/package.json index 2cea66b8c..6cb371a6f 100644 --- a/packages/format-explorer/package.json +++ b/packages/format-explorer/package.json @@ -1,7 +1,7 @@ { "name": "@intlify/message-format-explorer", "description": "Intlify message format explorer", - "version": "11.1.11", + "version": "11.1.12", "private": true, "scripts": { "dev": "vite", diff --git a/packages/message-compiler/package.json b/packages/message-compiler/package.json index e90d44c08..8f9390e3b 100644 --- a/packages/message-compiler/package.json +++ b/packages/message-compiler/package.json @@ -1,6 +1,6 @@ { "name": "@intlify/message-compiler", - "version": "11.1.11", + "version": "11.1.12", "description": "@intlify/message-compiler", "keywords": [ "compiler", diff --git a/packages/petite-vue-i18n/package.json b/packages/petite-vue-i18n/package.json index 958eb14f0..d59ed7fd1 100644 --- a/packages/petite-vue-i18n/package.json +++ b/packages/petite-vue-i18n/package.json @@ -1,6 +1,6 @@ { "name": "petite-vue-i18n", - "version": "11.1.11", + "version": "11.1.12", "description": "Vue I18n lite version", "keywords": [ "i18n", diff --git a/packages/shared/package.json b/packages/shared/package.json index 5543607e4..62485b4c1 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,6 +1,6 @@ { "name": "@intlify/shared", - "version": "11.1.11", + "version": "11.1.12", "description": "@intlify/shared", "keywords": [ "i18n", diff --git a/packages/size-check-core/package.json b/packages/size-check-core/package.json index 6663abadf..2ff352a5c 100644 --- a/packages/size-check-core/package.json +++ b/packages/size-check-core/package.json @@ -13,5 +13,5 @@ "devDependencies": { "vite": "^6.0.0" }, - "version": "11.1.11" + "version": "11.1.12" } diff --git a/packages/size-check-petite-vue-i18n/package.json b/packages/size-check-petite-vue-i18n/package.json index f3793f748..e5b3bee9c 100644 --- a/packages/size-check-petite-vue-i18n/package.json +++ b/packages/size-check-petite-vue-i18n/package.json @@ -17,5 +17,5 @@ "vite": "^6.0.0", "vue-tsc": "^2.0.0" }, - "version": "11.1.11" + "version": "11.1.12" } diff --git a/packages/size-check-vue-i18n/package.json b/packages/size-check-vue-i18n/package.json index 883218f13..8c9fdb47c 100644 --- a/packages/size-check-vue-i18n/package.json +++ b/packages/size-check-vue-i18n/package.json @@ -17,5 +17,5 @@ "vite": "^6.0.0", "vue-tsc": "^2.0.0" }, - "version": "11.1.11" + "version": "11.1.12" } diff --git a/packages/vue-i18n-core/package.json b/packages/vue-i18n-core/package.json index 35054317e..22e271479 100644 --- a/packages/vue-i18n-core/package.json +++ b/packages/vue-i18n-core/package.json @@ -1,6 +1,6 @@ { "name": "@intlify/vue-i18n-core", - "version": "11.1.11", + "version": "11.1.12", "description": "@intlify/vue-i18n-core", "keywords": [ "core", diff --git a/packages/vue-i18n/package.json b/packages/vue-i18n/package.json index 772c745a5..6dc41094e 100644 --- a/packages/vue-i18n/package.json +++ b/packages/vue-i18n/package.json @@ -1,6 +1,6 @@ { "name": "vue-i18n", - "version": "11.1.11", + "version": "11.1.12", "description": "Internationalization plugin for Vue.js", "keywords": [ "i18n",