diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5a4fa8b0c..30917bc88 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,11 @@
+# v11.1.9 (2025-07-03T07:48:17Z)
+
+This changelog is generated by [GitHub Releases](https://github.com/intlify/vue-i18n/releases/tag/v11.1.9)
+
+
+
+**Full Changelog**: https://github.com/intlify/vue-i18n/compare/v11.1.8...v11.1.9
+
# v11.1.8 (2025-07-02T15:01:43Z)
This changelog is generated by [GitHub Releases](https://github.com/intlify/vue-i18n/releases/tag/v11.1.8)
diff --git a/docs/guide/essentials/syntax.md b/docs/guide/essentials/syntax.md
index 1ad631c75..e5c2dc5b8 100644
--- a/docs/guide/essentials/syntax.md
+++ b/docs/guide/essentials/syntax.md
@@ -334,3 +334,82 @@ As result, the below:
world
```
+
+### Using the escape parameter option
+
+To help mitigate XSS risks when using HTML messages, Vue I18n provides escape parameter options. When enabled, this option escapes interpolation parameters and sanitizes the final translated HTML.
+
+:::tip NOTE
+The option name depends on which API mode you're using:
+- **Legacy API**: use `escapeParameterHtml`
+- **Composition API**: use `escapeParameter`
+:::
+
+#### Legacy API
+
+```js
+// enable `escapeParameterHtml` globally
+const i18n = createI18n({
+ locale: 'en',
+ legacy: true,
+ escapeParameterHtml: true,
+ messages: {
+ en: {
+ message: {
+ welcome: 'Welcome {name} !'
+ }
+ }
+ }
+})
+
+// or enable it per translation
+this.$t('message.welcome', { name: userInput }, { escapeParameter: true })
+```
+
+#### Composition API
+
+```js
+// enable `escapeParameter` globally
+const i18n = createI18n({
+ locale: 'en',
+ escapeParameter: true,
+ messages: {
+ en: {
+ message: {
+ welcome: 'Welcome {name} !'
+ }
+ }
+ }
+})
+
+// or enable it per translation
+const { t } = useI18n()
+t('message.welcome', { name: userInput }, { escapeParameter: true })
+```
+
+#### How it works
+
+When the escape parameter option is enabled:
+- HTML special characters (`<`, `>`, `"`, `'`, `&`, `/`, `=`) in interpolation parameters are escaped
+- The final translated HTML is sanitized to prevent XSS attacks:
+ - Dangerous characters in HTML attribute values are escaped
+ - Event handler attributes (`onclick`, `onerror`, etc.) are neutralized
+ - JavaScript URLs in href, src, action, formaction, and style attributes are disabled
+
+#### Example
+
+```js
+const userInput = ' '
+
+// Without escape parameter (DANGEROUS):
+$t('message.welcome', { name: userInput })
+// Result: Welcome !
+
+// With escape parameter (SAFE):
+$t('message.welcome', { name: userInput }, { escapeParameter: true })
+// Result: Welcome <img src=x onerror=alert(1)> !
+```
+
+:::warning IMPORTANT
+Even with the escape parameter option enabled, you should still be cautious about using HTML in translations. Always validate and sanitize user input before using it in translations, and prefer [Component interpolation](../advanced/component) when possible.
+:::
diff --git a/package.json b/package.json
index 81c461d73..33694c2d6 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "vue-i18n-root",
- "version": "11.1.9",
+ "version": "11.1.10",
"license": "MIT",
"author": {
"name": "kazuya kawaguchi",
diff --git a/packages/core-base/package.json b/packages/core-base/package.json
index 3390af533..e30ef50e6 100644
--- a/packages/core-base/package.json
+++ b/packages/core-base/package.json
@@ -1,6 +1,6 @@
{
"name": "@intlify/core-base",
- "version": "11.1.9",
+ "version": "11.1.10",
"description": "@intlify/core-base",
"keywords": [
"core",
diff --git a/packages/core-base/src/translate.ts b/packages/core-base/src/translate.ts
index bf3e8b4b7..3c84e9862 100644
--- a/packages/core-base/src/translate.ts
+++ b/packages/core-base/src/translate.ts
@@ -15,6 +15,7 @@ import {
isString,
mark,
measure,
+ sanitizeTranslatedHtml,
warn
} from '@intlify/shared'
import { isMessageAST } from './ast'
@@ -153,7 +154,16 @@ export interface TranslateOptions
fallbackWarn?: boolean
/**
* @remarks
- * Whether do escape parameter for list or named interpolation values
+ * Whether to escape parameters for list or named interpolation values.
+ * When enabled, this option:
+ * - Escapes HTML special characters (`<`, `>`, `"`, `'`, `&`, `/`, `=`) in interpolation parameters
+ * - Sanitizes the final translated HTML to prevent XSS attacks by:
+ * - Escaping dangerous characters in HTML attribute values
+ * - Neutralizing event handler attributes (onclick, onerror, etc.)
+ * - Disabling javascript: URLs in href, src, action, formaction, and style attributes
+ *
+ * @defaultValue false
+ * @see [HTML Message - Using the escapeParameter option](https://vue-i18n.intlify.dev/guide/essentials/syntax.html#using-the-escapeparameter-option)
*/
escapeParameter?: boolean
/**
@@ -765,10 +775,15 @@ export function translate<
)
// if use post translation option, proceed it with handler
- const ret = postTranslation
+ let ret = postTranslation
? postTranslation(messaged, key as string)
: messaged
+ // apply HTML sanitization for security
+ if (escapeParameter && isString(ret)) {
+ ret = sanitizeTranslatedHtml(ret) as MessageFunctionReturn
+ }
+
// NOTE: experimental !!
if (__DEV__ || __FEATURE_PROD_INTLIFY_DEVTOOLS__) {
// prettier-ignore
diff --git a/packages/core-base/test/translate.test.ts b/packages/core-base/test/translate.test.ts
index 027172685..ba2b2784c 100644
--- a/packages/core-base/test/translate.test.ts
+++ b/packages/core-base/test/translate.test.ts
@@ -685,7 +685,7 @@ describe('escapeParameter', () => {
})
expect(translate(ctx, 'hello', { name: 'kazupon ' })).toEqual(
- 'hello, <b>kazupon</b>!'
+ 'hello, <b>kazupon</b>!'
)
})
@@ -703,7 +703,7 @@ describe('escapeParameter', () => {
expect(
translate(ctx, 'hello', ['kazupon '], { escapeParameter: true })
- ).toEqual('hello, <b>kazupon</b>!')
+ ).toEqual('hello, <b>kazupon</b>!')
})
test('no escape', () => {
@@ -722,6 +722,72 @@ describe('escapeParameter', () => {
'hello, kazupon !'
)
})
+
+ test('vulnerable case from GHSA report - img onerror attack', () => {
+ // Mock console.warn to suppress warnings for this test
+ const originalWarn = console.warn
+ console.warn = vi.fn()
+
+ const ctx = context({
+ locale: 'en',
+ warnHtmlMessage: false,
+ escapeParameter: true,
+ messages: {
+ en: {
+ vulnerable: 'Caution: '
+ }
+ }
+ })
+
+ const result = translate(ctx, 'vulnerable', {
+ payload: ''
+ })
+
+ // with the fix, the payload should be escaped, preventing the attack
+ // The onerror attribute is neutralized by converting 'o' to o
+ expect(result).toEqual(
+ 'Caution: '
+ )
+
+ // result should NOT contain executable script tags
+ expect(result).not.toContain('')
+
+ // Restore console.warn
+ console.warn = originalWarn
+ })
+
+ test('vulnerable case - attribute injection attack', () => {
+ const ctx = context({
+ locale: 'en',
+ warnHtmlMessage: false,
+ escapeParameter: true,
+ messages: {
+ en: {
+ message: 'Click here '
+ }
+ }
+ })
+
+ const result = translate(ctx, 'message', {
+ url: 'javascript:alert(1)'
+ })
+
+ // with the fix, javascript: URL scheme is neutralized
+ expect(result).toEqual('Click here ')
+
+ // another attack vector with quotes
+ const result2 = translate(ctx, 'message', {
+ url: '" onclick="alert(1)"'
+ })
+
+ expect(result2).toEqual(
+ 'Click here '
+ )
+
+ // `onclick` attribute should be escaped
+ expect(result2).not.toContain('onclick=')
+ })
})
describe('error', () => {
diff --git a/packages/core/package.json b/packages/core/package.json
index 93f869127..edaa65c90 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -1,6 +1,6 @@
{
"name": "@intlify/core",
- "version": "11.1.9",
+ "version": "11.1.10",
"description": "@intlify/core",
"keywords": [
"core",
diff --git a/packages/devtools-types/package.json b/packages/devtools-types/package.json
index 8f7139231..2e25eea07 100644
--- a/packages/devtools-types/package.json
+++ b/packages/devtools-types/package.json
@@ -1,6 +1,6 @@
{
"name": "@intlify/devtools-types",
- "version": "11.1.9",
+ "version": "11.1.10",
"description": "@intlify/devtools-types",
"keywords": [
"devtools",
diff --git a/packages/format-explorer/package.json b/packages/format-explorer/package.json
index f21d79d50..45e5a0e22 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.9",
+ "version": "11.1.10",
"private": true,
"scripts": {
"dev": "vite",
diff --git a/packages/message-compiler/package.json b/packages/message-compiler/package.json
index f98bb3074..e8b254e61 100644
--- a/packages/message-compiler/package.json
+++ b/packages/message-compiler/package.json
@@ -1,6 +1,6 @@
{
"name": "@intlify/message-compiler",
- "version": "11.1.9",
+ "version": "11.1.10",
"description": "@intlify/message-compiler",
"keywords": [
"compiler",
diff --git a/packages/petite-vue-i18n/package.json b/packages/petite-vue-i18n/package.json
index 3e8c69deb..c0631b19b 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.9",
+ "version": "11.1.10",
"description": "Vue I18n lite version",
"keywords": [
"i18n",
diff --git a/packages/shared/package.json b/packages/shared/package.json
index e5f9587b4..5df42ac0d 100644
--- a/packages/shared/package.json
+++ b/packages/shared/package.json
@@ -1,6 +1,6 @@
{
"name": "@intlify/shared",
- "version": "11.1.9",
+ "version": "11.1.10",
"description": "@intlify/shared",
"keywords": [
"i18n",
diff --git a/packages/shared/src/utils.ts b/packages/shared/src/utils.ts
index f967df0cf..013e1085c 100644
--- a/packages/shared/src/utils.ts
+++ b/packages/shared/src/utils.ts
@@ -3,6 +3,8 @@
* written by kazuya kawaguchi
*/
+import { warn } from './warn'
+
export const inBrowser = typeof window !== 'undefined'
export let mark: (tag: string) => void | undefined
@@ -104,10 +106,66 @@ export const getGlobalThis = (): any => {
export function escapeHtml(rawText: string): string {
return rawText
+ .replace(/&/g, '&') // escape `&` first to avoid double escaping
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
+ .replace(/\//g, '/') // escape `/` to prevent closing tags or JavaScript URLs
+ .replace(/=/g, '=') // escape `=` to prevent attribute injection
+}
+
+function escapeAttributeValue(value: string): string {
+ return value
+ .replace(/&(?![a-zA-Z0-9#]{2,6};)/g, '&') // escape unescaped `&`
+ .replace(/"/g, '"')
+ .replace(/'/g, ''')
+ .replace(//g, '>')
+}
+
+export function sanitizeTranslatedHtml(html: string): string {
+ // Escape dangerous characters in attribute values
+ // Process attributes with double quotes
+ html = html.replace(
+ /(\w+)\s*=\s*"([^"]*)"/g,
+ (_, attrName, attrValue) =>
+ `${attrName}="${escapeAttributeValue(attrValue)}"`
+ )
+
+ // Process attributes with single quotes
+ html = html.replace(
+ /(\w+)\s*=\s*'([^']*)'/g,
+ (_, attrName, attrValue) =>
+ `${attrName}='${escapeAttributeValue(attrValue)}'`
+ )
+
+ // Detect and neutralize event handler attributes
+ const eventHandlerPattern = /\s*on\w+\s*=\s*["']?[^"'>]+["']?/gi
+ if (eventHandlerPattern.test(html)) {
+ if (__DEV__) {
+ warn(
+ 'Potentially dangerous event handlers detected in translation. ' +
+ 'Consider removing onclick, onerror, etc. from your translation messages.'
+ )
+ }
+ // Neutralize event handler attributes by escaping 'on'
+ html = html.replace(/(\s+)(on)(\w+\s*=)/gi, '$1on$3')
+ }
+
+ // Disable javascript: URLs in various contexts
+ const javascriptUrlPattern = [
+ // In href, src, action, formaction attributes
+ /(\s+(?:href|src|action|formaction)\s*=\s*["']?)\s*javascript:/gi,
+ // In style attributes within url()
+ /(style\s*=\s*["'][^"']*url\s*\(\s*)javascript:/gi
+ ]
+
+ javascriptUrlPattern.forEach(pattern => {
+ html = html.replace(pattern, '$1javascript:')
+ })
+
+ return html
}
const hasOwnProperty = Object.prototype.hasOwnProperty
diff --git a/packages/shared/test/utils.test.ts b/packages/shared/test/utils.test.ts
index 5cefc131c..8889eb245 100644
--- a/packages/shared/test/utils.test.ts
+++ b/packages/shared/test/utils.test.ts
@@ -1,4 +1,18 @@
-import { format, generateCodeFrame, makeSymbol, join } from '../src/index'
+import { vi } from 'vitest'
+
+// Mock the warn function before importing anything else
+vi.mock('../src/warn', () => ({
+ warn: vi.fn()
+}))
+
+import {
+ escapeHtml,
+ format,
+ generateCodeFrame,
+ join,
+ makeSymbol,
+ sanitizeTranslatedHtml
+} from '../src/index'
test('format', () => {
expect(format(`foo: {0}`, 'x')).toEqual('foo: x')
@@ -54,3 +68,330 @@ test('join', () => {
]
expect(join(longSize, ' ')).toEqual(longSize.join(' '))
})
+
+describe('escapeHtml', () => {
+ test('escape `<` and `>`', () => {
+ expect(escapeHtml('test
')).toBe(
+ '<div>test</div>'
+ )
+ })
+
+ test('escape quotes', () => {
+ expect(escapeHtml(`"double" and 'single'`)).toBe(
+ '"double" and 'single''
+ )
+ })
+
+ test('escape `&` correctly', () => {
+ expect(escapeHtml('<')).toBe('<')
+ expect(escapeHtml('&')).toBe('&')
+ })
+
+ test('escape `/` for preventing closing tags', () => {
+ expect(escapeHtml('')).toBe('</script>')
+ expect(escapeHtml('javascript://')).toBe('javascript://')
+ })
+
+ test('escape `=` for preventing attribute injection', () => {
+ expect(escapeHtml('onerror=alert(1)')).toBe('onerror=alert(1)')
+ expect(escapeHtml('src=x onerror=alert(1)')).toBe(
+ 'src=x onerror=alert(1)'
+ )
+ })
+
+ test('prevent img `onerror` attack', () => {
+ expect(escapeHtml(' ')).toBe(
+ '<img src=x onerror=alert(1)>'
+ )
+ })
+
+ test('prevent script injection', () => {
+ expect(escapeHtml('')).toBe(
+ '</script><script>alert(1)</script>'
+ )
+ })
+
+ test('handle complex XSS payloads', () => {
+ expect(escapeHtml(' ')).toBe(
+ '<img src="x" onerror="alert('XSS')">'
+ )
+ })
+
+ test('handle empty string', () => {
+ expect(escapeHtml('')).toBe('')
+ })
+
+ test('handle normal text without special characters', () => {
+ expect(escapeHtml('Hello World')).toBe('Hello World')
+ })
+
+ test('escape all special characters in order', () => {
+ expect(escapeHtml('&<>"\'/=')).toBe('&<>"'/=')
+ })
+})
+
+describe('sanitizeTranslatedHtml', () => {
+ test('neutralize event handlers', () => {
+ const html = 'Click '
+ const result = sanitizeTranslatedHtml(html)
+ expect(result).toBe('Click ')
+ })
+
+ test('neutralize javascript URLs', () => {
+ const html = 'Click '
+ const result = sanitizeTranslatedHtml(html)
+ expect(result).toBe('Click ')
+ })
+
+ test('escape dangerous characters in attribute values', () => {
+ const html = 'Test
'
+ const result = sanitizeTranslatedHtml(html)
+ expect(result).toBe(
+ 'Test
'
+ )
+ })
+
+ test('handle class attribute with normal values', () => {
+ const html = 'Button
'
+ const result = sanitizeTranslatedHtml(html)
+ expect(result).toBe('Button
')
+ })
+
+ test('escape dangerous characters in class attribute', () => {
+ const html = 'Test
'
+ const result = sanitizeTranslatedHtml(html)
+ expect(result).toBe(
+ 'Test
'
+ )
+ })
+
+ test('handle class attribute with script tags', () => {
+ const html = 'Test
'
+ const result = sanitizeTranslatedHtml(html)
+ expect(result).toBe(
+ 'Test
'
+ )
+ })
+
+ test('handle multiple attributes including class', () => {
+ const html =
+ 'Test
'
+ const result = sanitizeTranslatedHtml(html)
+ // Check that dangerous characters in attribute values are escaped
+ expect(result).toContain('class="btn"')
+ expect(result).toContain('data-value="<>"')
+ // Check that onclick is neutralized
+ expect(result).toContain('onclick=')
+ })
+
+ test('handle class attribute with single quotes', () => {
+ const html = "Alert
"
+ const result = sanitizeTranslatedHtml(html)
+ expect(result).toBe("Alert
")
+ })
+
+ test('escape single quotes in class attribute with single quotes', () => {
+ const html = "Test
"
+ const result = sanitizeTranslatedHtml(html)
+ expect(result).toBe("Test
")
+ })
+
+ test('handle id attribute with dangerous characters', () => {
+ const html = 'Test
'
+ const result = sanitizeTranslatedHtml(html)
+ expect(result).toBe(
+ 'Test
'
+ )
+ })
+
+ test('handle id attribute with script injection', () => {
+ const html = 'Test
'
+ const result = sanitizeTranslatedHtml(html)
+ expect(result).toBe(
+ 'Test
'
+ )
+ })
+
+ test('handle style attribute with dangerous characters', () => {
+ const html =
+ 'Test
'
+ const result = sanitizeTranslatedHtml(html)
+ expect(result).toBe(
+ 'Test
'
+ )
+ })
+
+ test('handle style attribute with javascript URL', () => {
+ const html = 'Test
'
+ const result = sanitizeTranslatedHtml(html)
+ expect(result).toBe(
+ 'Test
'
+ )
+ })
+
+ test('handle style attribute with javascript URL with spaces', () => {
+ const html =
+ 'Test
'
+ const result = sanitizeTranslatedHtml(html)
+ expect(result).toBe(
+ 'Test
'
+ )
+ })
+
+ test('handle style attribute with uppercase JavaScript URL', () => {
+ const html = 'Test
'
+ const result = sanitizeTranslatedHtml(html)
+ expect(result).toBe(
+ 'Test
'
+ )
+ })
+
+ test('handle multiple dangerous attributes including id and style', () => {
+ const html =
+ 'Test
'
+ const result = sanitizeTranslatedHtml(html)
+ expect(result).toContain('id="test>"')
+ expect(result).toContain('style="color: red"')
+ expect(result).toContain('class="btn"')
+ expect(result).toContain('onclick=')
+ })
+
+ test('handle formaction attribute with javascript URL', () => {
+ const html = 'Submit '
+ const result = sanitizeTranslatedHtml(html)
+ expect(result).toBe(
+ 'Submit '
+ )
+ })
+
+ test('handle data attributes with javascript URL', () => {
+ const html =
+ 'Test
'
+ const result = sanitizeTranslatedHtml(html)
+ // data-* attributes are not sanitized as they don't execute directly
+ expect(result).toBe(
+ 'Test
'
+ )
+ })
+
+ test('handle srcdoc attribute', () => {
+ const html = ''
+ const result = sanitizeTranslatedHtml(html)
+ expect(result).toBe(
+ ''
+ )
+ })
+
+ test('handle attribute values without quotes', () => {
+ const html = ' '
+ const result = sanitizeTranslatedHtml(html)
+ expect(result).toBe(' ')
+ })
+
+ test('handle mixed quote styles', () => {
+ const html = `Test
`
+ const result = sanitizeTranslatedHtml(html)
+ expect(result).toBe(
+ `Test
`
+ )
+ })
+
+ test('handle HTML entities in attribute values', () => {
+ const html = 'Test
'
+ const result = sanitizeTranslatedHtml(html)
+ // Already escaped entities should remain as is
+ expect(result).toBe(
+ 'Test
'
+ )
+ })
+
+ test('handle nested quotes in attributes', () => {
+ const html = `Test
`
+ const result = sanitizeTranslatedHtml(html)
+ expect(result).toBe(
+ `Test
`
+ )
+ })
+
+ // Accessibility (a11y) attributes tests
+ test('handle aria-label with dangerous characters', () => {
+ const html =
+ 'Button '
+ const result = sanitizeTranslatedHtml(html)
+ expect(result).toBe(
+ 'Button '
+ )
+ })
+
+ test('handle aria-describedby with quotes', () => {
+ const html = ` `
+ const result = sanitizeTranslatedHtml(html)
+ expect(result).toBe(
+ ' '
+ )
+ })
+
+ test('handle role attribute with dangerous characters', () => {
+ const html = 'Click
'
+ const result = sanitizeTranslatedHtml(html)
+ expect(result).toBe(
+ 'Click
'
+ )
+ })
+
+ test('handle tabindex attribute', () => {
+ const html =
+ 'Focusable
'
+ const result = sanitizeTranslatedHtml(html)
+ expect(result).toBe(
+ 'Focusable
'
+ )
+ })
+
+ test('handle alt attribute with dangerous content', () => {
+ const html =
+ ' '
+ const result = sanitizeTranslatedHtml(html)
+ expect(result).toBe(
+ ' '
+ )
+ })
+
+ test('handle title attribute with dangerous content', () => {
+ const html = 'XSS '
+ const result = sanitizeTranslatedHtml(html)
+ expect(result).toBe(
+ 'XSS '
+ )
+ })
+
+ test('handle multiple a11y attributes with dangerous content', () => {
+ const html =
+ 'Click '
+ const result = sanitizeTranslatedHtml(html)
+ expect(result).toContain(
+ 'aria-label="<script>alert(1)</script>"'
+ )
+ expect(result).toContain('role="button"')
+ expect(result).toContain('onclick=')
+ expect(result).toContain('tabindex="0"')
+ })
+
+ test('handle aria-hidden attribute', () => {
+ const html =
+ 'Hidden
'
+ const result = sanitizeTranslatedHtml(html)
+ expect(result).toBe(
+ 'Hidden
'
+ )
+ })
+
+ test('handle aria-live attribute', () => {
+ const html =
+ 'Status
'
+ const result = sanitizeTranslatedHtml(html)
+ expect(result).toBe(
+ 'Status
'
+ )
+ })
+})
diff --git a/packages/size-check-core/package.json b/packages/size-check-core/package.json
index 5289438b7..ef537cd9f 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.9"
+ "version": "11.1.10"
}
diff --git a/packages/size-check-petite-vue-i18n/package.json b/packages/size-check-petite-vue-i18n/package.json
index e227cf8a0..e18281756 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.9"
+ "version": "11.1.10"
}
diff --git a/packages/size-check-vue-i18n/package.json b/packages/size-check-vue-i18n/package.json
index 7106c2e5d..a6841f7d5 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.9"
+ "version": "11.1.10"
}
diff --git a/packages/vue-i18n-core/package.json b/packages/vue-i18n-core/package.json
index f264529b4..9b3e085ac 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.9",
+ "version": "11.1.10",
"description": "@intlify/vue-i18n-core",
"keywords": [
"core",
diff --git a/packages/vue-i18n-core/src/composer.ts b/packages/vue-i18n-core/src/composer.ts
index f84e85b7e..588d0e3c2 100644
--- a/packages/vue-i18n-core/src/composer.ts
+++ b/packages/vue-i18n-core/src/composer.ts
@@ -475,17 +475,21 @@ export interface ComposerOptions<
warnHtmlMessage?: boolean
/**
* @remarks
- * If `escapeParameter` is configured as true then interpolation parameters are escaped before the message is translated.
+ * Whether to escape parameters for list or named interpolation values.
+ * When enabled, this option:
+ * - Escapes HTML special characters (`<`, `>`, `"`, `'`, `&`, `/`, `=`) in interpolation parameters
+ * - Sanitizes the final translated HTML to prevent XSS attacks by:
+ * - Escaping dangerous characters in HTML attribute values
+ * - Neutralizing event handler attributes (onclick, onerror, etc.)
+ * - Disabling javascript: URLs in href, src, action, formaction, and style attributes
*
* This is useful when translation output is used in `v-html` and the translation resource contains html markup (e.g. around a user provided value).
*
* This usage pattern mostly occurs when passing precomputed text strings into UI components.
*
- * The escape process involves replacing the following symbols with their respective HTML character entities: `<`, `>`, `"`, `'`.
- *
* Setting `escapeParameter` as true should not break existing functionality but provides a safeguard against a subtle type of XSS attack vectors.
*
- * @VueI18nSee [HTML Message](../guide/essentials/syntax#html-message)
+ * @VueI18nSee [HTML Message - Using the escapeParameter option](../guide/essentials/syntax#using-the-escapeparameter-option)
*
* @defaultValue `false`
*/
diff --git a/packages/vue-i18n-core/src/legacy.ts b/packages/vue-i18n-core/src/legacy.ts
index 9d5dace6b..fe131f9b0 100644
--- a/packages/vue-i18n-core/src/legacy.ts
+++ b/packages/vue-i18n-core/src/legacy.ts
@@ -273,17 +273,21 @@ export interface VueI18nOptions<
warnHtmlInMessage?: WarnHtmlInMessageLevel
/**
* @remarks
- * If `escapeParameterHtml` is configured as true then interpolation parameters are escaped before the message is translated.
+ * Whether to escape parameters for list or named interpolation values.
+ * When enabled, this option:
+ * - Escapes HTML special characters (`<`, `>`, `"`, `'`, `&`, `/`, `=`) in interpolation parameters
+ * - Sanitizes the final translated HTML to prevent XSS attacks by:
+ * - Escaping dangerous characters in HTML attribute values
+ * - Neutralizing event handler attributes (onclick, onerror, etc.)
+ * - Disabling javascript: URLs in href, src, action, formaction, and style attributes
*
* This is useful when translation output is used in `v-html` and the translation resource contains html markup (e.g. around a user provided value).
*
* This usage pattern mostly occurs when passing precomputed text strings into UI components.
*
- * The escape process involves replacing the following symbols with their respective HTML character entities: `<`, `>`, `"`, `'`.
- *
* Setting `escapeParameterHtml` as true should not break existing functionality but provides a safeguard against a subtle type of XSS attack vectors.
*
- * @VueI18nSee [HTML Message](../guide/essentials/syntax#html-message)
+ * @VueI18nSee [HTML Message - Using the escapeParameter option](../guide/essentials/syntax#using-the-escapeparameter-option)
*
* @defaultValue `false`
*/
diff --git a/packages/vue-i18n/package.json b/packages/vue-i18n/package.json
index 02b110d37..b4772d492 100644
--- a/packages/vue-i18n/package.json
+++ b/packages/vue-i18n/package.json
@@ -1,6 +1,6 @@
{
"name": "vue-i18n",
- "version": "11.1.9",
+ "version": "11.1.10",
"description": "Internationalization plugin for Vue.js",
"keywords": [
"i18n",