forked from baron/baron-sso
fix(i18n): update userfront english copy
This commit is contained in:
@@ -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
|
||||
|
||||
19
docs/i18n.md
19
docs/i18n.md
@@ -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`
|
||||
|
||||
451
locales/en.toml
451
locales/en.toml
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
106
userfront/test/english_locale_placeholder_test.dart
Normal file
106
userfront/test/english_locale_placeholder_test.dart
Normal 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'),
|
||||
);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user