simplebank前端国际化:i18n与动态语言切换实现

simplebank前端国际化:i18n与动态语言切换实现

【免费下载链接】simplebank Backend master class: build a simple bank service in Go 【免费下载链接】simplebank 项目地址: https://gitcode.com/GitHub_Trending/si/simplebank

引言:全球化金融服务的本地化挑战

在当今数字化金融时代,SimpleBank作为一款面向全球用户的银行服务(Backend master class: build a simple bank service in Go),其前端应用的国际化(Internationalization,简称i18n)能力已成为提升用户体验的关键因素。用户可能来自不同国家和地区,说着不同的语言,有着不同的文化习惯。因此,实现一个灵活、高效的国际化解决方案,支持多语言切换和本地化内容展示,对于SimpleBank吸引全球用户、拓展国际市场至关重要。

本文将详细介绍如何为SimpleBank前端应用集成国际化功能,包括i18n库的选择与配置、翻译文件的组织、组件中的国际化实践以及动态语言切换的实现。通过本文的指南,你将能够为SimpleBank构建一个专业、易用的国际化前端界面,满足不同语言背景用户的需求。

技术选型:为何选择vue-i18n

SimpleBank前端采用Vue 3 + TypeScript技术栈,结合PrimeVue组件库构建用户界面。在众多国际化解决方案中,vue-i18n 是Vue.js官方推荐的国际化插件,具有以下显著优势,使其成为SimpleBank前端国际化的理想选择:

  1. 与Vue生态深度集成:vue-i18n专为Vue.js设计,能够与Vue的响应式系统、组合式API等无缝协作,提供自然的开发体验。
  2. 全面的国际化特性:支持文本翻译、复数形式、日期时间格式化、数字格式化、货币格式化等多种国际化需求,完全满足银行类应用的复杂场景。
  3. TypeScript支持:提供完善的TypeScript类型定义,有助于在开发阶段捕获潜在错误,提升代码质量和可维护性。
  4. 丰富的API与灵活的配置:提供多种翻译方式(如$t函数、指令、组合式API)、命名空间、模块系统等,可根据项目结构灵活组织翻译资源。
  5. 性能优化:内置缓存机制,减少不必要的翻译计算,提升应用性能。

考虑到SimpleBank前端的技术栈和未来的扩展性,vue-i18n是目前最适合的国际化解决方案。

环境准备与依赖安装

在开始集成i18n功能之前,我们需要确保前端项目环境正确,并安装必要的依赖。

检查现有项目依赖

从SimpleBank前端项目的package.json文件中可以看到,当前项目已包含Vue 3、TypeScript、Vue Router和PrimeVue等核心依赖:

{
  "name": "frontend",
  "version": "0.0.0",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "run-p type-check \"build-only {@}\" --",
    "preview": "vite preview",
    "test:unit": "vitest",
    "build-only": "vite build",
    "type-check": "vue-tsc --build --force",
    "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
    "format": "prettier --write src/"
  },
  "dependencies": {
    "axios": "^1.6.8",
    "primeflex": "^3.3.1",
    "primeicons": "^7.0.0",
    "primevue": "^3.51.0",
    "vue": "^3.4.21",
    "vue-router": "^4.3.0"
  },
  "devDependencies": {
    "@rushstack/eslint-patch": "^1.8.0",
    "@tsconfig/node20": "^20.1.4",
    "@types/jsdom": "^21.1.6",
    "@types/node": "^20.12.5",
    "@vitejs/plugin-vue": "^5.0.4",
    "@vitejs/plugin-vue-jsx": "^3.1.0",
    "@vue/eslint-config-prettier": "^9.0.0",
    "@vue/eslint-config-typescript": "^13.0.0",
    "@vue/test-utils": "^2.4.5",
    "@vue/tsconfig": "^0.5.1",
    "eslint": "^8.57.0",
    "eslint-plugin-vue": "^9.23.0",
    "jsdom": "^24.0.0",
    "npm-run-all2": "^6.2.6",
    "prettier": "^3.2.5",
    "typescript": "~5.4.0",
    "vite": "^5.2.8",
    "vitest": "^1.4.0",
    "vue-tsc": "^2.0.11"
  }
}

