Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
593f51c
i18n init
jiayev May 25, 2026
2724c3e
refactor(i18n): optimize locale string caching and storage
jiayev May 25, 2026
50526c8
Add internationalization support to WeatherEditor
jiayev May 25, 2026
bef439e
add zhcn
jiayev May 25, 2026
6910d76
feat(i18n): add system locale detection and CJK font support
jiayev May 25, 2026
f20bf8f
more
jiayev May 25, 2026
76ec526
Merge branch 'dev' into i18n
jiayev May 26, 2026
899ece5
feat(i18n): add locale validation and improve locale selection UI
jiayev May 26, 2026
4ea2c26
fixes
jiayev May 26, 2026
e4d89af
Translated using Weblate (Chinese (Simplified Han script))
weblate May 26, 2026
fe3c67e
Translated using Weblate (Chinese (Simplified Han script))
jiayev May 26, 2026
a75cbf9
Merge pull request #56 from weblate/weblate-community-shaders-communi…
jiayev May 26, 2026
93f46e2
Merge branch 'dev' into i18n
jiayev May 26, 2026
b2e97a7
fix accidently removed code
jiayev May 26, 2026
6f1551f
Merge branch 'dev' into i18n
jiayev May 28, 2026
0b1b483
Merge branch 'dev' into i18n
jiayev Jun 1, 2026
5c5b56d
Translated using Weblate (Chinese (Simplified Han script))
weblate Jun 1, 2026
85df22c
Translated using Weblate (Chinese (Simplified Han script))
jiayev Jun 1, 2026
4a20321
Merge pull request #60 from weblate/weblate-community-shaders-communi…
jiayev Jun 1, 2026
e3ff682
docs: add internationalization guidelines and translation rules to CL…
jiayev Jun 1, 2026
0e8c8f9
Merge branch 'dev' into i18n
jiayev Jun 1, 2026
e5957ab
fix: optimize csEditor name retrieval in WetnessEffects settings
jiayev Jun 1, 2026
995f72c
Merge branch 'dev' into i18n
jiayev Jun 1, 2026
b9ae734
Fix hidden “more results” count formatting
jiayev Jun 2, 2026
f8a7985
Merge branch 'dev' into i18n
jiayev Jun 2, 2026
8a1a290
update en.json
jiayev Jun 2, 2026
89c42d9
fix typo
jiayev Jun 2, 2026
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
63 changes: 63 additions & 0 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -561,3 +561,66 @@ Full details: [Developers wiki — Patch Release Process](https://github.com/com
- **Graphics State Corruption**: Minimize DirectX state changes; restore state after modifications
- **Thread Safety**: Graphics operations must consider Skyrim's rendering thread vs game logic thread
- **DRY Violations in Cross-Cutting Refactors**: When adding a utility pattern across many files (e.g., resource naming, debug hooks), check whether the implementation exists in multiple places before writing a new one. For example, `Buffer.h` helper classes and raw `device->Create*` callsites both need `SetResourceName` — ensure they share a single implementation, not duplicate GUID definitions or parallel helper functions. Use a forward declaration in headers to delegate to the canonical implementation in `Utils/D3D.cpp` rather than re-implementing inline.

## Internationalization (i18n) System

### Using Translations in Code

All user-visible strings must use the translation system. The source of truth for English strings is `package/SKSE/Plugins/CommunityShaders/Translations/en.json`.

**API**:

```cpp
// T() macro: key + inline English default
ImGui::Text("%s", T("menu.faq.q10", "My new FAQ question?"));

// TKEY macro: for feature files, prefixes are defined to keep keys short
#define I18N_KEY_PREFIX "feature.my_feature."
ImGui::Checkbox(T(TKEY("enabled"), "Enabled"), &settings.enabled);
#undef I18N_KEY_PREFIX
```

**After adding new translatable strings**, regenerate `en.json`:

```bash
python tools/extract-i18n.py --write
```

### Key Naming Convention

```
menu.<page>.<item> — Menu UI labels
menu.<page>.<item>_tooltip — Tooltip text
feature.<short_name>.<setting> — Feature settings
overlay.<type> — Overlay messages
common.<term> — Shared/reused text
ui.<component> — Utility UI
weather_editor.<item> — Weather editor
```

### Translation Rules (Must Follow When Writing Strings)

| Rule | Detail |
| --------------------------------- | ------------------------------------------------------------- |
| **Translate values, not keys** | Keys (left side of JSON) are never translated |
| **Preserve placeholders** | `{version}`, `{count}`, `{key}` must be kept in all languages |
| **Preserve format specifiers** | `%s`, `%d`, `%.1f` must be kept |
| **`\n` = line break** | Line break positions may be adjusted |
| **Don't translate `##` suffixes** | If a value contains `##xxx`, the part after `##` stays as-is |
| **Partial translations OK** | Missing keys automatically fall back to English |

### CI Validation (`pr-i18n.yaml`)

The CI workflow checks:

- `en.json` is in sync with source code (`--check`)
- No orphaned keys exist (`--orphans`)
- Translation files have valid JSON format
- Placeholders `{name}` are consistent across languages

**Before submitting PRs that add/modify UI strings**, run locally:

```bash
python tools/extract-i18n.py --check
python tools/extract-i18n.py --orphans
```
90 changes: 90 additions & 0 deletions .github/workflows/pr-i18n.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
name: "PR: i18n Check"

on:
pull_request:
types: [opened, synchronize, reopened]
paths:
- "src/**"
- "package/SKSE/Plugins/CommunityShaders/Translations/**"
- "tools/extract-i18n.py"

permissions:
contents: read

jobs:
i18n-check:
name: Verify en.json is in sync with source
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}

