timsquad 3.3.0 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/README.ko.md +288 -0
  2. package/README.md +158 -151
  3. package/dist/commands/compile.d.ts +3 -0
  4. package/dist/commands/compile.d.ts.map +1 -0
  5. package/dist/commands/compile.js +170 -0
  6. package/dist/commands/compile.js.map +1 -0
  7. package/dist/commands/daemon.d.ts.map +1 -1
  8. package/dist/commands/daemon.js +94 -5
  9. package/dist/commands/daemon.js.map +1 -1
  10. package/dist/commands/init.d.ts.map +1 -1
  11. package/dist/commands/init.js +12 -3
  12. package/dist/commands/init.js.map +1 -1
  13. package/dist/commands/skills.d.ts +12 -0
  14. package/dist/commands/skills.d.ts.map +1 -0
  15. package/dist/commands/skills.js +231 -0
  16. package/dist/commands/skills.js.map +1 -0
  17. package/dist/commands/upgrade.js +5 -0
  18. package/dist/commands/upgrade.js.map +1 -1
  19. package/dist/daemon/entry.js +3 -3
  20. package/dist/daemon/entry.js.map +1 -1
  21. package/dist/daemon/index.d.ts +3 -2
  22. package/dist/daemon/index.d.ts.map +1 -1
  23. package/dist/daemon/index.js +137 -45
  24. package/dist/daemon/index.js.map +1 -1
  25. package/dist/daemon/meta-cache.d.ts +1 -0
  26. package/dist/daemon/meta-cache.d.ts.map +1 -1
  27. package/dist/daemon/meta-cache.js +9 -0
  28. package/dist/daemon/meta-cache.js.map +1 -1
  29. package/dist/daemon/session-state.d.ts +19 -0
  30. package/dist/daemon/session-state.d.ts.map +1 -0
  31. package/dist/daemon/session-state.js +132 -0
  32. package/dist/daemon/session-state.js.map +1 -0
  33. package/dist/daemon/shutdown.d.ts.map +1 -1
  34. package/dist/daemon/shutdown.js +7 -1
  35. package/dist/daemon/shutdown.js.map +1 -1
  36. package/dist/index.js +4 -0
  37. package/dist/index.js.map +1 -1
  38. package/dist/lib/compile-rules.d.ts +66 -0
  39. package/dist/lib/compile-rules.d.ts.map +1 -0
  40. package/dist/lib/compile-rules.js +114 -0
  41. package/dist/lib/compile-rules.js.map +1 -0
  42. package/dist/lib/compiler.d.ts +105 -0
  43. package/dist/lib/compiler.d.ts.map +1 -0
  44. package/dist/lib/compiler.js +368 -0
  45. package/dist/lib/compiler.js.map +1 -0
  46. package/dist/lib/config.d.ts +1 -0
  47. package/dist/lib/config.d.ts.map +1 -1
  48. package/dist/lib/config.js +8 -1
  49. package/dist/lib/config.js.map +1 -1
  50. package/dist/lib/template.d.ts.map +1 -1
  51. package/dist/lib/template.js +6 -0
  52. package/dist/lib/template.js.map +1 -1
  53. package/dist/types/config.d.ts +1 -0
  54. package/dist/types/config.d.ts.map +1 -1
  55. package/dist/types/config.js +12 -1
  56. package/dist/types/config.js.map +1 -1
  57. package/dist/types/project.d.ts +1 -1
  58. package/dist/types/project.d.ts.map +1 -1
  59. package/dist/types/project.js +2 -0
  60. package/dist/types/project.js.map +1 -1
  61. package/package.json +1 -1
  62. package/templates/base/agents/overlays/domain/mobile/_common.md +13 -0
  63. package/templates/base/skills/controller/SKILL.md +111 -0
  64. package/templates/base/skills/controller/references/README.md +35 -0
  65. package/templates/base/skills/controller/rules/README.md +18 -0
  66. package/templates/base/skills/mobile/dart/SKILL.md +69 -0
  67. package/templates/base/skills/mobile/dart/rules/async-patterns.md +112 -0
  68. package/templates/base/skills/mobile/dart/rules/code-style.md +96 -0
  69. package/templates/base/skills/mobile/dart/rules/null-safety.md +84 -0
  70. package/templates/base/skills/mobile/dart/rules/type-system.md +111 -0
  71. package/templates/base/skills/mobile/flutter/SKILL.md +89 -0
  72. package/templates/base/skills/mobile/flutter/ci-cd/SKILL.md +82 -0
  73. package/templates/base/skills/mobile/flutter/ci-cd/references/ci-cd-pipeline.md +314 -0
  74. package/templates/base/skills/mobile/flutter/ci-cd/rules/code-signing.md +106 -0
  75. package/templates/base/skills/mobile/flutter/ci-cd/rules/codemagic-setup.md +116 -0
  76. package/templates/base/skills/mobile/flutter/ci-cd/rules/fastlane-setup.md +105 -0
  77. package/templates/base/skills/mobile/flutter/ci-cd/rules/github-actions.md +112 -0
  78. package/templates/base/skills/mobile/flutter/ci-cd/rules/store-deployment.md +106 -0
  79. package/templates/base/skills/mobile/flutter/ci-cd/rules/versioning.md +107 -0
  80. package/templates/base/skills/mobile/flutter/i18n/SKILL.md +78 -0
  81. package/templates/base/skills/mobile/flutter/i18n/references/i18n-architecture.md +225 -0
  82. package/templates/base/skills/mobile/flutter/i18n/rules/arb-files.md +182 -0
  83. package/templates/base/skills/mobile/flutter/i18n/rules/locale-switching.md +226 -0
  84. package/templates/base/skills/mobile/flutter/i18n/rules/localization-setup.md +137 -0
  85. package/templates/base/skills/mobile/flutter/i18n/rules/plural-gender.md +159 -0
  86. package/templates/base/skills/mobile/flutter/i18n/rules/text-direction.md +199 -0
  87. package/templates/base/skills/mobile/flutter/monitoring/SKILL.md +81 -0
  88. package/templates/base/skills/mobile/flutter/monitoring/references/monitoring-architecture.md +269 -0
  89. package/templates/base/skills/mobile/flutter/monitoring/rules/analytics.md +227 -0
  90. package/templates/base/skills/mobile/flutter/monitoring/rules/crashlytics-setup.md +195 -0
  91. package/templates/base/skills/mobile/flutter/monitoring/rules/logging.md +258 -0
  92. package/templates/base/skills/mobile/flutter/monitoring/rules/performance-monitoring.md +248 -0
  93. package/templates/base/skills/mobile/flutter/monitoring/rules/sentry-integration.md +249 -0
  94. package/templates/base/skills/mobile/flutter/networking/SKILL.md +88 -0
  95. package/templates/base/skills/mobile/flutter/networking/references/api-client-architecture.md +305 -0
  96. package/templates/base/skills/mobile/flutter/networking/rules/caching.md +212 -0
  97. package/templates/base/skills/mobile/flutter/networking/rules/connectivity.md +213 -0
  98. package/templates/base/skills/mobile/flutter/networking/rules/dio-setup.md +159 -0
  99. package/templates/base/skills/mobile/flutter/networking/rules/error-handling.md +209 -0
  100. package/templates/base/skills/mobile/flutter/networking/rules/interceptors.md +205 -0
  101. package/templates/base/skills/mobile/flutter/networking/rules/retrofit-patterns.md +194 -0
  102. package/templates/base/skills/mobile/flutter/push-notifications/SKILL.md +87 -0
  103. package/templates/base/skills/mobile/flutter/push-notifications/references/notification-architecture.md +340 -0
  104. package/templates/base/skills/mobile/flutter/push-notifications/references/platform-setup.md +286 -0
  105. package/templates/base/skills/mobile/flutter/push-notifications/rules/background-processing.md +308 -0
  106. package/templates/base/skills/mobile/flutter/push-notifications/rules/deep-linking.md +217 -0
  107. package/templates/base/skills/mobile/flutter/push-notifications/rules/fcm-setup.md +164 -0
  108. package/templates/base/skills/mobile/flutter/push-notifications/rules/local-notifications.md +262 -0
  109. package/templates/base/skills/mobile/flutter/push-notifications/rules/notification-handling.md +210 -0
  110. package/templates/base/skills/mobile/flutter/push-notifications/rules/notification-permissions.md +246 -0
  111. package/templates/base/skills/mobile/flutter/push-notifications/rules/rich-notifications.md +320 -0
  112. package/templates/base/skills/mobile/flutter/references/freezed-patterns.md +162 -0
  113. package/templates/base/skills/mobile/flutter/references/project-structure.md +170 -0
  114. package/templates/base/skills/mobile/flutter/rules/animations.md +112 -0
  115. package/templates/base/skills/mobile/flutter/rules/architecture.md +121 -0
  116. package/templates/base/skills/mobile/flutter/rules/navigation-routing.md +117 -0
  117. package/templates/base/skills/mobile/flutter/rules/performance.md +112 -0
  118. package/templates/base/skills/mobile/flutter/rules/platform-adaptive.md +126 -0
  119. package/templates/base/skills/mobile/flutter/rules/state-management.md +110 -0
  120. package/templates/base/skills/mobile/flutter/rules/testing.md +131 -0
  121. package/templates/base/skills/mobile/flutter/rules/widget-conventions.md +122 -0
  122. package/templates/base/skills/mobile/flutter/security/SKILL.md +86 -0
  123. package/templates/base/skills/mobile/flutter/security/references/mobile-security-checklist.md +168 -0
  124. package/templates/base/skills/mobile/flutter/security/rules/api-key-protection.md +206 -0
  125. package/templates/base/skills/mobile/flutter/security/rules/authentication.md +248 -0
  126. package/templates/base/skills/mobile/flutter/security/rules/data-protection.md +271 -0
  127. package/templates/base/skills/mobile/flutter/security/rules/obfuscation.md +213 -0
  128. package/templates/base/skills/mobile/flutter/security/rules/secure-storage.md +171 -0
  129. package/templates/base/skills/mobile/flutter/security/rules/ssl-pinning.md +197 -0
  130. package/templates/platforms/claude-code/CLAUDE.md.template +25 -0
  131. package/templates/platforms/claude-code/scripts/completion-guard.sh +57 -0
  132. package/templates/platforms/claude-code/scripts/phase-guard.sh +79 -0
  133. package/templates/platforms/claude-code/settings.json +75 -3
  134. package/templates/project-types/mobile-app/config.yaml +123 -0
  135. package/templates/project-types/mobile-app/process/workflow.xml +191 -0