目前项目中尚未包含vue-i18n依赖,因此我们需要进行安装。

安装vue-i18n

打开终端,在SimpleBank前端项目根目录(frontend/)下执行以下命令安装vue-i18n:

npm install vue-i18n@9 --save

版本说明:我们安装vue-i18n v9版本,该版本对应Vue 3。如果使用的是Vue 2,则应安装vue-i18n v8版本。

安装完成后,package.jsondependencies中会新增vue-i18n条目。

国际化架构设计

一个完善的国际化架构应包括以下几个核心部分:

  1. 翻译文件:存储不同语言的文本翻译。
  2. i18n实例配置:初始化vue-i18n,设置语言、地区、翻译文件等。
  3. 翻译加载策略:如何加载和管理翻译文件,支持懒加载或预加载。
  4. 组件国际化:在Vue组件中使用翻译功能。
  5. 动态语言切换:允许用户在运行时切换界面语言。
  6. 本地化格式化:处理日期、时间、数字、货币等的本地化显示。

下面我们将逐一实现这些部分。

翻译文件组织

翻译文件的组织方式对项目的可维护性至关重要。推荐采用以下结构:

frontend/
└── src/
    └── locales/          # 国际化根目录
        ├── index.ts      # i18n配置入口
        ├── en/           # 英语翻译
        │   ├── index.ts
        │   ├── common.ts # 通用翻译 (按钮、标签等)
        │   ├── login.ts  # 登录相关翻译
        │   └── home.ts   # 首页相关翻译
        ├── zh-CN/        # 简体中文翻译
        │   ├── index.ts
        │   ├── common.ts
        │   ├── login.ts
        │   └── home.ts
        └── ja/           # 日语翻译 (可根据需求添加更多语言)
            ├── index.ts
            ├── common.ts
            ├── login.ts
            └── home.ts

这种按语言和功能模块拆分的方式,有利于多人协作和翻译内容的维护。

创建翻译文件示例

下面创建几个核心的翻译文件示例:

src/locales/en/common.ts (英语通用翻译)

export default {
  welcome: 'Welcome to Simple Bank!',
  login: 'Login',
  logout: 'Logout',
  username: 'Username',
  password: 'Password',
  submit: 'Submit',
  cancel: 'Cancel',
  success: 'Success',
  error: 'Error',
  info: 'Information',
  warning: 'Warning',
  hello: 'Hello',
  goodbye: 'Goodbye',
  you_have_successfully_logged_in: 'You have successfully logged in.',
  you_have_successfully_logged_out: 'You have successfully logged out.',
  an_error_occurred_please_try_again_later: 'An error occurred. Please try again later.'
};

src/locales/zh-CN/common.ts (简体中文通用翻译)

export default {
  welcome: '欢迎使用Simple Bank!',
  login: '登录',
  logout: '退出',
  username: '用户名',
  password: '密码',
  submit: '提交',
  cancel: '取消',
  success: '成功',
  error: '错误',
  info: '信息',
  warning: '警告',
  hello: '你好',
  goodbye: '再见',
  you_have_successfully_logged_in: '登录成功。',
  you_have_successfully_logged_out: '退出成功。',
  an_error_occurred_please_try_again_later: '发生错误,请稍后再试。'
};

src/locales/en/login.ts (英语登录相关翻译)

export default {
  login_title: 'Account Login',
  username_placeholder: 'Enter your username',
  password_placeholder: 'Enter your password',
  login_failed: 'Login failed',
  invalid_credentials: 'Invalid username or password'
};

src/locales/zh-CN/login.ts (简体中文登录相关翻译)

