Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
202 changes: 199 additions & 3 deletions docs/WEBUI_DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -296,20 +296,46 @@ onMounted(() => {
```vue
<template>
<div>
<!-- 使用 $t (通过 globalInjection) -->
<!-- 在模板中使用 $t (通过 globalInjection) -->
<h1>{{ $t('common.title') }}</h1>
<p>{{ $t('common.description') }}</p>

<!-- 或使用 t 函数(Composition API) -->
<p>{{ t('common.description') }}</p>
<!-- 在属性中使用 -->
<input :placeholder="$t('common.placeholder')" />
<button :title="$t('common.tooltip')">{{ $t('common.button') }}</button>
</div>
</template>

<script setup>
import { useI18n } from 'vue-i18n'
// 在 script 中使用 useI18n() 获取 t 函数
const { t } = useI18n()
</script>
```

#### 在 `<script setup>` 中使用

当需要在 JavaScript 代码中使用翻译(如 `alert()`, `confirm()` 等),必须使用 `useI18n()`:

```vue
<script setup>
import { useI18n } from 'vue-i18n'

const { t } = useI18n()

// 在函数中使用
const handleConfirm = () => {
if (confirm(t('common.confirm_message'))) {
// 处理确认
}
}

const showError = () => {
alert(t('common.error_message'))
}
</script>
```

#### 在 Composables 中

```javascript
Expand Down Expand Up @@ -591,6 +617,176 @@ npm run preview
- 语言文件位于 `public/assets/locale/` 目录
- 配置在 `config/i18n.js` 中

### i18n 开发工作流

项目提供了一套完整的国际化(i18n)工具链,用于确保翻译文件的质量和一致性。基准语言文件是 `en.json`,所有其他语言文件需要与其保持同步。

#### 可用命令

```bash
# 验证所有语言文件的完整性
npm run i18n:validate

# 检查并自动同步缺失的翻译键(使用英文占位值)
npm run i18n:sync

# 格式化并排序所有语言文件(按字母顺序)
npm run i18n:format

# 检查文件格式
npm run i18n:format:check

# 验证翻译完整性
npm run i18n:validate
```

#### 添加新的翻译键

1. **在基准文件中添加新键**:首先在 `en.json` 中添加新的翻译键和英文值
```json
{
"myfeature": {
"title": "My Feature Title",
"description": "My feature description",
"button_label": "Submit"
}
}
```

2. **同步到其他语言文件**:
```bash
npm run i18n:sync
```
这将自动在所有语言文件中添加缺失的键,并使用英文值作为占位符

3. **格式化文件**:
```bash
npm run i18n:format
```
这将对所有语言文件进行统一排序和格式化,减少 Git 冲突

4. **翻译占位符**:手动将自动添加的英文占位符翻译为对应语言

5. **验证**:
```bash
npm run i18n:validate
```
确保所有语言文件都包含完整的翻译键

#### 国际化现有组件示例

以下是一个完整的国际化现有组件的示例:

**步骤 1:识别硬编码文本**
```vue
<!-- 原始组件 -->
<template>
<div>
<h2>客户端列表</h2>
<table>
<thead>
<tr>
<th>名称</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="client in clients" :key="client.id">
<td>{{ client.name || '未知客户端' }}</td>
<td>
<button @click="handleDelete">删除</button>
</td>
</tr>
</tbody>
</table>
</div>
</template>

<script setup>
const handleDelete = () => {
if (confirm('确定要删除吗?')) {
// 删除逻辑
}
}
</script>
```

**步骤 2:在 `en.json` 中添加翻译键**
```json
{
"client": {
"list_title": "Client List",
"name": "Name",
"actions": "Actions",
"unknown_client": "Unknown Client",
"delete": "Delete",
"confirm_delete": "Are you sure you want to delete?"
}
}
```

**步骤 3:更新组件使用翻译**
```vue
<template>
<div>
<h2>{{ $t('client.list_title') }}</h2>
<table>
<thead>
<tr>
<th>{{ $t('client.name') }}</th>
<th>{{ $t('client.actions') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="client in clients" :key="client.id">
<td>{{ client.name || $t('client.unknown_client') }}</td>
<td>
<button @click="handleDelete">{{ $t('client.delete') }}</button>
</td>
</tr>
</tbody>
</table>
</div>
</template>

<script setup>
import { useI18n } from 'vue-i18n'

const { t } = useI18n()

const handleDelete = () => {
if (confirm(t('client.confirm_delete'))) {
// 删除逻辑
}
}
</script>
```

**步骤 4:同步和验证**
```bash
npm run i18n:sync
npm run i18n:format
npm run i18n:validate
```

#### 最佳实践

- **提交前验证**:在提交代码前运行 `npm run i18n:validate` 确保没有缺失的翻译
- **保持格式一致**:定期运行 `npm run i18n:format` 保持文件格式统一
- **避免直接编辑**:不要直接删除或重命名翻译键,应先在 `en.json` 中修改,然后同步
- **CI 集成**:CI 会自动检查翻译文件的完整性和格式,确保代码质量

#### 脚本说明

- **validate-i18n.js**:验证所有语言文件是否包含 `en.json` 中定义的所有键,并报告缺失或多余的键
- **format-i18n.js**:对所有语言文件的键进行字母排序,并应用统一的格式化(2 空格缩进)

这些工具确保了:
- ✅ 所有语言文件具有相同的翻译键
- ✅ 文件格式统一,减少不必要的 Git 冲突
- ✅ 翻译缺失可以快速被发现和修复
- ✅ 代码审查更加容易

## 🎨 主题系统

- 支持明暗主题切换
Expand Down
116 changes: 113 additions & 3 deletions docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,25 +67,135 @@ The following is a simple example of how to use it.
}
```

@note{The json keys should be sorted alphabetically. You can use [jsonabc](https://novicelab.org/jsonabc)
to sort the keys.}
@note{The json keys should be sorted alphabetically. You can use the provided i18n tools to automatically
format and sort all locale files: `npm run i18n:format`}

@attention{Due to the integration with Crowdin, it is important to only add strings to the *en.json* file,
and to not modify any other language files. After the PR is merged, the translations can take place
on [CrowdIn][crowdin-url]. Once the translations are complete, a PR will be made
to merge the translations into Sunshine.}

##### i18n Development Tools

The project provides several npm scripts to help maintain translation quality:

```bash
# Validate all locale files have the same keys as en.json
npm run i18n:validate

# Auto-sync missing keys to all locale files (uses English as placeholder)
npm run i18n:sync

# Format and sort all locale JSON files alphabetically
npm run i18n:format

# Check if files are properly formatted
npm run i18n:format:check

# Validate translations
npm run i18n:validate
```

**Workflow when adding new translation keys:**

1. Add new keys to `en.json` only
2. Run `npm run i18n:sync` to add missing keys to all locale files
3. Run `npm run i18n:format` to ensure consistent formatting
4. Run `npm run i18n:validate` to verify completeness
5. Commit your changes - CI will automatically validate the translations

The i18n validation is integrated into the CI pipeline and will prevent merging PRs with
incomplete or incorrectly formatted translation files.

* Use the string in the Vue component.
```html
<template>
<div>
<!-- In template, use $t (global injection) -->
<p>{{ $t('index.welcome') }}</p>

<!-- Or use in attributes -->
<input :placeholder="$t('index.placeholder')" />
<button :title="$t('index.tooltip')">{{ $t('index.button') }}</button>
</div>
</template>

<script setup>
import { useI18n } from 'vue-i18n'

// In script, use useI18n() to get t function
const { t } = useI18n()

const handleClick = () => {
alert(t('index.success_message'))
if (confirm(t('index.confirm_action'))) {
// Handle confirmation
}
}
</script>
```

@tip{More formatting examples can be found in the
[Vue I18n guide](https://kazupon.github.io/vue-i18n/guide/formatting.html).}
[Vue I18n guide](https://vue-i18n.intlify.dev/guide/formatting.html).}

##### Internationalizing Existing Components

When internationalizing existing components with hardcoded text, follow these steps:

1. **Identify hardcoded strings** in the component (both in template and script)

2. **Add translation keys to `en.json`**:
```json
{
"mycomponent": {
"title": "My Title",
"button_text": "Click Me",
"confirm_message": "Are you sure?"
}
}
```

3. **Replace hardcoded text in template**:
```vue
<!-- Before -->
<h1>我的标题</h1>
<button>点击我</button>

<!-- After -->
<h1>{{ $t('mycomponent.title') }}</h1>
<button>{{ $t('mycomponent.button_text') }}</button>
```

4. **Replace hardcoded text in script** (must use `useI18n()`):
```vue
<script setup>
import { useI18n } from 'vue-i18n'
const { t } = useI18n()

// Before
const handleClick = () => {
if (confirm('确定吗?')) {
// ...
}
}

// After
const handleClick = () => {
if (confirm(t('mycomponent.confirm_message'))) {
// ...
}
}
</script>
```

5. **Sync translation keys**:
```bash
npm run i18n:sync
npm run i18n:format
npm run i18n:validate
```

@note{Always use `useI18n()` in `<script setup>` when you need translations in JavaScript code (like `alert()`, `confirm()`, etc.). The `$t` function is only available in templates through global injection.}

##### C++

Expand Down
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@
"dev-server": "cross-env VITE_USE_ROLldOWN=1 vite serve --config vite.dev.config.js",
"dev-full": "node dev-server.js",
"preview": "vite preview",
"preview:build": "npm run build && npm run preview"
"preview:build": "npm run build && npm run preview",
"i18n:validate": "node scripts/validate-i18n.js",
"i18n:sync": "node scripts/validate-i18n.js --sync",
"i18n:reverse-sync": "node scripts/reverse-sync-i18n.js --sync",
"i18n:reverse-check": "node scripts/reverse-sync-i18n.js",
"i18n:format": "node scripts/format-i18n.js",
"i18n:format:check": "node scripts/format-i18n.js --check"
},
"dependencies": {
"@fortawesome/fontawesome-free": "6.6.0",
Expand Down
Loading