@@ -0,0 +1,225 @@
1
+ ---
2
+ title: i18n Architecture & Translation Workflow
3
+ category: reference
4
+ source: internal
5
+ tags: architecture, directory, workflow, ci, missing-keys, translation
6
+ ---
7
+
8
+ # i18n Architecture & Translation Workflow
9
+
10
+ i18n 서비스 아키텍처. 디렉토리 구조, 번역 워크플로우, CI 자동 검증, 누락 키 감지.
11
+
12
+ ## Key Concepts
13
+
14
+ - **ARB 중심**: 모든 번역의 원본은 ARB 파일 (코드에 문자열 하드코딩 금지)
15
+ - **코드 생성**: `flutter gen-l10n` → 타입 안전한 `AppLocalizations` 클래스 자동 생성
16
+ - **CI 검증**: 빌드 파이프라인에서 누락 키, 미사용 키, 포맷 오류 자동 감지
17
+ - **번역 프로세스**: 개발자 (키 추가) → 번역가 (번역) → 검수 (QA) → 머지
18
+
19
+ ## Directory Structure
20
+
21
+ ```
22
+ lib/
23
+ ├── l10n/
24
+ │ ├── app_en.arb # 기본 (template) — 모든 키 + 메타데이터
25
+ │ ├── app_ko.arb # 한국어
26
+ │ ├── app_ms.arb # 말레이어
27
+ │ ├── app_id.arb # 인도네시아어
28
+ │ └── app_zh.arb # 중국어 (간체)
29
+
30
+ ├── core/
31
+ │ └── l10n/
32
+ │ ├── locale_notifier.dart # LocaleNotifier (Riverpod)
33
+ │ ├── app_locale.dart # AppLocale enum (지원 로캘 목록)
34
+ │ ├── l10n_extension.dart # BuildContext.l10n extension
35
+ │ └── locale_observer.dart # 시스템 로캘 변경 감지
36
+
37
+ ├── features/
38
+ │ └── settings/
39
+ │ └── presentation/
40
+ │ └── screens/
41
+ │ └── language_settings_screen.dart # 언어 설정 UI
42
+
43
+ └── .dart_tool/
44
+ └── flutter_gen/
45
+ └── gen_l10n/ # 자동 생성 (커밋 X)
46
+ ├── app_localizations.dart
47
+ ├── app_localizations_en.dart
48
+ ├── app_localizations_ko.dart
49
+ └── ...
50
+ ```
51
+
52
+ ## Translation Workflow
53
+
54
+ ```
55
+ ┌──────────────────────────────────────────────────────────┐
56
+ │ 번역 워크플로우 │
57
+ ├──────────┬────────────────┬──────────────┬───────────────┤
58
+ │ 1. 개발자 │ 2. 번역가 │ 3. 검수 │ 4. 머지 │
59
+ ├──────────┼────────────────┼──────────────┼───────────────┤
60
+ │ app_en.arb│ app_ko.arb │ 화면 확인 │ PR 승인 │
61
+ │ 키 추가 │ app_ms.arb │ 문맥 확인 │ CI 통과 │
62
+ │ @메타데이터│ app_id.arb │ 길이 확인 │ 머지 │
63
+ │ 설명 작성 │ 번역 작성 │ RTL 확인 │ 배포 │
64
+ ├──────────┼────────────────┼──────────────┼───────────────┤
65
+ │ PR 생성 │ 번역 PR 생성 │ 리뷰 코멘트 │ gen-l10n 실행 │
66
+ └──────────┴────────────────┴──────────────┴───────────────┘
67
+ ```
68
+
69
+ ### 단계별 상세
70
+
71
+ ```
72
+ 1. 개발자 (키 추가)
73
+ - app_en.arb에 새 키 + @메타데이터 추가
74
+ - description에 화면 위치, 용도, 맥락 설명
75
+ - placeholder에 type, example 명시
76
+ - 스크린샷/화면 위치 정보 첨부 (번역가 맥락 전달)
77
+
78
+ 2. 번역가 (번역)
79
+ - 각 로캘 ARB 파일에 번역 추가
80
+ - ICU 복수형/성별 규칙 적용 (언어별)
81
+ - placeholder 위치 조정 (언어 어순에 맞게)
82
+
83
+ 3. 검수 (QA)
84
+ - 실제 화면에서 번역 확인 (텍스트 잘림, 줄바꿈)
85
+ - RTL 레이아웃 확인 (해당 시)
86
+ - 복수형/성별 변형 모두 확인
87
+ - 문화적 적절성 확인
88
+
89
+ 4. 머지
90
+ - CI 자동 검증 통과 확인
91
+ - flutter gen-l10n 빌드 성공 확인
92
+ - 머지 후 릴리스 포함
93
+ ```
94
+
95
+ ## CI Automated Verification
96
+
97
+ ### 누락 키 감지 스크립트
98
+
99
+ ```bash
100
+ #!/bin/bash
101
+ # scripts/check_l10n.sh — CI에서 실행
102
+
103
+ set -e
104
+
105
+ echo "=== i18n Key Verification ==="
106
+
107
+ # 1. gen-l10n 빌드 테스트
108
+ flutter gen-l10n
109
+ echo "✓ gen-l10n succeeded"
110
+
111
+ # 2. 누락 키 체크 (template ARB 기준)
112
+ TEMPLATE="lib/l10n/app_en.arb"
113
+ ERRORS=0
114
+
115
+ for ARB in lib/l10n/app_*.arb; do
116
+ if [ "$ARB" = "$TEMPLATE" ]; then continue; fi
117
+
118
+ LOCALE=$(basename "$ARB" .arb | sed 's/app_//')
119
+
120
+ # template의 비-메타 키 추출
121
+ TEMPLATE_KEYS=$(grep -oP '^\s*"(?!@|@@)\K[^"]+' "$TEMPLATE" | sort)
122
+ LOCALE_KEYS=$(grep -oP '^\s*"(?!@|@@)\K[^"]+' "$ARB" | sort)
123
+
124
+ # 누락 키 찾기
125
+ MISSING=$(comm -23 <(echo "$TEMPLATE_KEYS") <(echo "$LOCALE_KEYS"))
126
+ if [ -n "$MISSING" ]; then
127
+ echo "✗ $LOCALE: Missing keys:"
128
+ echo "$MISSING" | sed 's/^/ /'
129
+ ERRORS=$((ERRORS + 1))
130
+ else
131
+ echo "✓ $LOCALE: All keys present"
132
+ fi
133
+ done
134
+
135
+ if [ $ERRORS -gt 0 ]; then
136
+ echo "FAIL: $ERRORS locale(s) have missing keys"
137
+ exit 1
138
+ fi
139
+
140
+ echo "=== All i18n checks passed ==="
141
+ ```
142
+
143
+ ### 미사용 키 감지
144
+
145
+ ```bash
146
+ #!/bin/bash
147
+ # scripts/check_unused_l10n.sh
148
+
149
+ TEMPLATE="lib/l10n/app_en.arb"
150
+ UNUSED=0
151
+
152
+ # template의 키 목록 추출
153
+ KEYS=$(grep -oP '^\s*"(?!@|@@)\K[^"]+' "$TEMPLATE")
154
+
155
+ for KEY in $KEYS; do
156
+ # Dart 코드에서 사용 여부 확인 (.l10n.$KEY 또는 l10n.$KEY)
157
+ if ! grep -rq "\.$KEY" lib/ --include="*.dart" 2>/dev/null; then
158
+ echo "⚠ Potentially unused: $KEY"
159
+ UNUSED=$((UNUSED + 1))
160
+ fi
161
+ done
162
+
163
+ if [ $UNUSED -gt 0 ]; then
164
+ echo "WARNING: $UNUSED potentially unused key(s)"
165
+ fi
166
+ ```
167
+
168
+ ## ARB Key Organization
169
+
170
+ ```json
171
+ // 키 그룹화 전략 (featureName_ 접두사로 자연 정렬)
172
+ {
173
+ "@@locale": "en",
174
+
175
+ // === Common (공통) ===
176
+ "common_cancelButton": "Cancel",
177
+ "common_confirmButton": "Confirm",
178
+ "common_deleteButton": "Delete",
179
+ "common_loadingMessage": "Loading...",
180
+ "common_retryButton": "Retry",
181
+
182
+ // === Error (에러) ===
183
+ "error_networkTimeout": "Network timeout. Please try again.",
184
+ "error_serverError": "Server error. Please try later.",
185
+ "error_unauthorized": "Please log in again.",
186
+
187
+ // === Match (매치) ===
188
+ "matchDetail_inviteButton": "Invite to Match",
189
+ "matchDetail_playerCount": "{count, plural, =0{No players} =1{1 player} other{{count} players}}",
190
+ "matchList_emptyState": "No matches available",
191
+ "matchList_title": "Available Matches",
192
+
193
+ // === Settings (설정) ===
194
+ "settings_languageTitle": "Language",
195
+ "settings_systemLanguage": "System Language"
196
+ }
197
+ ```
198
+
199
+ ## Common Pitfalls
200
+
201
+ 1. **gen-l10n 미실행**: ARB 수정 후 코드 생성 안 하면 IDE 자동완성 미반영
202
+ 2. **@@locale 누락**: ARB 파일에 로캘 코드 없으면 파일명에서 유추 (명시 권장)
203
+ 3. **ICU 구문 오류**: 중괄호 불일치, other 누락 → gen-l10n 빌드 실패
204
+ 4. **텍스트 길이**: 독일어 등 긴 번역 → UI 레이아웃 깨짐 (Expanded/Flexible 사용)
205
+ 5. **날짜/숫자 포맷**: ARB placeholder format 미지정 → 로캘별 포맷 미적용
206
+ 6. **컨텍스트 없는 description**: 번역가가 맥락 모르면 오역 → 화면 위치/용도 필수
207
+ 7. **하드코딩된 문자열**: 로그, 에러 메시지도 ARB로 (사용자 노출 가능성)
208
+ 8. **synthetic-package 이해**: `.dart_tool/` 에 생성 → `.gitignore` 에 이미 포함 (커밋 X)
209
+
210
+ ## Examples
211
+
212
+ ### 최소 구현 체크리스트
213
+
214
+ ```
215
+ [ ] pubspec.yaml: flutter_localizations + intl + generate: true
216
+ [ ] l10n.yaml: arb-dir, template-arb-file, output-localization-file
217
+ [ ] lib/l10n/app_en.arb: 기본 키 + @메타데이터
218
+ [ ] lib/l10n/app_ko.arb (등): 번역 ARB
219
+ [ ] MaterialApp: localizationsDelegates (4종) + supportedLocales
220
+ [ ] core/l10n/: LocaleNotifier + AppLocale enum
221
+ [ ] main.dart: SharedPreferences 초기화 + ProviderScope override
222
+ [ ] 설정 화면: 언어 선택 UI
223
+ [ ] CI: 누락 키 검증 스크립트
224
+ [ ] 위젯 테스트: RTL 레이아웃 확인
225
+ ```
@@ -0,0 +1,182 @@
1
+ ---
2
+ title: ARB File Management
3
+ impact: CRITICAL
4
+ impactDescription: "ARB 구조 불일치 → 빌드 실패, 키 누락 → 런타임 null, 네이밍 비일관 → 유지보수 비용 증가"
5
+ tags: arb, app-resource-bundle, placeholder, gen-l10n, naming
6
+ ---
7
+
8
+ ## ARB File Management
9
+
10
+ **Impact: CRITICAL (ARB 구조 불일치 → 빌드 실패, 키 누락 → 런타임 null, 네이밍 비일관 → 유지보수 비용 증가)**
11
+
12
+ ARB (Application Resource Bundle) 파일 구조, @placeholder 메타데이터, 네이밍 컨벤션,
13
+ 자동 생성 워크플로우. 모든 번역의 원본 소스.
14
+
15
+ ### 기본 ARB 파일 구조
16
+
17
+ **Incorrect (메타데이터 없는 평면 구조):**
18
+ ```json
19
+ {
20
+ "hello": "Hello",
21
+ "welcome": "Welcome, {name}",
22
+ "items": "You have {count} items"
23
+ }
24
+ ```
25
+
26
+ **Correct (메타데이터 포함 구조화된 ARB):**
27
+ ```json
28
+ {
29
+ "@@locale": "en",
30
+ "@@last_modified": "2026-02-18T10:00:00+09:00",
31
+
32
+ "appTitle": "MyApp",
33
+ "@appTitle": {
34
+ "description": "The application title shown in AppBar"
35
+ },
36
+
37
+ "welcomeMessage": "Welcome, {userName}!",
38
+ "@welcomeMessage": {
39
+ "description": "Greeting shown on home screen after login",
40
+ "placeholders": {
41
+ "userName": {
42
+ "type": "String",
43
+ "example": "Tim"
44
+ }
45
+ }
46
+ },
47
+
48
+ "matchCount": "{count, plural, =0{No matches} =1{1 match} other{{count} matches}}",
49
+ "@matchCount": {
50
+ "description": "Number of available matches",
51
+ "placeholders": {
52
+ "count": {
53
+ "type": "int",
54
+ "example": "5"
55
+ }
56
+ }
57
+ }
58
+ }
59
+ ```
60
+
61
+ ### 키 네이밍 컨벤션
62
+
63
+ ```
64
+ 패턴: featureName_context_element
65
+
66
+ 예시:
67
+ ✅ matchDetail_inviteButton → 매치 상세 화면의 초대 버튼
68
+ ✅ matchDetail_playerCount → 매치 상세의 참가자 수
69
+ ✅ settings_languageLabel → 설정 화면의 언어 레이블
70
+ ✅ common_cancelButton → 공통 취소 버튼
71
+ ✅ error_networkTimeout → 에러 메시지: 네트워크 타임아웃
72
+ ✅ validation_emailInvalid → 유효성 검사: 이메일 형식 오류
73
+
74
+ ❌ invite → 맥락 없음 (어디서 사용?)
75
+ ❌ button1 → 의미 없는 번호
76
+ ❌ match_detail_invite_button → snake_case 혼용 (camelCase 사용)
77
+ ❌ MATCH_INVITE → 대문자 (camelCase 사용)
78
+ ```
79
+
80
+ ### 다국어 ARB 파일 관리
81
+
82
+ ```json
83
+ // lib/l10n/app_en.arb (기본 — template ARB)
84
+ {
85
+ "@@locale": "en",
86
+ "matchDetail_inviteButton": "Invite to Match",
87
+ "@matchDetail_inviteButton": {
88
+ "description": "Button to invite a player to the match"
89
+ },
90
+ "matchDetail_playerCount": "{count, plural, =0{No players} =1{1 player} other{{count} players}}",
91
+ "@matchDetail_playerCount": {
92
+ "description": "Number of players in the match",
93
+ "placeholders": {
94
+ "count": { "type": "int" }
95
+ }
96
+ }
97
+ }
98
+
99
+ // lib/l10n/app_ko.arb
100
+ {
101
+ "@@locale": "ko",
102
+ "matchDetail_inviteButton": "매치 초대",
103
+ "matchDetail_playerCount": "{count, plural, =0{참가자 없음} =1{1명} other{{count}명}}"
104
+ }
105
+
106
+ // lib/l10n/app_ms.arb
107
+ {
108
+ "@@locale": "ms",
109
+ "matchDetail_inviteButton": "Jemput ke Perlawanan",
110
+ "matchDetail_playerCount": "{count, plural, =0{Tiada pemain} =1{1 pemain} other{{count} pemain}}"
111
+ }
112
+ ```
113
+
114
+ ### @placeholder 메타데이터
115
+
116
+ ```json
117
+ {
118
+ "orderSummary": "Order #{orderId} — {itemCount} items, {totalPrice}",
119
+ "@orderSummary": {
120
+ "description": "Order summary displayed in confirmation screen",
121
+ "placeholders": {
122
+ "orderId": {
123
+ "type": "String",
124
+ "example": "A1234"
125
+ },
126
+ "itemCount": {
127
+ "type": "int",
128
+ "format": "compact",
129
+ "example": "3"
130
+ },
131
+ "totalPrice": {
132
+ "type": "double",
133
+ "format": "currency",
134
+ "optionalParameters": {
135
+ "decimalDigits": 2,
136
+ "symbol": "$"
137
+ },
138
+ "example": "29.99"
139
+ }
140
+ }
141
+ },
142
+
143
+ "lastUpdated": "Last updated: {date}",
144
+ "@lastUpdated": {
145
+ "description": "Timestamp for last data refresh",
146
+ "placeholders": {
147
+ "date": {
148
+ "type": "DateTime",
149
+ "format": "yMMMd",
150
+ "example": "Feb 18, 2026"
151
+ }
152
+ }
153
+ }
154
+ }
155
+ ```
156
+
157
+ ### 자동 생성
158
+
159
+ ```bash
160
+ # ARB 파일에서 Dart 코드 생성
161
+ flutter gen-l10n
162
+
163
+ # 생성 결과 (synthetic-package: true 기본):
164
+ # .dart_tool/flutter_gen/gen_l10n/
165
+ # ├── app_localizations.dart # 추상 클래스
166
+ # ├── app_localizations_en.dart # English 구현
167
+ # └── app_localizations_ko.dart # Korean 구현
168
+
169
+ # import 경로:
170
+ # import 'package:flutter_gen/gen_l10n/app_localizations.dart';
171
+ ```
172
+
173
+ ### 규칙
174
+
175
+ - `app_en.arb` → template ARB (기본), 모든 키와 `@` 메타데이터 포함
176
+ - 번역 ARB (app_ko.arb 등) → 키와 값만 포함, `@` 메타데이터 생략 가능
177
+ - `@@locale` → 각 ARB 파일 최상단에 로캘 코드 명시
178
+ - 키 네이밍 → `featureName_context` camelCase, 공통 키는 `common_` 접두사
179
+ - `@placeholder` → 모든 동적 값에 `type`, `example` 필수, 숫자/날짜는 `format` 추가
180
+ - template ARB의 모든 키 → 번역 ARB에도 존재해야 함 (누락 시 gen-l10n 경고)
181
+ - ARB 파일 변경 후 `flutter gen-l10n` 실행 (빌드 시 자동이지만 IDE 자동완성용)
182
+ - description → 번역가를 위한 맥락 설명, 화면 위치/용도 명시
@@ -0,0 +1,226 @@
1
+ ---
2
+ title: Runtime Locale Switching
3
+ impact: MEDIUM
4
+ impactDescription: "전환 미지원 → 앱 재시작 필요, 영속화 누락 → 매 시작 시 시스템 로캘로 리셋"
5
+ tags: locale, riverpod, shared-preferences, runtime, system-locale
6
+ ---
7
+
8
+ ## Runtime Locale Switching
9
+
10
+ **Impact: MEDIUM (전환 미지원 → 앱 재시작 필요, 영속화 누락 → 매 시작 시 시스템 로캘로 리셋)**
11
+
12
+ 런타임 로캘 전환. Riverpod 기반 LocaleNotifier, SharedPreferences 영속화,
13
+ 시스템 로캘 감지, 앱 재시작 없이 즉시 반영.
14
+
15
+ ### LocaleNotifier (Riverpod)
16
+
17
+ **Incorrect (전역 변수 + setState):**
18
+ ```dart
19
+ // 전역 변수로 로캘 관리 → 위젯 트리 재빌드 불완전
20
+ Locale currentLocale = const Locale('en');
21
+
22
+ void changeLocale(Locale locale) {
23
+ currentLocale = locale;
24
+ // setState? → MaterialApp 레벨까지 전파 불가
25
+ }
26
+ ```
27
+
28
+ **Correct (Riverpod StateNotifier + 영속화):**
29
+ ```dart
30
+ /// 지원 로캘 목록
31
+ enum AppLocale {
32
+ en(Locale('en'), 'English'),
33
+ ko(Locale('ko'), '한국어'),
34
+ ms(Locale('ms'), 'Bahasa Melayu'),
35
+ id(Locale('id'), 'Bahasa Indonesia');
36
+
37
+ const AppLocale(this.locale, this.displayName);
38
+ final Locale locale;
39
+ final String displayName;
40
+
41
+ static AppLocale fromCode(String code) {
42
+ return AppLocale.values.firstWhere(
43
+ (l) => l.locale.languageCode == code,
44
+ orElse: () => AppLocale.en,
45
+ );
46
+ }
47
+ }
48
+
49
+ /// 로캘 상태 관리 + 영속화
50
+ class LocaleNotifier extends StateNotifier<Locale?> {
51
+ final SharedPreferences _prefs;
52
+ static const _key = 'app_locale';
53
+
54
+ LocaleNotifier(this._prefs) : super(null) {
55
+ _loadSavedLocale();
56
+ }
57
+
58
+ void _loadSavedLocale() {
59
+ final saved = _prefs.getString(_key);
60
+ if (saved != null) {
61
+ state = AppLocale.fromCode(saved).locale;
62
+ }
63
+ // null → 시스템 로캘 사용 (MaterialApp.locale = null)
64
+ }
65
+
66
+ /// 로캘 변경 (즉시 반영 + 영속화)
67
+ Future<void> setLocale(AppLocale appLocale) async {
68
+ state = appLocale.locale;
69
+ await _prefs.setString(_key, appLocale.locale.languageCode);
70
+ }
71
+
72
+ /// 시스템 로캘로 리셋
73
+ Future<void> resetToSystem() async {
74
+ state = null;
75
+ await _prefs.remove(_key);
76
+ }
77
+
78
+ /// 현재 유효 로캘 (상태 또는 시스템)
79
+ Locale effectiveLocale(BuildContext context) {
80
+ return state ?? Localizations.localeOf(context);
81
+ }
82
+ }
83
+
84
+ /// Riverpod Providers
85
+ final sharedPreferencesProvider = Provider<SharedPreferences>((ref) {
86
+ throw UnimplementedError('Override in ProviderScope');
87
+ });
88
+
89
+ final localeNotifierProvider =
90
+ StateNotifierProvider<LocaleNotifier, Locale?>((ref) {
91
+ return LocaleNotifier(ref.watch(sharedPreferencesProvider));
92
+ });
93
+
94
+ final localeProvider = Provider<Locale?>((ref) {
95
+ return ref.watch(localeNotifierProvider);
96
+ });
97
+ ```
98
+
99
+ ### MaterialApp 연동
100
+
101
+ ```dart
102
+ class MyApp extends ConsumerWidget {
103
+ const MyApp({super.key});
104
+
105
+ @override
106
+ Widget build(BuildContext context, WidgetRef ref) {
107
+ final locale = ref.watch(localeProvider);
108
+
109
+ return MaterialApp.router(
110
+ locale: locale, // null → 시스템 로캘 사용
111
+ localizationsDelegates: const [
112
+ AppLocalizations.delegate,
113
+ GlobalMaterialLocalizations.delegate,
114
+ GlobalWidgetsLocalizations.delegate,
115
+ GlobalCupertinoLocalizations.delegate,
116
+ ],
117
+ supportedLocales: AppLocalizations.supportedLocales,
118
+ // 시스템 로캘과 지원 로캘 매칭 전략
119
+ localeResolutionCallback: (deviceLocale, supportedLocales) {
120
+ for (final supported in supportedLocales) {
121
+ if (supported.languageCode == deviceLocale?.languageCode) {
122
+ return supported;
123
+ }
124
+ }
125
+ return supportedLocales.first; // 폴백: 첫 번째 (en)
126
+ },
127
+ routerConfig: ref.watch(routerProvider),
128
+ );
129
+ }
130
+ }
131
+ ```
132
+
133
+ ### main.dart 초기화
134
+
135
+ ```dart
136
+ Future<void> main() async {
137
+ WidgetsFlutterBinding.ensureInitialized();
138
+
139
+ // SharedPreferences 초기화 (앱 시작 시 1회)
140
+ final prefs = await SharedPreferences.getInstance();
141
+
142
+ runApp(
143
+ ProviderScope(
144
+ overrides: [
145
+ sharedPreferencesProvider.overrideWithValue(prefs),
146
+ ],
147
+ child: const MyApp(),
148
+ ),
149
+ );
150
+ }
151
+ ```
152
+
153
+ ### 설정 화면 UI
154
+
155
+ ```dart
156
+ class LanguageSettingsScreen extends ConsumerWidget {
157
+ const LanguageSettingsScreen({super.key});
158
+
159
+ @override
160
+ Widget build(BuildContext context, WidgetRef ref) {
161
+ final currentLocale = ref.watch(localeProvider);
162
+
163
+ return Scaffold(
164
+ appBar: AppBar(title: Text(context.l10n.settings_languageTitle)),
165
+ body: ListView(
166
+ children: [
167
+ // 시스템 로캘 옵션
168
+ RadioListTile<Locale?>(
169
+ title: Text(context.l10n.settings_systemLanguage),
170
+ subtitle: Text(_getSystemLocaleName(context)),
171
+ value: null,
172
+ groupValue: currentLocale,
173
+ onChanged: (_) {
174
+ ref.read(localeNotifierProvider.notifier).resetToSystem();
175
+ },
176
+ ),
177
+ const Divider(),
178
+ // 지원 로캘 목록
179
+ ...AppLocale.values.map((appLocale) {
180
+ return RadioListTile<Locale?>(
181
+ title: Text(appLocale.displayName),
182
+ value: appLocale.locale,
183
+ groupValue: currentLocale,
184
+ onChanged: (_) {
185
+ ref.read(localeNotifierProvider.notifier).setLocale(appLocale);
186
+ },
187
+ );
188
+ }),
189
+ ],
190
+ ),
191
+ );
192
+ }
193
+
194
+ String _getSystemLocaleName(BuildContext context) {
195
+ final systemLocale = WidgetsBinding.instance.platformDispatcher.locale;
196
+ return systemLocale.languageCode.toUpperCase();
197
+ }
198
+ }
199
+ ```
200
+
201
+ ### 시스템 로캘 변경 감지
202
+
203
+ ```dart
204
+ // WidgetsBindingObserver로 시스템 로캘 변경 감지
205
+ class LocaleObserver extends WidgetsBindingObserver {
206
+ final VoidCallback onLocaleChanged;
207
+
208
+ LocaleObserver({required this.onLocaleChanged});
209
+
210
+ @override
211
+ void didChangeLocales(List<Locale>? locales) {
212
+ // 사용자가 "시스템 로캘 사용" 선택한 경우에만 반응
213
+ onLocaleChanged();
214
+ }
215
+ }
216
+ ```
217
+
218
+ ### 규칙
219
+
220
+ - `LocaleNotifier` → Riverpod StateNotifier, `Locale?` 상태 (null = 시스템 로캘)
221
+ - `SharedPreferences` → 선택 로캘 영속화, 앱 재시작 시 복원
222
+ - `MaterialApp.locale` → `null` 전달 시 시스템 로캘 자동 사용
223
+ - `localeResolutionCallback` → 지원 로캘과 시스템 로캘 매칭, 미지원 시 폴백
224
+ - 앱 재시작 없이 전환 → `locale` 변경 시 MaterialApp 재빌드 → 즉시 반영
225
+ - "시스템 언어 사용" 옵션 → 사용자에게 항상 제공 (기본값)
226
+ - `didChangeLocales` → 시스템 언어 변경 시 실시간 반영 (시스템 모드일 때만)