export default {
  login_title: '账户登录',
  username_placeholder: '请输入用户名',
  password_placeholder: '请输入密码',
  login_failed: '登录失败',
  invalid_credentials: '用户名或密码错误'
};

src/locales/en/home.ts (英语首页相关翻译)

export default {
  home_title: 'Simple Bank Dashboard',
  account_balance: 'Account Balance',
  recent_transactions: 'Recent Transactions',
  welcome_back: 'Welcome back, {name}!'
};

src/locales/zh-CN/home.ts (简体中文首页相关翻译)

export default {
  home_title: 'Simple Bank 控制台',
  account_balance: '账户余额',
  recent_transactions: '最近交易',
  welcome_back: '欢迎回来,{name}!'
};

语言入口文件

为每种语言创建一个入口文件,合并该语言的所有翻译模块:

src/locales/en/index.ts

import common from './common';
import login from './login';
import home from './home';

export default {
  common,
  login,
  home
};

src/locales/zh-CN/index.ts

import common from './common';
import login from './login';
import home from './home';

export default {
  common,
  login,
  home
};

i18n实例配置

接下来,我们需要创建i18n实例并进行配置,使其能够加载上述翻译文件,并与Vue应用集成。

创建i18n配置文件

创建src/locales/index.ts文件,用于初始化i18n实例:

import { createI18n } from 'vue-i18n';
import en from './en';
import zhCN from './zh-CN';
// 如需支持更多语言,可以继续导入
// import ja from './ja';

// 定义支持的语言
export const SUPPORTED_LANGUAGES = {
  'en': 'English',
  'zh-CN': '简体中文'
  // 'ja': '日本語'
};

// 默认语言
export const DEFAULT_LANGUAGE = 'en';

// 翻译消息对象
const messages = {
  'en': en,
  'zh-CN': zhCN
  // 'ja': ja
};

// 检测用户浏览器语言
const detectUserLanguage = () => {
  const browserLang = navigator.language || navigator.userLanguage;
  const langCode = browserLang.split('-')[0]; // 例如 'zh-CN' -> 'zh'

  // 检查是否有完全匹配的语言
  if (Object.keys(SUPPORTED_LANGUAGES).includes(browserLang)) {
    return browserLang;
  }

  // 检查是否有语言代码前缀匹配的语言 (如 'zh' 匹配 'zh-CN')
  for (const lang of Object.keys(SUPPORTED_LANGUAGES)) {
    if (lang.startsWith(langCode)) {
      return lang;
    }
  }

  // 默认返回英语
  return DEFAULT_LANGUAGE;
};

// 创建i18n实例
const i18n = createI18n({
  legacy: false, // 使用Composition API,必须设置为false
  locale: detectUserLanguage(), // 从浏览器检测或默认语言
  fallbackLocale: DEFAULT_LANGUAGE, // 回退语言
  messages: messages, // 翻译消息
  silentTranslationWarn: false, // 翻译缺失时是否警告
  missingWarn: true,
  fallbackWarn: true
});

export default i18n;

在main.ts中集成i18n

修改src/main.ts,将i18n实例注入Vue应用:

import './assets/main.css';

import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import PrimeVue from 'primevue/config';
import ToastService from 'primevue/toastservice';
import i18n from './locales'; // 导入i18n实例

const app = createApp(App);

app.use(router);
app.use(PrimeVue);
app.use(ToastService);
app.use(i18n); // 使用i18n

app.mount('#app');

组件中的国际化实践

完成i18n的基本配置后,我们可以在Vue组件中使用国际化功能了。vue-i18n提供了多种在组件中使用翻译的方式,包括组合式API、选项式API和模板指令。

<script setup>中使用组合式API

在Vue 3的组合式API中,可以使用useI18n函数来获取翻译相关的函数和属性:

import { useI18n } from 'vue-i18n';

const { t, locale, availableLocales } = useI18n();

