Svelte 国际化实战指南:打造多语言应用的最佳实践

279 阅读6分钟

Svelte 国际化实战指南:打造多语言应用的最佳实践

🔥 在全球化时代,一款成功的应用需要能够与世界各地的用户交流。本文将带你一步步实现 Svelte 应用的国际化,从基础配置到高级特性,全面掌握 svelte-i18n

1. 引言:为什么选择 Svelte 做国际化?

在开发跨国应用时,国际化(i18n)和本地化(l10n)是不可忽视的环节。无论是电商平台、社交应用还是企业系统,提供多语言支持都能显著提升用户体验和市场覆盖率。

Svelte 作为一个新兴的前端框架,其编译时优化的特性使它在性能方面表现出色:

  • 更小的包体积:Svelte 在构建过程中将代码转换为原生 JS,减少了运行时开销
  • 更快的启动速度:相比 React 和 Vue 等框架,Svelte 应用启动时间更短
  • 反应式语法:简洁直观的代码风格,降低开发难度

虽然 Svelte 本身不提供内置的 i18n 支持,但我们可以借助 svelte-i18n 这个轻量级库轻松实现多语言功能。下面,让我们通过实际案例学习如何在 Svelte 中实现国际化。

2. 搭建基础项目

首先,我们需要创建一个 Svelte 项目并安装必要的依赖:

# 创建新的 Svelte 项目
npm create svelte@latest svelte-i18n-demo

# 进入项目目录
cd svelte-i18n-demo

# 安装依赖
npm install

# 安装 svelte-i18n 库
npm install svelte-i18n

创建项目时,我选择了以下配置(你可以根据自己的需求调整):

  • Skeleton project(骨架项目):是
  • TypeScript 支持:否(当然你也可以选是)
  • ESLint 代码检查:是
  • Prettier 代码格式化:是

3. 创建基础组件

先创建一个简单的欢迎组件,稍后我们会为其添加多语言支持。在 src 目录下创建 Welcome.svelte 文件:

<!-- src/Welcome.svelte -->
<script>
  export let username;
</script>

<div>Welcome {username}!</div>

这个组件接收一个 username 属性并显示欢迎信息。目前它只支持英文,但很快我们就会让它支持多种语言。

4. 添加多语言支持

4.1 创建翻译文件

为了支持多语言,我们需要为每种语言创建翻译文件。在 src 目录下创建 locales 文件夹,然后添加 en.jsonzh.json 两个文件:

// src/locales/en.json
{
  "hello": "Hello {username}!",
  "buttons": {
    "save": "Save",
    "cancel": "Cancel"
  },
  "items": {
    "count": "{count, plural, =0 {No items} one {1 item} other {{count} items}}"
  }
}
// src/locales/zh.json
{
  "hello": "你好 {username}!",
  "buttons": {
    "save": "保存",
    "cancel": "取消"
  },
  "items": {
    "count": "{count, plural, =0 {没有项目} other {{count} 个项目}}"
  }
}

注意这些文件的组织结构 - 我们使用嵌套对象来管理翻译字符串,这有助于随着应用规模增长时保持翻译的条理性。{username}{count} 是占位符,运行时会被实际值替换。

4.2 配置 i18n

接下来,我们需要创建 i18n 配置文件,告诉 Svelte 如何使用这些翻译:

// src/i18n.js
import { register, init, getLocaleFromNavigator } from 'svelte-i18n';

// 注册翻译文件
register('en', () => import('./locales/en.json'));
register('zh', () => import('./locales/zh.json'));

// 初始化 i18n
init({
  fallbackLocale: 'en',  // 当选定语言缺少翻译时使用的备选语言
  initialLocale: getLocaleFromNavigator(),  // 自动检测浏览器语言
});

我们注册了两个翻译文件(英文和中文),并设置了初始语言和备选语言。getLocaleFromNavigator() 函数会自动检测浏览器的语言设置,为用户提供最自然的体验。

4.3 更新组件使用翻译

现在,我们可以更新欢迎组件以使用翻译了:

<!-- src/Welcome.svelte -->
<script>
  import { _ } from 'svelte-i18n';
  export let username;
</script>

<div>{$_('hello', { username })}</div>

$_svelte-i18n 提供的特殊帮助函数,用于获取翻译字符串。当我们传递 { username } 作为第二个参数时,它会替换翻译字符串中的占位符。

4.4 创建语言切换器

