Skip to content

Commit 31abcdf

Browse files
committed
feat(i18n): implement internationalization support with vue-i18n
- Add vue-i18n v11 and @intlify/unplugin-vue-i18n dependencies - Create language switcher component and i18n configuration - Add Chinese (Simplified and Traditional) and English language packs - Refactor components to use i18n translations - Update routing to support localized page titles - Modify link-config.js to support internationalized mode labels - Integrate i18n into Vite build process
1 parent d8cb8e6 commit 31abcdf

File tree

15 files changed

+703
-61
lines changed

15 files changed

+703
-61
lines changed

docs/I18N_IMPLEMENTATION_PLAN.md

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
# i18n国际化实施方案
2+
3+
## 1. 项目分析总结
4+
5+
### 前端文本分析结果
6+
通过对项目代码的详细分析,需要国际化的文本内容主要包括:
7+
8+
#### 1.1 页面标题和元信息
9+
- 路由meta标题:`"登录"``"管理短链接"``"页面不存在"`
10+
- 页面主要标题:`"Login"`
11+
12+
#### 1.2 表单和界面文本
13+
- **登录页面 (UserAuthForm.vue)**
14+
- `"Password"``"Enter your password"``"Sign In"`
15+
- 错误信息:`"Password is required"``"Password must be at least 6 characters"``"Login failed"``"Network error, please try again"`
16+
17+
- **管理页面 (Manage.vue)**
18+
- `"搜索短链接..."`
19+
- 筛选标签:`"全部"`(在modeOptions中)
20+
21+
- **创建/编辑对话框 (CreateLinkDialog.vue)**
22+
- 按钮文本:`"更新"``"创建"`
23+
- 占位符:`"短链接"``"显示名称"``"访问密码"``"为这个短链接添加一些备注信息..."`
24+
- 功能标签:`"高级设置"`
25+
26+
#### 1.3 配置文件文本 (link-config.js)
27+
- 模式标签:`"跳转"``"提醒"``"隐藏"``"代理"`
28+
- 模式描述:
29+
- `"302 重定向到目标地址"`
30+
- `"用户访问时显示目标地址和备注信息"`
31+
- `"隐藏目标地址,让用户只看到其中的短链接"`
32+
- `"通过服务器加载目标地址,同时代理子路径"`
33+
34+
#### 1.4 Toast通知消息
35+
- 成功消息:`"更新成功"``"创建成功"``"生成失败"`
36+
- 错误消息:`"更新失败"``"创建失败"``"操作失败"``"网络错误,请检查连接后重试"`
37+
- 描述信息:`"短链接 ${slug} 已更新/已创建"``"无法生成短链接标识符,请手动输入"`
38+
39+
### 后端文本分析结果
40+
后端API响应消息主要包括:
41+
- `"Success"``"Invalid action."``"Password error."``"Invalid URL."``"Slug already exists."`
42+
43+
## 2. 改造量评估
44+
45+
### 2.1 工作量评估(按工作日计算)
46+
47+
#### 前端改造 (2-3工作日)
48+
1. **依赖安装和配置** (0.5工作日)
49+
- 安装vue-i18n v11
50+
- 安装@intlify/unplugin-vue-i18n
51+
- 配置Vite构建
52+
53+
2. **创建语言文件** (0.5工作日)
54+
- 创建中文(zh-CN)语言包
55+
- 创建英文(en)语言包
56+
- 整理约35-40个翻译键
57+
58+
3. **组件改造** (1-1.5工作日)
59+
- 6个Vue组件需要修改
60+
- 1个配置文件需要重构
61+
- 路由meta信息国际化
62+
63+
4. **测试和调试** (0.5工作日)
64+
- 功能测试
65+
- 语言切换测试
66+
- 构建测试
67+
68+
#### 后端改造 (0.5工作日)
69+
1. **API响应消息国际化** (0.5工作日)
70+
- 修改6个API响应消息
71+
- 添加语言检测逻辑(可选)
72+
73+
### 2.2 文件修改统计
74+
- **需要修改的文件**: 约8-10个
75+
- **需要新增的文件**: 约4-5个
76+
- **代码行数变化**: 估计增加150-200行
77+
78+
## 3. 技术方案
79+
80+
### 3.1 技术栈选择
81+
- **vue-i18n v11**: 官方Vue.js国际化库
82+
- **@intlify/unplugin-vue-i18n**: Vite构建优化插件
83+
- **支持语言**: 中文(zh-CN)、英文(en)
84+
85+
### 3.2 项目结构
86+
```
87+
web/src/
88+
├── locales/ # 语言文件目录
89+
│ ├── zh-CN.json # 中文语言包
90+
│ ├── en.json # 英文语言包
91+
│ └── index.js # i18n配置
92+
├── composables/ # 组合式函数
93+
│ └── useI18n.js # i18n工具函数
94+
└── components/ # 组件修改
95+
```
96+
97+
### 3.3 实施策略
98+
1. **渐进式迁移**: 逐个组件进行国际化改造
99+
2. **向下兼容**: 保持现有功能不受影响
100+
3. **性能优化**: 使用Vite插件进行构建时预编译
101+
102+
## 4. 实施步骤
103+
104+
### 阶段1: 基础设施搭建
105+
1. 安装依赖包
106+
2. 配置Vite构建
107+
3. 创建基础i18n配置
108+
4. 创建语言文件结构
109+
110+
### 阶段2: 核心文件创建
111+
1. 创建中英文语言包
112+
2. 重构link-config.js为i18n模式
113+
3. 创建i18n工具函数
114+
115+
### 阶段3: 组件逐步改造
116+
1. 登录相关组件 (Login.vue, UserAuthForm.vue)
117+
2. 管理页面 (Manage.vue)
118+
3. 对话框组件 (CreateLinkDialog.vue, QRCodeDialog.vue)
119+
4. 其他辅助组件
120+
121+
### 阶段4: 后端API适配
122+
1. 修改API响应消息
123+
2. 添加Accept-Language头部检测(可选)
124+
125+
### 阶段5: 测试和优化
126+
1. 功能测试
127+
2. 性能测试
128+
3. 构建测试
129+
4. 用户体验优化
130+
131+
## 5. 风险评估
132+
133+
### 5.1 低风险
134+
- Vue 3 + Vite + vue-i18n是成熟的技术栈
135+
- 项目文本量相对较少
136+
- 组件结构简单清晰
137+
138+
### 5.2 中等风险
139+
- Toast消息的动态内容需要特殊处理
140+
- 后端API响应的i18n处理需要仔细设计
141+
142+
### 5.3 缓解措施
143+
- 充分的测试覆盖
144+
- 分阶段实施,降低单次修改风险
145+
- 保留回滚方案
146+
147+
## 6. 预期收益
148+
149+
### 6.1 用户体验提升
150+
- 支持多语言用户群体
151+
- 提升国际化用户体验
152+
- 增强产品竞争力
153+
154+
### 6.2 技术架构优化
155+
- 规范化文本管理
156+
- 便于未来添加新语言
157+
- 提升代码可维护性
158+
159+
## 7. 结论
160+
161+
**可行性评估**: ✅ 高度可行
162+
**推荐实施**: ✅ 强烈推荐
163+
**预计工期**: 3-4工作日
164+
**技术难度**: 🟡 中等
165+
**投入产出比**: 🟢 良好
166+
167+
项目的i18n改造具有很高的可行性,工作量适中,技术风险较低。建议按照上述方案分阶段实施,可以有效提升项目的国际化能力。