// 使用t函数进行翻译
const welcomeMessage = t('common.welcome');

// 获取当前语言
const currentLocale = locale;

// 获取可用语言
const languages = availableLocales;

在模板中使用$t函数或v-t指令

在模板中,可以直接使用$t函数(当legacy: false时,在模板中可直接访问)或v-t指令:

<!-- 使用$t函数 -->
<h1>{{ $t('common.welcome') }}</h1>

<!-- 使用v-t指令 -->
<h1 v-t="'common.welcome'"></h1>

<!-- 带参数的翻译 -->
<p>{{ $t('home.welcome_back', { name: user.full_name }) }}</p>

登录组件国际化改造 (LoginUser.vue)

src/components/LoginUser.vue为例,进行国际化改造:

<script setup lang="ts">
import InputGroup from 'primevue/inputgroup';
import InputGroupAddon from 'primevue/inputgroupaddon';
import FloatLabel from 'primevue/floatlabel';
import InputText from 'primevue/inputtext';
import Button from 'primevue/button';
import { useToast } from 'primevue/usetoast';
import { ref } from 'vue';
import axios from 'axios';
import type { User } from '@/types/user';
import store from '@/store';
import { useI18n } from 'vue-i18n'; // 导入useI18n

const { t } = useI18n(); // 获取t函数

interface LoginResponse {
  user: User;
  access_token: string;
  refresh_token: string;
}

const username = ref<string>('');
const password = ref<string>('');
const errorMessage = ref<string>('');
const toast = useToast();

const handleLogin = async () => {
  try {
    const response = await axios.post<LoginResponse>(
      'http://localhost:8080/v1/login_user',
      {
        username: username.value,
        password: password.value
      },
      {
        headers: {
          'Content-Type': 'application/json',
          Authorization: 'none'
        }
      }
    );

    store.setUser(response.data.user, response.data.access_token, response.data.refresh_token);
    toast.add({
      severity: 'success',
      summary: t('common.hello', { name: response.data.user.full_name }), // 国际化标题
      detail: t('common.you_have_successfully_logged_in'), // 国际化详情
      life: 3000
    });
  } catch (error: any) {
    if (error.response && error.response.status === 404) {
      errorMessage.value = error.response.data.message;
    } else {
      errorMessage.value = t('common.an_error_occurred_please_try_again_later'); // 国际化错误消息
    }

    toast.add({
      severity: 'error',
      summary: t('login.login_failed'), // 国际化标题
      detail: errorMessage.value,
      life: 3000
    });
  }
};
</script>

<template>
  <div class="flex flex-column row-gap-5">
    <h2>{{ $t('login.login_title') }}</h2> <!-- 国际化标题 -->
    <InputGroup>
      <InputGroupAddon>
        <i class="pi pi-user"></i>
      </InputGroupAddon>
      <FloatLabel>
        <InputText id="username" v-model="username" />
        <label for="username">{{ $t('common.username') }}</label> <!-- 国际化标签 -->
      </FloatLabel>
    </InputGroup>
    <InputGroup>
      <InputGroupAddon>
        <i class="pi pi-lock"></i>
      </InputGroupAddon>
      <FloatLabel>
        <InputText id="password" type="password" v-model="password" />
        <label for="password">{{ $t('common.password') }}</label> <!-- 国际化标签 -->
      </FloatLabel>
    </InputGroup>
    <Button 
      label="$t('common.login')" 
      @click="handleLogin" 
      :aria-label="$t('common.login')"
    /> <!-- 国际化按钮文本和aria-label -->
  </div>
</template>

首页组件国际化改造 (HomeView.vue)

改造src/views/HomeView.vue

<script setup lang="ts">
import UserInfo from '@/components/UserInfo.vue';
import LoginUser from '../components/LoginUser.vue';
import store from '../store';
import type { User } from '@/types/user';
import Toast from 'primevue/toast';
import { useToast } from 'primevue/usetoast';
import { useI18n } from 'vue-i18n'; // 导入useI18n