接下来,我们需要一个组件让用户切换语言:

<!-- src/LanguageSelect.svelte -->
<script>
  import { locale } from 'svelte-i18n';

  const languages = [
    { code: 'en', name: 'English' },
    { code: 'zh', name: '中文' }
  ];
</script>

<div class="language-selector">
  {#each languages as { code, name }}
    <button 
      class:active={$locale === code}
      on:click={() => locale.set(code)}>
      {name}
    </button>
  {/each}
</div>

<style>
  .language-selector {
    margin: 1rem 0;
  }
  button {
    margin-right: 0.5rem;
    padding: 0.5rem 1rem;
    background-color: #f1f1f1;
    border: 1px solid #ddd;
    border-radius: 4px;
    cursor: pointer;
  }
  .active {
    background-color: #4c6ef5;
    color: white;
  }
</style>

我们创建了两个按钮,分别对应英文和中文。点击按钮时,locale.set() 函数会切换当前语言。按钮的样式会根据当前选中的语言动态变化。

4.5 组装应用

最后,我们需要在主应用中组装所有部分:

<!-- src/App.svelte -->
<script>
  import { onMount } from 'svelte';
  import { waitLocale } from 'svelte-i18n';
  import Welcome from './Welcome.svelte';
  import LanguageSelect from './LanguageSelect.svelte';
  import './i18n.js'; // 导入 i18n 配置
  
  const username = '开发者';
</script>

{#await waitLocale()}
  <p>加载中...</p>
{:then}
  <main>
    <h1>Svelte i18n 演示</h1>
    <LanguageSelect />
    <Welcome {username} />
  </main>
{/await}

<style>
  main {
    max-width: 800px;
    margin: 0 auto;
    padding: 2rem;
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
      Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
  }
  h1 {
    color: #333;
  }
</style>

waitLocale() 函数确保在显示内容之前加载翻译,这可以防止在应用首次加载时出现闪烁或缺少翻译的情况。

5. 高级国际化功能

随着应用的增长,你可能需要处理更复杂的国际化场景。让我们探索一些常见的高级需求。

5.1 格式化数字和货币

不同国家/地区对数字和货币的格式有不同的处理方式。例如:

  • 美国:$1,234.56
  • 中国:¥1,234.56
  • 德国:1.234,56 €

我们可以使用 Intl.NumberFormat API 处理这些差异:

// src/lib/formatUtils.js
export function formatNumber(number, locale) {
  return new Intl.NumberFormat(locale).format(number);
}

export function formatCurrency(amount, locale, currency = 'CNY') {
  return new Intl.NumberFormat(locale, {
    style: 'currency',
    currency
  }).format(amount);
}

创建一个价格展示组件:

<!-- src/PriceDisplay.svelte -->
<script>
  import { _ } from 'svelte-i18n';
  import { locale } from 'svelte-i18n';
  import { formatCurrency } from './lib/formatUtils';
  
  export let price = 1234.56;
</script>

<div>
  <p>{$_('productPrice')}: {formatCurrency(price, $locale)}</p>
</div>

5.2 格式化日期

日期格式在不同地区也有显著差异:

  • 美国:MM/DD/YYYY (例如 09/23/2023)
  • 中国:YYYY-MM-DD (例如 2023-09-23)
  • 德国:DD.MM.YYYY (例如 23.09.2023)

使用 Intl.DateTimeFormat API 可以轻松处理:

// src/lib/dateUtils.js
export function formatDate(date, locale) {
  return new Intl.DateTimeFormat(locale, {
    year: 'numeric',
    month: 'long',
    day: 'numeric'
  }).format(date);
}

创建日期展示组件:

<!-- src/DateDisplay.svelte -->
<script>
  import { locale } from 'svelte-i18n';
  import { formatDate } from './lib/dateUtils';
  
  let today = new Date();
</script>

<p>今日日期: {formatDate(today, $locale)}</p>

5.3 处理复数形式

不同语言的复数规则差异很大。例如,英语有两种形式(单数和复数),而阿拉伯语有六种复数形式,中文则没有明显的复数变化。

我们可以使用 svelte-i18n 的 ICU 消息语法处理这些差异:

<!-- src/ItemCounter.svelte -->
<script>
  import { _ } from 'svelte-i18n';
  let count = 0;
</script>

<p>{$_('items.count', { count })}</p>
<div class="counter-buttons">
  <button on:click={() => count++}>添加项目</button>
  <button on:click={() => count--} disabled={count === 0}>移除项目</button>
</div>

<style>
  .counter-buttons {
    margin-top: 0.5rem;
  }
  button {
    margin-right: 0.5rem;
    padding: 0.5rem 1rem;
    background-color: #f1f1f1;
    border: 1px solid #ddd;
    border-radius: 4px;
    cursor: pointer;
  }
  button:hover:not([disabled]) {
    background-color: #e1e1e1;
  }
  button:disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }
</style>

5.4 本地化图片

国际化不仅仅是翻译文本,有时还需要根据不同地区提供不同的图片,例如不同的营销图片、地图或者文化符号。

// src/locales/en.json
{
  "images": {
    "logo": "/images/en/logo.png",
    "banner": "/images/en/banner.jpg",
    "logoAlt": "Company Logo"
  }
}

// src/locales/zh.json
{
  "images": {
    "logo": "/images/zh/logo.png",
    "banner": "/images/zh/banner.jpg",
    "logoAlt": "公司标志"
  }
}

使用动态图片路径:

<!-- src/LocalizedImage.svelte -->
<script>
  import { _ } from 'svelte-i18n';
  export let imageKey = "logo"; // 默认为logo
</script>

<img src={$_(`images.${imageKey}`)} alt={$_('images.logoAlt')} />

5.5 处理缺失的翻译

随着应用功能的增长,有时可能会遗漏某些翻译。为了优雅地处理这种情况,我们可以设置 missingKeyHandler 选项:

// src/i18n.js 修改版
import { register, init, getLocaleFromNavigator } from 'svelte-i18n';

register('en', () => import('./locales/en.json'));
register('zh', () => import('./locales/zh.json'));

init({
  fallbackLocale: 'en',
  initialLocale: getLocaleFromNavigator(),
  missingKeyHandler: (locale, key) => {
    console.warn(`缺少翻译: ${key} (${locale})`);
    return key; // 当翻译缺失时显示键名
  }
});

这段代码会在控制台记录警告,并显示键名而不是空白内容,方便开发过程中发现问题。

6. 生产环境优化

6.1 自动检测用户语言

我们已经在初始化时使用了 getLocaleFromNavigator() 函数自动检测用户语言。但在实际应用中,还可能需要考虑以下因素:

  1. 用户偏好: 允许用户选择偏好语言并保存在 localStorage 中
  2. 地理位置: 根据用户 IP 判断可能的语言偏好
  3. URL 参数: 支持通过 URL 参数切换语言,便于分享特定语言的链接
// src/i18n.js 增强版
import { register, init, getLocaleFromNavigator } from 'svelte-i18n';

register('en', () => import('./locales/en.json'));
register('zh', () => import('./locales/zh.json'));

// 获取用户首选语言
function getUserPreferredLocale() {
  // 1. 首先检查 URL 参数
  const urlParams = new URLSearchParams(window.location.search);
  const urlLocale = urlParams.get('lang');
  if (urlLocale && ['en', 'zh'].includes(urlLocale)) {
    return urlLocale;
  }
  
  // 2. 然后检查 localStorage
  const savedLocale = localStorage.getItem('preferred-language');
  if (savedLocale && ['en', 'zh'].includes(savedLocale)) {
    return savedLocale;
  }
  
  // 3. 最后使用浏览器语言
  return getLocaleFromNavigator();
}

// 监听语言变化并保存到localStorage
import { locale } from 'svelte-i18n';
locale.subscribe(value => {
  if (value) {
    localStorage.setItem('preferred-language', value);
  }
});

init({
  fallbackLocale: 'en',
  initialLocale: getUserPreferredLocale(),
});

6.2 懒加载翻译文件

当支持的语言较多时,我们可以采用懒加载策略,只加载当前需要的语言包:

// src/i18n.js 懒加载版
import { register, init, locale } from 'svelte-i18n';

// 预先只加载英文和中文
const preloadedLocales = ['en', 'zh'];

// 预注册所有支持的语言
register('en', () => import('./locales/en.json'));
register('zh', () => import('./locales/zh.json'));
register('fr', () => import('./locales/fr.json'));
register('de', () => import('./locales/de.json'));
register('ja', () => import('./locales/ja.json'));
// ... 更多语言

// 初始化
init({
  fallbackLocale: 'en',
  initialLocale: 'en', // 先设置为英文
});

// 检测用户语言并异步加载
const userLocale = getUserPreferredLocale();
if (!preloadedLocales.includes(userLocale)) {
  locale.set(userLocale); // 这会触发懒加载
}

7. 最佳实践与扩展性建议

随着项目规模扩大,国际化管理可能变得复杂。以下是一些最佳实践:

7.1 组织翻译文件

  • 逻辑分组: 按功能或页面组织翻译,如 auth.login, profile.settings
  • 命名一致性: 使用一致的键名模式,避免重复
  • 注释: 在翻译文件中添加注释,解释上下文或特殊格式

7.2 使用翻译管理平台

随着应用规模增长,手动管理翻译文件会变得繁琐。可以考虑使用专业的翻译管理平台,如 Locize、Lokalise 或 Crowdin。

这些平台提供以下优势:

  • 团队协作翻译
  • 版本控制
  • 自动同步到代码库
  • 实时翻译更新
  • 翻译进度追踪

7.3 全面测试

  • 模拟多语言环境: 在不同语言设置下测试应用
  • 文本长度变化: 特别注意某些语言(如德语)文本可能比其他语言长很多
  • RTL 支持: 测试阿拉伯语等从右到左书写的语言
  • 字符编码: 确保正确处理特殊字符和非拉丁字母

7.4 文化考虑

除了语言翻译外,还要考虑文化差异:

  • 颜色含义在不同文化中可能有不同
  • 图片和图标可能需要根据文化背景调整
  • 某些手势或符号在特定文化中可能有负面含义

8. 实战完整示例

让我们整合前面的所有内容,创建一个功能齐全的示例:

<!-- src/App.svelte -->
<script>
  import { onMount } from 'svelte';
  import { waitLocale, _, locale } from 'svelte-i18n';
  import './i18n.js';
  
  import Welcome from './Welcome.svelte';
  import LanguageSelect from './LanguageSelect.svelte';
  import ItemCounter from './ItemCounter.svelte';
  import PriceDisplay from './PriceDisplay.svelte';
  import DateDisplay from './DateDisplay.svelte';
  import LocalizedImage from './LocalizedImage.svelte';
  
  const username = '开发者';
  let loaded = false;
  
  onMount(async () => {
    await waitLocale();
    loaded = true;
  });
</script>

{#if !loaded}
  <div class="loading">
    <span>加载中...</span>
  </div>
{:else}
  <main>
    <h1>Svelte i18n 示例应用</h1>
    
    <section class="language-section">
      <h2>{$_('selectLanguage')}</h2>
      <LanguageSelect />
    </section>
    
    <section>
      <Welcome {username} />
    </section>
    
    <section>
      <h2>{$_('productDetails')}</h2>
      <PriceDisplay price={1299.99} />
      <LocalizedImage />
    </section>
    
    <section>
      <h2>{$_('dateFormatting')}</h2>
      <DateDisplay />
    </section>
    
    <section>
      <h2>{$_('pluralization')}</h2>
      <ItemCounter />
    </section>
  </main>
{/if}

<style>
  .loading {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    font-size: 1.5rem;
  }
  main {
    max-width: 800px;
    margin: 0 auto;
    padding: 2rem;
    font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  }
  section {
    margin-bottom: 2rem;
    padding: 1.5rem;
    border: 1px solid #eaeaea;
    border-radius: 8px;
  }
  .language-section {
    background-color: #f8f9fa;
  }
  h1 {
    color: #333;
    text-align: center;
    margin-bottom: 2rem;
  }
  h2 {
    color: #555;
    margin-top: 0;
  }
</style>

9. 结语

在 Svelte 中实现国际化比你想象的更简单。通过 svelte-i18n,你可以轻松构建多语言应用,从基本的文本翻译到复杂的日期、货币格式化和复数处理。

总结几点关键要点:

  1. 从小开始: 先添加基本翻译和语言切换功能,随着应用发展再添加高级特性
  2. 关注用户体验: 自动检测用户语言,记住用户偏好
  3. 考虑文化因素: 不仅仅是翻译文本,还要适应不同文化的需求
  4. 优化性能: 懒加载翻译文件,减少初始加载时间
  5. 持续完善: 收集用户反馈,不断改进翻译质量和覆盖面

通过这些步骤,你可以创建真正全球化的 Svelte 应用,为世界各地的用户提供自然、友好的体验。

希望本指南对你有所帮助,祝你的 Svelte 国际化之旅顺利!


作者:[corn]
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。