最后一道防线扩大受众的半径:多语言(让其他语种的用户跑进同一条跑道)与无障碍(让所有能力的用户都能完赛)。本节用 i18n 模块实现语言路由与文案管理,处理 RTL 与区域格式,再按 WCAG 要点过一遍无障碍自查——8.3 报告里 Accessibility 分类扣的分,答案大半在这节。
官方 i18n 模块的核心决策是路由策略——每种语言是否有独立 URL:
npm install @nuxtjs/i18n
// nuxt.config.ts export default defineNuxtConfig({ modules: ['@nuxtjs/i18n'], i18n: { strategy: 'prefix_except_default', // 默认语言不带前缀,其他带 defaultLocale: 'zh', locales: [ { code: 'zh', iso: 'zh-CN', name: '中文' }, { code: 'en', iso: 'en-US', name: 'English' }, { code: 'ar', iso: 'ar-EG', name: 'العربية', dir: 'rtl' }, // 阿拉伯语:从右到左 ], }, })
三种策略的取舍:

| 策略 | URL 形态 | 适用 |
|---|---|---|
| prefix_except_default | /(中文)、/en、/ar | 默认市场为主,SEO 友好(常用) |
| prefix | /zh、/en、/ar | 各语言平等,结构最清晰 |
| no_prefix | 同一 URL 按浏览器协商 | 工具类应用,SEO 弱 |
文案文件按语言分目录(模块约定),组件里用组合函数取:
<script setup> const { t, locale, setLocale } = useI18n() </script> <template> <nav> <h1>{{ t('products.title') }}</h1> <p>{{ t('products.count', { n: 42 }) }}</p> <!-- 语言切换:模块处理路由前缀与 cookie 记忆 --> <button v-for="l in ['zh', 'en', 'ar']" :key="l" @click="setLocale(l)"> {{ l }} </button> </nav> </template>
# i18n/locales/zh.yaml products: title: 全部商品 count: 共 {n} 件 # i18n/locales/en.yaml products: title: All Products count: '{n} items'
SEO 配套三件事:每语言的 hreflang 标签(模块自动生成)、useSeoMeta 按当前语言出标题描述(8.2 的多语言版)、sitemap 包含全部语言变体。
dir: 'rtl' 声明后,模块在语言切换时自动更新文档方向。样式侧用逻辑属性替代物理方向,一套 CSS 适配双向:
/* 物理属性:RTL 下全部反掉,要写两套 */ /* margin-left: 16px; */ /* 逻辑属性:方向感知,一套通吃 */ .sidebar { margin-inline-start: 16px; } .title { text-align: start; } .row { padding-inline: 24px; }
区域格式交给标准 API,别手拼字符串:
// 日期、数字、货币按 locale 格式化 const dtf = new Intl.DateTimeFormat(locale.value, { dateStyle: 'long' }) dtf.format(new Date()) // 中文:2026年9月8日 const nf = new Intl.NumberFormat(locale.value, { style: 'currency', currency: 'CNY' }) nf.format(399) // 中文:¥399.00
水合视角的注意点(4.1 的回访):服务端格式化用的 locale 来自请求(cookie 或 URL 前缀),客户端必须用同一来源,否则格式差异触发 mismatch——语言从 route 参数推导而不是浏览器嗅探,正是为了两端一致。
按"能感知、可操作、可理解、健壮"四原则过清单,配合 8.3 的 Accessibility 分类逐项修:
| 检查项 | 做法 | 常见扣分点 |
|---|---|---|
| 图片替代文本 | 有信息量的 img 必写 alt;装饰图 alt 置空 | 空图无 alt、alt 写文件名 |
| 表单标签 | label 与控件显式关联 | 只有 placeholder 没有 label |
| 键盘可达 | 全部交互可 Tab 触达、焦点顺序合理、模态锁焦点 | div 当按钮、自定义组件丢焦点 |
| 语义地标 | header/nav/main/footer 用语义标签 | 一律 div 加 class |
| 对比度 | 文本与背景至少四点五比一 | 浅灰字白底 |
| 动效尊重 | 减动效偏好下关闭非必要动画 | 强制入场动画 |
<template> <!-- 扣分写法:<div class="btn" @click="submit">提交</div> --> <!-- 合格写法:真按钮 + 可聚焦 + 状态可读 --> <button type="submit" :disabled="busy" :aria-busy="busy" > {{ busy ? '提交中…' : '提交订单' }} </button> </template>
修无障碍的最佳时机是写组件的时候——aria-* 与语义标签随组件一起落地,几乎零成本;事后回补则要在全站找交互元素,成本翻几倍。9.1 的 E2E 用角色选择器(getByRole)测试,本身就是无障碍的回归测试:角色选择器找不到的元素,屏幕阅读器多半也读不到。
读屏验证不必购买设备:系统自带的读屏(旁白、讲述人、屏幕阅读器)关掉显示器走一遍下单流程,比想象中更快暴露问题。8.3 归因图里 Accessibility 的扣分项(alt 缺失、表单无标签)用本节清单逐条核销即可。
⚠️ 常见坑:机器翻译批量生成语言文件直接上线。译文质量差损害品牌,RTL 语言的排版没人工过一遍必乱(标点、数字方向、混合文本)。每增加一门语言,留人工审校与 RTL 走查的预算。
💡 关键直觉:无障碍不是"照顾少数人"的公益项——键盘可达帮了快捷键用户,alt 文本喂了搜索引擎(8.2 的 SEO),对比度惠及强光下的每个手机用户。它是把"能用"扩展到"更多人能用"的通用质量。