const { t } = useI18n(); // 获取t函数
const toast = useToast();

const onLogout = (user: User) => {
  toast.add({
    severity: 'success',
    summary: t('common.goodbye', { name: user.full_name }), // 国际化标题
    detail: t('common.you_have_successfully_logged_out'), // 国际化详情
    life: 3000
  });
  store.clearUser();
};
</script>

<template>
  <main>
    <Toast />
    <h1 class="green">{{ $t('common.welcome') }}</h1> <!-- 国际化欢迎语 -->
    <UserInfo v-if="store.state.user" :user="store.state.user" @logout="onLogout" />
    <LoginUser v-else />
  </main>
</template>

<style scoped>
h1 {
  font-weight: 500;
  font-size: 2.6rem;
}
</style>

用户信息组件国际化改造 (UserInfo.vue)

改造src/components/UserInfo.vue

<script setup lang="ts">
import Card from 'primevue/card';
import Divider from 'primevue/divider';
import Button from 'primevue/button';
import type { PropType } from 'vue';
import type { User } from '@/types/user';
import { useI18n } from 'vue-i18n'; // 导入useI18n

const { t } = useI18n(); // 获取t函数

const props = defineProps({
  user: {
    type: Object as PropType<User>,
    required: true
  }
});

const emit = defineEmits<{
  (e: 'logout', user: User): void;
}>();

const onLogout = () => emit('logout', props.user);
</script>

<template>
  <Card>
    <template #title>{{ $t('home.home_title') }}</template> <!-- 国际化卡片标题 -->
    <template #content>
      <div class="flex flex-column row-gap-2">
        <div>
          <i class="pi pi-user" />
          <span class="m-2">{{ user.full_name }}</span>
        </div>
        <div>
          <i class="pi pi-envelope" />
          <span class="m-2">
            <a :href="`mailto:${user.email}`">{{ user.email }}</a>
          </span>
        </div>
      </div>
      <Divider />
      <Button 
        :label="$t('common.logout')" 
        icon="pi pi-sign-out" 
        @click="onLogout"
        :aria-label="$t('common.logout')"
      /> <!-- 国际化按钮 -->
    </template>
  </Card>
</template>

动态语言切换实现

动态语言切换允许用户在不刷新页面的情况下切换界面语言。实现这一功能需要以下几个步骤:

  1. 创建一个语言选择组件。
  2. 在组件中通过i18n实例的locale属性切换语言。
  3. 可选:将用户选择的语言偏好保存到本地存储(localStorage),以便下次访问时自动应用。

创建语言选择组件 (LanguageSelector.vue)

创建src/components/LanguageSelector.vue

<script setup lang="ts">
import { useI18n } from 'vue-i18n';
import Dropdown from 'primevue/dropdown';
import { ref, watch } from 'vue';
import { SUPPORTED_LANGUAGES } from '@/locales';

const { locale } = useI18n();
const selectedLanguage = ref(locale.value);

// 监听语言变化,保存到localStorage
watch(
  () => selectedLanguage.value,
  (newLang) => {
    locale.value = newLang;
    localStorage.setItem('simplebank_preferred_language', newLang);
  }
);

// 将支持的语言转换为Dropdown所需的选项格式
const languageOptions = Object.entries(SUPPORTED_LANGUAGES).map(([code, name]) => ({
  value: code,
  label: name
}));

// 从localStorage加载保存的语言偏好
const savedLang = localStorage.getItem('simplebank_preferred_language');
if (savedLang && Object.keys(SUPPORTED_LANGUAGES).includes(savedLang)) {
  selectedLanguage.value = savedLang;
}
</script>

<template>
  <div class="language-selector">
    <Dropdown
      v-model="selectedLanguage"
      :options="languageOptions"
      :placeholder="$t('common.select_language')"
      class="w-full md:w-48"
      :showClear="false"
    />
  </div>