web/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,20 @@
1414
"class-variance-authority": "^0.7.1",
1515
"clsx": "^2.1.1",
1616
"date-fns": "^4.1.0",
17-
"lucide-react": "^0.525.0",
1817
"qrcode": "^1.5.4",
1918
"radix-vue": "^1.2.6",
2019
"tailwind-merge": "^3.3.1",
2120
"tailwindcss-animate": "^1.0.7",
2221
"v-calendar": "next",
2322
"vue": "^3.3.11",
23+
"vue-i18n": "11",
2424
"vue-router": "4",
2525
"vue3-clipboard": "^1.0.0"
2626
},
2727
"devDependencies": {
2828
"@iconify/json": "^2.2.166",
2929
"@iconify/tailwind": "^0.1.4",
30+
"@intlify/unplugin-vue-i18n": "^6.0.8",
3031
"@vitejs/plugin-vue": "^4.5.2",
3132
"autoprefixer": "^10.4.16",
3233
"postcss": "^8.4.32",
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<template>
2+
<Select :model-value="getCurrentLocale()" @update:model-value="handleLocaleChange">
3+
<SelectTrigger class="w-auto gap-2 h-8 rounded-full border-0 shadow-none">
4+
<SelectValue>
5+
<div class="flex items-center gap-2">
6+
<!-- <span>{{ currentLocale.flag }}</span> -->
7+
<span class="text-sm">{{ currentLocale.name }}</span>
8+
</div>
9+
</SelectValue>
10+
</SelectTrigger>
11+
<SelectContent>
12+
<SelectItem v-for="locale in supportedLocales" :key="locale.code" :value="locale.code">
13+
<div class="flex items-center gap-2">
14+
<!-- <span>{{ locale.flag }}</span> -->
15+
<span>{{ locale.name }}</span>
16+
</div>
17+
</SelectItem>
18+
</SelectContent>
19+
</Select>
20+
</template>
21+
22+
<script setup>
23+
import { computed } from "vue";
24+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
25+
import { supportedLocales, setLocale, getCurrentLocale } from "@/locales";
26+
27+
// 获取当前语言信息
28+
const currentLocale = computed(() => {
29+
const current = getCurrentLocale();
30+
return supportedLocales.find((l) => l.code === current) || supportedLocales[0];
31+
});
32+
33+
// 处理语言切换
34+
const handleLocaleChange = (localeCode) => {
35+
setLocale(localeCode);
36+
// 刷新页面以确保所有内容都更新
37+
window.location.reload();
38+
};
39+
</script>

web/src/lib/link-config.js

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { i18n } from '@/locales'
2+
13
export const DeepClone = (obj) => {
24
return JSON.parse(JSON.stringify(obj));
35
};
@@ -12,29 +14,53 @@ export const BaseData = {
1214
clicks: 0, // 点击次数
1315
};
1416

17+
// 获取国际化的模式列表
18+
export function getModeList() {
19+
const { t } = i18n.global
20+
return [
21+
{
22+
value: "redirect",
23+
label: t('modes.redirect'),
24+
description: t('modes.redirectDesc'),
25+
},
26+
{
27+
value: "remind",
28+
label: t('modes.remind'),
29+
description: t('modes.remindDesc'),
30+
},
31+
{
32+
value: "cloaking",
33+
label: t('modes.cloaking'),
34+
description: t('modes.cloakingDesc'),
35+
},
36+
{
37+
value: "proxy",
38+
label: t('modes.proxy'),
39+
description: t('modes.proxyDesc'),
40+
},
41+
]
42+
}
43+
44+
// 为了向后兼容,保留原有的modeList导出
1545
export const modeList = [
1646
{
1747
value: "redirect",
1848
label: "跳转",
19-
// label: "Redirect",
2049
description: "302 重定向到目标地址",
2150
},
2251
{
2352
value: "remind",
2453
label: "提醒",
25-
// label: "Remind",
2654
description: "用户访问时显示目标地址和备注信息",
2755
},
2856
{
2957
value: "cloaking",
3058
label: "隐藏",
31-
// label: "Cloaking",
3259
description: "隐藏目标地址,让用户只看到其中的短链接",
3360
},
3461
{
3562
value: "proxy",
3663
label: "代理",
37-
// label: "Proxy",
3864
description: "通过服务器加载目标地址,同时代理子路径",
3965
},
4066
];

web/src/locales/en.json

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
{
2+
"common": {
3+
"language": "Language",
4+
"all": "All",
5+
"search": "Search",
6+
"clear": "Clear",
7+
"create": "Create",
8+
"update": "Update",
9+
"delete": "Delete",
10+
"edit": "Edit",
11+
"save": "Save",
12+
"cancel": "Cancel",
13+
"confirm": "Confirm",
14+
"submit": "Submit",
15+
"close": "Close",
16+
"loading": "Loading...",
17+
"success": "Success",
18+
"error": "Error",
19+
"failed": "Failed",
20+
"required": "Required",
21+
"optional": "Optional"
22+
},
23+
"auth": {
24+
"login": "Login",
25+
"password": "Password",
26+
"enterPassword": "Enter your password",
27+
"signIn": "Sign In",
28+
"passwordRequired": "Password is required",
29+
"passwordLength": "Password must be at least 6 characters",
30+
"loginFailed": "Login failed",
31+
"networkError": "Network error, please try again"
32+
},
33+
"navigation": {
34+
"home": "Home",
35+
"manage": "Manage",
36+
"dashboard": "Dashboard"
37+
},
38+
"shortLink": {
39+
"title": "Short Link",
40+
"url": "URL",
41+
"slug": "Slug",
42+
"displayName": "Display Name",
43+
"notes": "Notes",
44+
"passcode": "Access Password",
45+
"urlPlaceholder": "https://example.com",
46+
"slugPlaceholder": "Short link",
47+
"displayNamePlaceholder": "Display name",
48+
"passcodePlaceholder": "Access password",
49+
"notesPlaceholder": "Add some notes for this short link...",
50+
"searchPlaceholder": "Search short links...",
51+
"advancedSettings": "Advanced Settings",
52+
"clicks": "Clicks"
53+
},
54+
"modes": {
55+
"redirect": "Redirect",
56+
"remind": "Remind",
57+
"cloaking": "Cloaking",
58+
"proxy": "Proxy",
59+
"redirectDesc": "302 redirect to target URL",
60+
"remindDesc": "Display target URL and notes when visited",
61+
"cloakingDesc": "Hide target URL, show only short link",
62+
"proxyDesc": "Load target URL through server, proxy sub-paths"
63+
},
64+
"messages": {
65+
"createSuccess": "Created successfully",
66+
"updateSuccess": "Updated successfully",
67+
"createFailed": "Creation failed",
68+
"updateFailed": "Update failed",
69+
"operationFailed": "Operation failed",
70+
"networkError": "Network error, please check connection and try again",
71+
"generateFailed": "Generation failed",
72+
"generateFailedDesc": "Unable to generate short link identifier, please enter manually",
73+
"linkCreated": "Short link {slug} created",
74+
"linkUpdated": "Short link {slug} updated"
75+
},
76+
"pages": {
77+
"loginTitle": "Login",
78+
"manageTitle": "Manage Short Links",
79+
"notFoundTitle": "Page Not Found",
80+
"notFound": "The page you are looking for does not exist.",
81+
"backToHome": "Back to Home"
82+
},
83+
"table": {
84+
"searching": "Searching...",
85+
"loadingMore": "Loading more...",
86+
"scrollToLoadMore": "Scroll down to load more",
87+
"allRecordsShown": "All {count} records shown",
88+
"noResults": "No matching results found",
89+
"noData": "No short links yet",
90+
"tryAdjustFilters": "Try adjusting search terms or filter types",
91+
"createFirstLink": "Click the button below to create your first short link",
92+
"createLink": "Create Link",
93+
"searchInType": "Search \"{query}\" in {type} type, found {count} results",
94+
"searchResults": "Searching \"{query}\", found {count} results",
95+
"filterResults": "Filtering {type} type, found {count} results"
96+
},
97+
"footer": {
98+
"buildOn": "Build on",
99+
"poweredBy": "Powered by Cloudflare Workers"
100+
}
101+
}

0 commit comments

Comments
 (0)