1
0
forked from baron/baron-sso

fix(i18n): update userfront english copy

This commit is contained in:
Lectom C Han
2026-03-23 13:44:55 +09:00
parent 2de22869c0
commit 318948c2fb
5 changed files with 582 additions and 444 deletions

View File

@@ -322,6 +322,7 @@ KETO_WRITE_URL = "http://keto:4467"
- **Source of Truth**: `locales/template.toml`이 전체 키의 기준이며 `locales/ko.toml`, `locales/en.toml`과 항상 동기화합니다.
- **React(Admin/Dev)**: `adminfront/src/lib/i18n.ts`, `devfront/src/lib/i18n.ts`에서 `t(key, fallback, vars)`로 사용하고 TOML을 `?raw`로 로드합니다.
- **Flutter(User)**: `userfront/lib/i18n.dart`에서 `tr(key, fallback, params)` 사용. `locales/*.toml``tools/i18n-scanner/gen-flutter-i18n.js``userfront/lib/i18n_data.dart`에 사전 생성합니다.
- **UserFront 동기화 규칙**: `locales/*.toml`을 수정한 뒤에는 반드시 `./scripts/sync_userfront_locales.sh`를 실행해 `userfront/assets/translations/*.toml`과 런타임 번역 리소스를 동기화합니다.
- **검증**: `node tools/i18n-scanner/index.js`로 코드-키-로케일 동기화 상태를 점검합니다.
## 🧪 Code Check CI

View File

@@ -111,6 +111,14 @@ TOML에서는 `[Section]`을 사용하여 계층을 표현합니다.
#### 5.2.2 관리 프로세스 (Template & CI)
1. **`template.toml`**: 개발자가 새로운 번역 키를 추가할 때 반드시 이 파일에 먼저 정의해야 합니다.
2. **`ko.toml`, `en.toml`**: 템플릿의 키를 바탕으로 실제 번역 값을 채워 넣습니다.
3. **UserFront 런타임 리소스 동기화**:
* UserFront는 런타임에 `userfront/assets/translations/*.toml`을 직접 읽습니다.
* 따라서 `locales/ko.toml`, `locales/en.toml`, `locales/template.toml`을 수정한 뒤에는 반드시 아래 동기화 스크립트를 실행해야 합니다.
* 실행 명령:
```bash
./scripts/sync_userfront_locales.sh
```
* 이 단계가 누락되면 루트 SoT와 UserFront 실제 표시 문구가 어긋날 수 있습니다.
3. **CI 검증 (Verification)**:
* **Level 1: 리소스 동기화 검사 (`template` vs `lang`)**
* `template.toml`에 있는 모든 키가 `ko.toml`, `en.toml`에 존재하는지 재귀적으로 검사합니다.
@@ -244,3 +252,14 @@ UserFront(`/error`)는 프로덕션에서 다음 규칙으로 에러를 표시
2. [ ] **CI**: `template.toml` vs `*.toml` 키 동기화 검증 스크립트 작성 (`scripts/verify-i18n.js` or `py`).
3. [ ] **Admin/DevFront**: Vite TOML 플러그인 설정 및 `react-i18next` 연동.
4. [ ] **UserFront**: TOML -> JSON 변환 스크립트 추가 및 `easy_localization` 연동.
### 6.1 UserFront 번역 수정 체크포인트
UserFront 번역을 수정할 때는 아래 순서를 기본 절차로 사용합니다.
1. `locales/*.toml` 수정
2. `./scripts/sync_userfront_locales.sh` 실행
3. UserFront 회귀 테스트 실행
- 예: `cd userfront && flutter test test/english_locale_placeholder_test.dart`
4. 전체 키 정합성 점검
- 예: `node tools/i18n-scanner/index.js`

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,106 @@
import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
import 'package:toml/toml.dart';
const Set<String> _placeholderValues = {
'Action',
'Action Label',
'Approve Error',
'Approve Success',
'Body',
'Code Hint',
'Code Label',
'Confirm',
'Confirm Button',
'Description',
'Error',
'Heading',
'Input Label',
'Invalid',
'Label',
'Load Failed',
'Page Title',
'Request Code',
'Result Failure',
'Sent',
'Subtitle',
'Title',
'Update Success',
};
String? _readTomlValue(Map<String, dynamic> root, String key) {
dynamic cursor = root;
for (final part in key.split('.')) {
if (cursor is! Map<String, dynamic>) {
return null;
}
cursor = cursor[part];
}
return cursor is String ? cursor : null;
}
void main() {
test('critical english copy does not expose placeholder values', () {
final file = File('assets/translations/en.toml');
final document = TomlDocument.parse(file.readAsStringSync());
final translations = document.toMap();
const criticalKeys = <String>[
'ui.userfront.forgot.heading',
'ui.userfront.forgot.input_label',
'ui.userfront.forgot.title',
'msg.userfront.forgot.description',
'msg.userfront.forgot.sent',
'ui.userfront.login.link.action_label',
'ui.userfront.login.link.page_title',
'ui.userfront.login.link.title',
'ui.userfront.login.unregistered.action',
'ui.userfront.login.unregistered.title',
'ui.userfront.login.verification.action_label',
'ui.userfront.login.verification.page_title',
'ui.userfront.login.verification.title',
'msg.userfront.login.qr.load_failed',
'msg.userfront.login.short_code.invalid',
'msg.userfront.login.unregistered.body',
'ui.userfront.login_success.title',
'msg.userfront.login_success.subtitle',
'msg.userfront.profile.load_failed',
'msg.userfront.profile.update_success',
'ui.userfront.profile.password.title',
'ui.userfront.profile.phone.code_hint',
'msg.userfront.reset.invalid_body',
'msg.userfront.reset.invalid_title',
'ui.userfront.reset.subtitle',
'ui.userfront.reset.title',
'ui.userfront.signup.title',
'msg.userfront.signup.agreement.title',
'msg.userfront.signup.auth.title',
'ui.userfront.signup.auth.email.label',
'ui.userfront.signup.auth.email.title',
'msg.userfront.signup.password.title',
'msg.userfront.signup.profile.title',
'msg.userfront.signup.success.body',
'msg.userfront.signup.success.title',
'ui.userfront.signup.success.action',
];
final failures = <String>[];
for (final key in criticalKeys) {
final value = _readTomlValue(translations, key);
if (value == null || value.trim().isEmpty) {
failures.add('$key is missing');
continue;
}
if (_placeholderValues.contains(value.trim())) {
failures.add('$key uses placeholder "$value"');
}
}
expect(
failures,
isEmpty,
reason: failures.isEmpty ? null : failures.join('\n'),
);
});
}