Skip to content
Open
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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Refactor ER diagram to single TB layout with Record position tracking
- Replace ER diagram SVG rendering with pure HTML for better performance
- Adopt DBX-style compact toolbar layout
- Adopt compact toolbar layout
- Refactor sidebar: move ER diagram button to database actions dropdown

## [0.7.4] - 2026-07-01
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

[Download](https://www.geekfun.club/download) · [Docs](docs/DISTRIBUTION.md) · [Website](https://www.geekfun.club) · [Releases](https://github.com/geek-fun/sqlkit/releases)

English · [简体中文](README_zh.md)
English · [简体中文](README_zh.md) · [Português (BR)](README_pt-BR.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Español](README_es.md)

</div>

Expand Down
309 changes: 309 additions & 0 deletions README_es.md

Large diffs are not rendered by default.

309 changes: 309 additions & 0 deletions README_ja.md

Large diffs are not rendered by default.

309 changes: 309 additions & 0 deletions README_ko.md

Large diffs are not rendered by default.

309 changes: 309 additions & 0 deletions README_pt-BR.md

Large diffs are not rendered by default.

113 changes: 25 additions & 88 deletions docs/I18N.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Multi-Language Support Implementation

This document describes the internationalization (i18n) implementation in SQLKit, which supports both English and Chinese (Simplified) languages.
This document describes the internationalization (i18n) implementation in SQLKit, which supports English, Simplified Chinese, Traditional Chinese (Taiwan), Brazilian Portuguese, Japanese, Korean, and Spanish.

## Overview

Expand All @@ -12,9 +12,14 @@ The multi-language support is implemented using [vue-i18n](https://vue-i18n.intl

All language files are located in `src/lang/`:

- **`index.ts`**: Main i18n configuration and initialization
- **`enUS.ts`**: English (US) translations
- **`index.ts`**: Main i18n configuration, locale detection, and initialization
- **`enUS.ts`**: English (US) translations — the key source of truth
- **`zhCN.ts`**: Chinese Simplified translations
- **`zhTW.ts`**: Traditional Chinese (Taiwan) translations
- **`ptBR.ts`**: Brazilian Portuguese translations
- **`jaJP.ts`**: Japanese translations
- **`koKR.ts`**: Korean translations
- **`esES.ts`**: Spanish translations

### Translation Keys Organization

Expand Down Expand Up @@ -43,9 +48,13 @@ const _translations = {
The language preference is:

1. **Stored in localStorage** with key `'lang'`
2. **Auto-detection**: If set to `'auto'`, follows system language (`navigator.language`)
- If system language is `'zh-CN'`, uses Chinese
- Otherwise, defaults to English
2. **Auto-detection**: If set to `'auto'` (the default), follows the system language (`navigator.language`):
- `zh-TW` / `zh-HK` / `zh-MO` / `zh-Hant` → `zhTW`
- other `zh-*` → `zhCN`
- `pt-*` → `ptBR`, `ja*` → `jaJP`, `ko*` → `koKR`, `es*` → `esES`
- anything else → `enUS`

Invalid stored values fall back to auto-detection, and `fallbackLocale` is `enUS`, so a missing key in any locale renders the English string instead of a raw key.

## Usage in Components

Expand Down Expand Up @@ -78,105 +87,33 @@ For dynamic content, use interpolation:
</template>
```

## Language Switcher

Users can change the language in **Settings** page under **Appearance** section. The language selector offers three options:

1. **Auto (Follow System)** - `'auto'`
2. **English** - `'enUS'`
3. **简体中文** - `'zhCN'`

When changed, the preference is saved to `localStorage` and the UI updates immediately.

## Translated Pages and Components

### Pages
- ✅ ConnectionsPage
- ✅ SettingsPage
- ✅ HistoryPage
- ✅ ImportExportPage
- ✅ QueriesPage
- ✅ DataStudioPage

### Components
- ✅ ServerFormDialog
- ✅ ServerCard
- ✅ AppHeader (displays app name "SQLKit" - no translation needed)

## Adding New Translations

To add translations for a new feature:

1. **Add keys to both `enUS.ts` and `zhCN.ts`**:

```typescript
// In enUS.ts
export const enUS = {
// ... existing translations
pages: {
myNewPage: {
title: 'My New Page',
description: 'This is a new page',
}
}
}

// In zhCN.ts
export const zhCN = {
// ... existing translations
pages: {
myNewPage: {
title: '我的新页面',
description: '这是一个新页面',
}
}
}
```
1. **Add keys to ALL language files in `src/lang/`** — `enUS.ts` first, then the same keys in every other locale file. The parity test (see below) fails the build if a locale file is missing keys.

2. **Use in component**:

```vue
<script setup lang="ts">
import { useI18n } from 'vue-i18n'

const { t } = useI18n()
</script>

<template>
<h1>{{ t('pages.myNewPage.title') }}</h1>
<p>{{ t('pages.myNewPage.description') }}</p>
</template>
```

## Key Translation Examples
## Adding a New Language

### Common Buttons
- `common.buttons.create` → "Create" / "创建"
- `common.buttons.save` → "Save" / "保存"
- `common.buttons.cancel` → "Cancel" / "取消"
- `common.buttons.delete` → "Delete" / "删除"

### Connection Status
- `common.status.connected` → "Connected" / "已连接"
- `common.status.connecting` → "Connecting..." / "连接中..."
- `common.status.disconnected` → "Disconnected" / "未连接"
- `common.status.error` → "Connection Error" / "连接错误"

### Server Form
- `components.serverForm.labels.connectionName` → "Connection Name" / "连接名称"
- `components.serverForm.labels.host` → "Host" / "主机地址"
- `components.serverForm.labels.port` → "Port" / "端口"
- `components.serverForm.labels.database` → "Database" / "数据库"
1. Copy `src/lang/enUS.ts` to `src/lang/{locale}.ts` (locale codes follow the `zhCN` / `ptBR` style, e.g. `frFR`).
2. Translate the values. Keep brand names, SQL keywords, code identifiers, keyboard key names, and interpolation placeholders (`{name}`) exactly as they are. Keep language-name keys (if any) as native names.
3. Register the file in `src/lang/index.ts`: import it, add the locale code to the `AppLocale` type and `AVAILABLE_LOCALES`, add it to the `messages` object, and extend `detectFromNavigator()` so the system language maps to it.
4. Add the locale to `src/__tests__/i18nParity.test.ts`.
5. Run `npm test` — the parity test and the key-usage test must both pass.

## Testing

To test translations:

1. Run the application: `npm run tauri dev`
2. Navigate to **Settings** page
3. Change the **Language** dropdown to test different languages
4. Navigate through different pages to verify translations
5. Try creating/editing connections to test form translations
- `src/__tests__/i18nParity.test.ts` asserts every locale file carries exactly the `enUS` key set; CI fails on drift, and missing keys fall back to English at runtime rather than showing raw keys.
- `src/__tests__/i18nKeys.test.ts` asserts every literal `t('key')` used in source files resolves in `enUS` and `zhCN`.
- To verify translations manually: run `npm run tauri dev`, switch the system language (or set `localStorage.lang`), and walk through the main pages.

## Implementation Reference

Expand Down
32 changes: 32 additions & 0 deletions src/__tests__/i18nParity.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { enUS } from '@/lang/enUS'
import { esES } from '@/lang/esES'
import { jaJP } from '@/lang/jaJP'
import { koKR } from '@/lang/koKR'
import { ptBR } from '@/lang/ptBR'
import { zhCN } from '@/lang/zhCN'
import { zhTW } from '@/lang/zhTW'

type Messages = Record<string, unknown>

const flatten = (messages: Messages, prefix = ''): string[] =>
Object.entries(messages).flatMap(([key, value]) =>
value && typeof value === 'object'
? flatten(value as Messages, `${prefix}${key}.`)
: [`${prefix}${key}`],
)

// Guards against locale drift: every language file must carry exactly the same
// key set as enUS, otherwise missing keys silently fall back to English at runtime.
describe('i18n locale parity', () => {
const locales: Record<string, Messages> = { zhCN, zhTW, ptBR, jaJP, koKR, esES }

it.each(Object.entries(locales))('%s has exactly the enUS key set', (_name, messages) => {
const baseKeys = flatten(enUS as unknown as Messages).sort()
const localeKeys = flatten(messages).sort()
const missing = baseKeys.filter(key => !localeKeys.includes(key))
const extra = localeKeys.filter(key => !baseKeys.includes(key))

expect(missing).toEqual([])
expect(extra).toEqual([])
})
})
Loading
Loading