- uses: actions/setup-python@v5
with:
python-version: "3.11"
Comment thread
jiayev marked this conversation as resolved.

- name: Check en.json is up to date
run: python tools/extract-i18n.py --check

- name: Check for orphaned keys
run: python tools/extract-i18n.py --orphans

- name: Validate translation file formats
run: |
python -c "
import json, sys, pathlib

translations_dir = pathlib.Path('package/SKSE/Plugins/CommunityShaders/Translations')
if not translations_dir.exists():
print('No Translations directory found')
sys.exit(0)

# Load en.json keys as reference
en_path = translations_dir / 'en.json'
if not en_path.exists():
print('ERROR: en.json not found')
sys.exit(1)

with open(en_path, encoding='utf-8') as f:
en_data = json.load(f)
en_keys = {k for k in en_data if k != '_meta'}

errors = []
for path in sorted(translations_dir.glob('*.json')):
if path.name == 'en.json':
continue
try:
with open(path, encoding='utf-8') as f:
data = json.load(f)
except json.JSONDecodeError as e:
errors.append(f'{path.name}: Invalid JSON - {e}')
continue

if not isinstance(data, dict):
errors.append(f'{path.name}: Root must be a JSON object')
continue

# Check for keys not in en.json (stale/typo keys)
locale_keys = {k for k in data if k != '_meta'}
extra_keys = locale_keys - en_keys
if extra_keys:
errors.append(f'{path.name}: {len(extra_keys)} key(s) not in en.json: {sorted(extra_keys)[:5]}')

# Check placeholder consistency
import re
placeholder_re = re.compile(r'\{(\w+)\}')
for key in locale_keys & en_keys:
en_placeholders = set(placeholder_re.findall(en_data[key]))
locale_placeholders = set(placeholder_re.findall(data[key]))
if en_placeholders != locale_placeholders:
errors.append(f'{path.name}: Key \"{key}\" has mismatched placeholders: expected {en_placeholders}, got {locale_placeholders}')

if errors:
print(f'Found {len(errors)} error(s):')
for e in errors:
print(f' - {e}')
sys.exit(1)
else:
print(f'All translation files valid. Checked {len(list(translations_dir.glob(\"*.json\"))) - 1} locale(s).')
"
119 changes: 119 additions & 0 deletions TRANSLATING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Translating Community Shaders

