国际化
ubean 在 Vue 侧使用 vue-i18n 11(Composition API,必须 legacy: false),在 Hono handler 中使用 @intlify/core。语言路由是 约束前缀:由 compileLocalePaths() 编译一次,vue-router 与 Hono 共用同一张 path 表。
useI18n 直接从 vue-i18n 导入(自动导入需在 ubean.config.ts 设置 autoImports: { vueI18n: true },默认关闭;否则请显式导入),t 从其返回的 composer 解构;setLocale 等 ubean 封装从 ubean/client 导入。
配置
全部写在 ubean.config.ts。检测中间件由 createUbeanApp 自动挂载,不需要 src/middleware/02.i18n.ts。
// ubean.config.ts
import { defineConfig } from 'ubean';
export default defineConfig({
i18n: {
defaultLocale: 'en',
locales: [
{ code: 'en', language: 'en', name: 'English' },
{ code: 'zh', language: 'zh-CN', name: '简体中文' }
],
strategy: 'prefix_except_default',
baseUrl: 'https://example.com',
detectBrowserLanguage: {
cookieName: 'ubean_locale',
redirectOn: 'root' // 默认
},
vueI18n: {
fallbackLocale: 'en'
}
}
});locales: ['en', 'zh'] 会正规化为 { code } 对象。i18n: false(或 locales 为空)关闭路由改写和中间件。
language 是 BCP 47,用于 <html lang> 和 hreflang。locale code 是 URL 保留段:若 zh 是语言 code,pages/[id].vue 不会把 /zh 当成 id。
文案文件
约定目录 src/locales/**/*.{json,json5,yaml,yml,ts,js}。消息语法是 Intlify(不是 ICU):{name}、@:key、a | b | {n} c。
// src/locales/en.json
{
"hello": "Hello",
"welcome": "Welcome to ubean",
"greeting": "Hello, {name}!",
"items": "no items | one item | {count} items",
"app": { "name": "ubean" },
"linked": "Welcome to @:app.name!"
}通过 loadLocale(code) 懒加载。SSR 只预载当前 locale + fallback;__UBEAN_LOCALE__ 只序列化这两份。
Vue 用法
<script setup lang="ts">
const { t, d, n, locale } = useI18n();
async function toZh() {
await setLocale('zh'); // 加载文案 + 写 cookie + router.replace
}
</script>
<template>
<h1>{{ t('hello') }}</h1>
<p>{{ t('greeting', { name: 'World' }) }}</p>
<button @click="toZh">中文</button>
<p>当前:{{ locale }}</p>
</template>切换语言必须走框架 setLocale,不要直接赋 locale.value = 'zh'。setLocale 会加载文案、写 cookie、再导航(no_prefix 跳过导航)。
语言切换按钮应遍历 配置里的 locales(getI18nRuntimeConfig()?.locales),不要用 vue-i18n 的 availableLocales(水合后通常只有当前 + fallback)。
<script setup lang="ts">
const codes = getI18nRuntimeConfig()?.locales ?? [];
</script>
<template>
<button v-for="code in codes" :key="code" @click="setLocale(code)">
{{ code }}
</button>
</template>路径与 <Link>
<script setup lang="ts">
const localePath = useLocalePath();
const switchLocalePath = useSwitchLocalePath();
</script>
<template>
<Link to="/about">About</Link>
<!-- zh → /zh/about ;默认 en → /about -->
<Link to="/about" locale="en">English About</Link>
</template>useLocalePath('/about', 'zh') → /zh/about。useSwitchLocalePath('zh') 改写当前路由。
类型化 key
ubean prepare / dev / build 会根据默认 locale 的 JSON 生成 .ubean/i18n.d.ts(DefineLocaleMessage),此时 t('missing') 会报类型错误。也可以手写 module augmentation:
declare module 'vue-i18n' {
export interface DefineLocaleMessage {
hello: string;
}
}API / handler
handler 里从 ubean/i18n 导入的 t() / d() / n() 读请求 AsyncLocalStorage。没有请求作用域时抛错,禁止回落进程全局 locale。
import { defineHandler } from 'ubean/server';
import { t, getRequestLocale } from 'ubean/i18n';
export const GET = defineHandler(c => {
const locale = getRequestLocale(c);
return c.json({ locale, hello: t('hello') });
});/api/、/_、/__ 仍进入 ALS(cookie / Accept-Language),但不改 URL。
Cloudflare Workers 需要 nodejs_compat 才能用 AsyncLocalStorage。ALS 不可用时 t() 抛错;请改读 c.get('locale')。
路由策略
Hono 与 vue-router 共用同一编译结果(defaultLocale: 'en',locales en, zh,页面 /about):
| strategy | vue-router | Hono |
|---|---|---|
prefix_except_default(默认) | /:locale(zh)?/about | /about、/zh/about(无 /en/about) |
prefix | /:locale(en|zh)/about | /en/about、/zh/about |
prefix_and_default | /:locale(en|zh)?/about | /about、/en/about、/zh/about |
no_prefix | /about | /about |
检测顺序(有前缀的 strategy):URL 前缀 → cookie → Accept-Language → defaultLocale。Cookie 会写入。默认 redirectOn: 'root':只有 / 会因 header 302;/about 不会。prefix 策略访问无前缀内容时无论 redirectOn 都会 302。
SEO
SSR HTML 自动设置:
<html lang>/<html dir>link rel="alternate" hreflang(含语言组 catchall 与x-default)canonical(prefix_and_default下默认语言指向 无前缀 URL)og:locale/og:locale:alternate
客户端切换后可用 useLocaleHead() 更新 head。请配置 i18n.baseUrl 以生成绝对 hreflang。