</template>

<style scoped>
.language-selector {
  position: absolute;
  top: 1rem;
  right: 1rem;
}
</style>

注意:需要在翻译文件中添加common.select_language的翻译,例如:

  • en/common.ts: select_language: 'Select Language'
  • zh-CN/common.ts: select_language: '选择语言'

更新i18n配置以支持从localStorage加载语言

修改src/locales/index.ts中的detectUserLanguage函数,使其优先从localStorage加载用户保存的语言偏好:

const detectUserLanguage = () => {
  // 1. 优先从localStorage加载
  const savedLang = localStorage.getItem('simplebank_preferred_language');
  if (savedLang && Object.keys(SUPPORTED_LANGUAGES).includes(savedLang)) {
    return savedLang;
  }

  // 2. 检测浏览器语言
  const browserLang = navigator.language || navigator.userLanguage;
  const langCode = browserLang.split('-')[0];

  // 检查是否有完全匹配的语言
  if (Object.keys(SUPPORTED_LANGUAGES).includes(browserLang)) {
    return browserLang;
  }

  // 检查是否有语言代码前缀匹配的语言
  for (const lang of Object.keys(SUPPORTED_LANGUAGES)) {
    if (lang.startsWith(langCode)) {
      return lang;
    }
  }

  // 3. 默认返回英语
  return DEFAULT_LANGUAGE;
};

在首页集成语言选择器

修改src/views/HomeView.vue,添加语言选择器:

<script setup lang="ts">
import UserInfo from '@/components/UserInfo.vue';
import LoginUser from '../components/LoginUser.vue';
import store from '../store';
import type { User } from '@/types/user';
import Toast from 'primevue/toast';
import { useToast } from 'primevue/usetoast';
import { useI18n } from 'vue-i18n';
import LanguageSelector from '../components/LanguageSelector.vue'; // 导入语言选择器

const { t } = useI18n();
const toast = useToast();

const onLogout = (user: User) => {
  toast.add({
    severity: 'success',
    summary: t('common.goodbye', { name: user.full_name }),
    detail: t('common.you_have_successfully_logged_out'),
    life: 3000
  });
  store.clearUser();
};
</script>

<template>
  <main>
    <Toast />
    <LanguageSelector /> <!-- 添加语言选择器 -->
    <h1 class="green">{{ $t('common.welcome') }}</h1>
    <UserInfo v-if="store.state.user" :user="store.state.user" @logout="onLogout" />
    <LoginUser v-else />
  </main>
</template>

<style scoped>
h1 {
  font-weight: 500;
  font-size: 2.6rem;
  margin-top: 2rem; /* 为语言选择器腾出空间 */
}
</style>

高级国际化特性

除了基本的文本翻译和语言切换,vue-i18n还提供了许多高级特性,可以进一步增强SimpleBank前端的国际化能力。

复数形式处理

不同语言的复数规则可能不同。vue-i18n支持通过$tc (translation with count) 函数处理复数:

翻译文件中定义复数规则:

// en/common.ts
export default {
  // ...
  notification: 'You have {n} notification | You have {n} notifications'
};

// zh-CN/common.ts
export default {
  // ...
  notification: '你有 {n} 条通知' // 中文复数形式通常与单数相同
};

在组件中使用:

<p>{{ $tc('common.notification', 1) }}</p> <!-- 你有 1 条通知 -->
<p>{{ $tc('common.notification', 5) }}</p> <!-- 你有 5 条通知 -->

日期时间格式化

使用$d函数可以格式化日期时间,支持本地化:

// 在i18n配置中可以设置日期时间格式
// src/locales/index.ts
const i18n = createI18n({
  // ...
  datetimeFormats: {
    'en': {
      short: {
        year: 'numeric',
        month: 'short',
        day: 'numeric'
      },
      long: {
        year: 'numeric',
        month: 'long',
        day: 'numeric',
        hour: '2-digit',
        minute: '2-digit'
      }
    },
    'zh-CN': {
      short: {
        year: 'numeric',
        month: 'numeric',
        day: 'numeric'
      },
      long: {
        year: 'numeric',
        month: 'long',
        day: 'numeric',
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      }
    }
  }
});