Community Shaders supports multiple languages through a JSON-based translation system.
This document explains how to contribute translations.

## For Translators (No Coding Required)

### Option A: Via Weblate (Recommended)

The easiest way to contribute translations is through our hosted Weblate instance:

1. Visit: **[hosted.weblate.org/projects/community-shaders](https://hosted.weblate.org/projects/community-shaders/)** _(link will be active once configured)_
2. Create an account or log in with GitHub
3. Select your language
4. Translate strings in the web interface
5. Your translations are automatically submitted as PRs

Weblate provides:

- Translation memory and suggestions
- Consistency checks
- Placeholder validation (`{name}` must be preserved)
- Progress tracking per language

### Option B: Direct PR on GitHub

1. Fork the repository
2. Copy `package/SKSE/Plugins/CommunityShaders/Translations/en.json` to a new file named with your locale code (e.g., `zh_CN.json`, `ja.json`, `de.json`)
3. Translate the string values (NOT the keys)
4. Submit a Pull Request

## Translation File Format

```json
{
"_meta": {
"language": "简体中文",
"locale": "zh_CN",
"version": "1.0.0",
"authors": ["Your Name"]
},
"menu.home.welcome": "欢迎使用 Community Shaders {version}",
"menu.faq.q1": "什么是 Community Shaders?",
...
}
```

### Rules

| Rule | Example |
| --------------------------------- | ---------------------------------------------------- |
| **Translate values, not keys** | `"menu.faq.q1": "翻译这里"` — key 左边不改 |
| **Preserve placeholders** | `{version}`, `{count}`, `{key}` 必须保留,位置可调整 |
| **Preserve format specifiers** | `%s`, `%d`, `%.1f` 必须保留 |
| **`\n` = line break** | 可以调整分行位置 |
| **`_meta.language`** | 用该语言自身书写(如 "日本語" 而非 "Japanese") |
| **Don't translate `##` suffixes** | 如果值中包含 `##xxx`,不翻译 `##` 后面的部分 |
| **Partial translations OK** | 缺失的 key 会自动 fallback 到英文 |

### Locale Codes

Use standard BCP 47-style codes:

| Code | Language |
| ------- | ------------------ |
| `zh_CN` | 简体中文 |
| `zh_TW` | 繁體中文 |
| `ja` | 日本語 |
| `ko` | 한국어 |
| `de` | Deutsch |
| `fr` | Français |
| `es` | Español |
| `pt_BR` | Português (Brasil) |
| `ru` | Русский |
| `it` | Italiano |
| `pl` | Polski |

## For Developers

### Adding New Translatable Strings

```cpp
// 1. Use T() with inline default in source code
ImGui::Text("%s", T("menu.faq.q10", "My new FAQ question?"));

// 2. For Feature files, use TKEY macro for shorter keys
#define I18N_KEY_PREFIX "feature.my_feature."
ImGui::Checkbox(T(TKEY("enabled"), "Enabled"), &settings.enabled);
#undef I18N_KEY_PREFIX

// 3. Regenerate en.json
// Run: python tools/extract-i18n.py --write
```

### CI Validation

The `pr-i18n.yaml` workflow checks:

- `en.json` is in sync with source code (`--check`)
- No orphaned keys exist (`--orphans`)
- Translation files have valid JSON format
- Placeholders `{name}` are consistent across languages

### Key Naming Convention

```
menu.<page>.<item> — Menu UI labels
menu.<page>.<item>_tooltip — Tooltip text
feature.<short_name>.<setting> — Feature settings
overlay.<type> — Overlay messages
common.<term> — Shared/reused text
ui.<component> — Utility UI
weather_editor.<item> — Weather editor
```
Comment thread
jiayev marked this conversation as resolved.

## CJK Font Support

CJK languages (Chinese, Japanese, Korean) require fonts with appropriate glyph coverage.
Community Shaders uses system CJK fonts by default.
Loading
Loading