在组件中使用:

<p>{{ $d(new Date(), 'short') }}</p> <!-- 10/5/2023 (en) 或 2023/10/5 (zh-CN) -->
<p>{{ $d(new Date(), 'long') }}</p> <!-- October 5, 2023, 13:45 (en) 或 2023年10月5日 13:45 (zh-CN) -->

数字和货币格式化

使用$n函数可以格式化数字和货币:

// 在i18n配置中可以设置数字和货币格式
// src/locales/index.ts
const i18n = createI18n({
  // ...
  numberFormats: {
    'en': {
      currency: {
        style: 'currency',
        currency: 'USD',
        minimumFractionDigits: 2
      },
      decimal: {
        style: 'decimal',
        minimumFractionDigits: 2,
        maximumFractionDigits: 2
      }
    },
    'zh-CN': {
      currency: {
        style: 'currency',
        currency: 'CNY',
        minimumFractionDigits: 2
      },
      decimal: {
        style: 'decimal',
        minimumFractionDigits: 2,
        maximumFractionDigits: 2
      }
    }
  }
});

在组件中使用:

<!-- 货币格式化 -->
<p>{{ $n(1000, 'currency') }}</p> <!-- $1,000.00 (en) 或 ¥1,000.00 (zh-CN) -->

<!-- 数字格式化 -->
<p>{{ $n(1234.56, 'decimal') }}</p> <!-- 1,234.56 (en) 或 1,234.56 (zh-CN) -->

对于SimpleBank这样的金融应用,正确的货币和数字格式化尤为重要,可以增强数据的可读性和专业性。

延迟加载翻译文件

对于包含多种语言的大型应用,一次性加载所有翻译文件可能会影响初始加载性能。vue-i18n支持通过lazy-load方式按需加载翻译文件。

修改i18n配置:

// src/locales/index.ts
import { createI18n, useI18n } from 'vue-i18n';

// 仅导入默认语言的翻译
import en from './en';

export const SUPPORTED_LANGUAGES = {
  'en': 'English',
  'zh-CN': '简体中文'
  // 'ja': '日本語'
};

export const DEFAULT_LANGUAGE = 'en';

const i18n = createI18n({
  legacy: false,
  locale: DEFAULT_LANGUAGE,
  fallbackLocale: DEFAULT_LANGUAGE,
  messages: {
    'en': en
  }
});

// 定义一个加载翻译的函数
export const loadLocaleMessages = async (lang: string) => {
  // 如果语言已加载,则直接返回
  if (Object.keys(i18n.global.messages).includes(lang)) {
    return;
  }

  // 动态导入语言包
  const messages = await import(`./${lang}`);
  i18n.global.setLocaleMessage(lang, messages.default);
  return;
};

export default i18n;

在语言选择组件中使用延迟加载:

<script setup lang="ts">
import { useI18n } from 'vue-i18n';
import Dropdown from 'primevue/dropdown';
import { ref, watch, onMounted } from 'vue';
import { SUPPORTED_LANGUAGES, loadLocaleMessages } from '@/locales';

const { locale } = useI18n();
const selectedLanguage = ref(locale.value);
const isLoading = ref(false);

const loadLanguage = async (lang: string) => {
  isLoading.value = true;
  try {
    await loadLocaleMessages(lang);
    locale.value = lang;
    localStorage.setItem('simplebank_preferred_language', lang);
  } catch (e) {
    console.error('Failed to load language:', e);
  } finally {
    isLoading.value = false;
  }
};

onMounted(() => {
  const savedLang = localStorage.getItem('simplebank_preferred_language');
  if (savedLang && savedLang !== selectedLanguage.value && SUPPORTED_LANGUAGES[savedLang]) {
    loadLanguage(savedLang);
  }
});

watch(
  () => selectedLanguage.value,
  (newLang) => {
    if (newLang !== locale.value) {
      loadLanguage(newLang);
    }
  }
);

const languageOptions = Object.entries(SUPPORTED_LANGUAGES).map(([code, name]) => ({
  value: code,
  label: name
}));
</script>

<template>
  <div class="language-selector">
    <Dropdown
      v-model="selectedLanguage"
      :options="languageOptions"
      :placeholder="$t('common.select_language')"
      class="w-full md:w-48"
      :showClear="false"
      :disabled="isLoading"
    />
  </div>
</template>

测试与验证

国际化功能实现后,需要进行充分的测试和验证,确保其正确性和可用性。测试要点包括:

  1. 文本翻译准确性:检查所有翻译文本是否准确,无遗漏或错误。
  2. 语言切换功能:验证切换语言后,界面所有文本是否正确更新。
  3. 浏览器语言检测:测试在不同语言设置的浏览器中,应用是否能正确检测并应用默认语言。
  4. 本地存储持久性:验证用户选择的语言是否能保存到localStorage,并在刷新页面或重新打开浏览器后正确应用。
  5. 复数、日期、数字格式化:测试这些高级特性在不同语言下的表现是否符合预期。
  6. UI布局适应性:某些语言的文本长度可能与原语言有较大差异,需要确保UI布局在不同语言下仍保持美观和可用性。

结论与展望

通过集成vue-i18n,SimpleBank前端应用现在具备了专业的国际化能力,能够为全球不同语言背景的用户提供友好的界面和流畅的体验。本文详细介绍了从技术选型、环境配置、翻译文件组织、组件改造到动态语言切换的完整实现过程,并探讨了复数处理、日期时间格式化等高级特性。

未来,可以进一步扩展SimpleBank的国际化能力,例如:

  1. 支持更多语言:根据用户分布,添加更多语言支持,如西班牙语、法语、阿拉伯语等。
  2. RTL (Right-to-Left) 布局支持:为阿拉伯语、希伯来语等从右向左书写的语言提供RTL布局支持。
  3. 专业翻译协作流程:引入专业的翻译管理工具或服务,简化翻译更新和维护流程。
  4. 语言切换动画:添加平滑的过渡动画,提升语言切换时的用户体验。

国际化是一个持续改进的过程,随着SimpleBank用户群体的扩大和业务的发展,需要不断优化和完善国际化策略,以满足全球用户的需求。

附录:项目结构回顾

完成国际化改造后,SimpleBank前端项目的主要结构如下:

frontend/
├── src/
│   ├── assets/
│   ├── components/
│   │   ├── LanguageSelector.vue  # 新增:语言选择组件
│   │   ├── LoginUser.vue         # 改造:国际化登录组件
│   │   └── UserInfo.vue          # 改造:国际化用户信息组件
│   ├── locales/                  # 新增:国际化相关文件
│   │   ├── index.ts              # i18n配置入口
│   │   ├── en/                   # 英语翻译
│   │   ├── zh-CN/                # 简体中文翻译
│   │   └── ... (其他语言)
│   ├── router/
│   ├── types/
│   ├── views/
│   │   └── HomeView.vue          # 改造:集成语言选择器和国际化文本
│   ├── App.vue
│   ├── main.ts                   # 改造:集成i18n
│   └── store.ts
├── package.json                  # 改造:添加vue-i18n依赖
└── ...

通过这种模块化的组织方式,国际化功能与现有代码保持了良好的分离性和可维护性,为后续功能扩展奠定了坚实基础。

【免费下载链接】simplebank Backend master class: build a simple bank service in Go 【免费下载链接】simplebank 项目地址: https://gitcode.com/GitHub_Trending/si/simplebank

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值