@yeongjaeyou/claude-code-config 0.17.0 → 0.18.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.
- package/.claude/agents/code-review-handler.md +47 -171
- package/.claude/commands/gh/auto-review-loop.md +107 -130
- package/.claude/guidelines/work-guidelines.md +29 -0
- package/.claude/skills/gradio-cv-app/SKILL.md +170 -0
- package/.claude/skills/gradio-cv-app/references/github-references.md +134 -0
- package/.claude/skills/gradio-cv-app/references/i18n-patterns.md +416 -0
- package/.claude/skills/gradio-cv-app/references/refined-theme.md +403 -0
- package/.claude/skills/gradio-cv-app/references/task-templates.md +433 -0
- package/package.json +1 -1
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
# Internationalization (i18n) Patterns
|
|
2
|
+
|
|
3
|
+
Simple dictionary-based internationalization for Gradio CV applications. Supports Korean and English.
|
|
4
|
+
|
|
5
|
+
## Basic Pattern
|
|
6
|
+
|
|
7
|
+
### Label Dictionary
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
LABELS = {
|
|
11
|
+
"en": {
|
|
12
|
+
"title": "Image Classification",
|
|
13
|
+
"upload": "Upload Image",
|
|
14
|
+
"result": "Result",
|
|
15
|
+
"run": "Run",
|
|
16
|
+
"clear": "Clear",
|
|
17
|
+
"loading": "Processing...",
|
|
18
|
+
"error": "An error occurred",
|
|
19
|
+
},
|
|
20
|
+
"ko": {
|
|
21
|
+
"title": "이미지 분류",
|
|
22
|
+
"upload": "이미지 업로드",
|
|
23
|
+
"result": "결과",
|
|
24
|
+
"run": "실행",
|
|
25
|
+
"clear": "초기화",
|
|
26
|
+
"loading": "처리 중...",
|
|
27
|
+
"error": "오류가 발생했습니다",
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
def L(key: str, lang: str = "en") -> str:
|
|
32
|
+
"""Get localized label"""
|
|
33
|
+
return LABELS.get(lang, LABELS["en"]).get(key, key)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Usage in Gradio
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
import gradio as gr
|
|
40
|
+
|
|
41
|
+
lang = "ko" # or "en"
|
|
42
|
+
|
|
43
|
+
with gr.Blocks() as demo:
|
|
44
|
+
gr.Markdown(f"# {L('title', lang)}")
|
|
45
|
+
image = gr.Image(label=L("upload", lang), type="pil")
|
|
46
|
+
output = gr.Label(label=L("result", lang))
|
|
47
|
+
btn = gr.Button(L("run", lang), variant="primary")
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Common UI Labels
|
|
53
|
+
|
|
54
|
+
### General
|
|
55
|
+
|
|
56
|
+
| Key | English | Korean |
|
|
57
|
+
|-----|---------|--------|
|
|
58
|
+
| `run` | Run | 실행 |
|
|
59
|
+
| `submit` | Submit | 제출 |
|
|
60
|
+
| `clear` | Clear | 초기화 |
|
|
61
|
+
| `reset` | Reset | 리셋 |
|
|
62
|
+
| `cancel` | Cancel | 취소 |
|
|
63
|
+
| `loading` | Loading... | 로딩 중... |
|
|
64
|
+
| `processing` | Processing... | 처리 중... |
|
|
65
|
+
| `done` | Done | 완료 |
|
|
66
|
+
| `error` | Error | 오류 |
|
|
67
|
+
| `success` | Success | 성공 |
|
|
68
|
+
|
|
69
|
+
### Image Components
|
|
70
|
+
|
|
71
|
+
| Key | English | Korean |
|
|
72
|
+
|-----|---------|--------|
|
|
73
|
+
| `upload_image` | Upload Image | 이미지 업로드 |
|
|
74
|
+
| `input_image` | Input Image | 입력 이미지 |
|
|
75
|
+
| `output_image` | Output Image | 출력 이미지 |
|
|
76
|
+
| `result` | Result | 결과 |
|
|
77
|
+
| `original` | Original | 원본 |
|
|
78
|
+
| `preview` | Preview | 미리보기 |
|
|
79
|
+
|
|
80
|
+
### Settings
|
|
81
|
+
|
|
82
|
+
| Key | English | Korean |
|
|
83
|
+
|-----|---------|--------|
|
|
84
|
+
| `settings` | Settings | 설정 |
|
|
85
|
+
| `advanced` | Advanced Options | 고급 옵션 |
|
|
86
|
+
| `language` | Language | 언어 |
|
|
87
|
+
| `theme` | Theme | 테마 |
|
|
88
|
+
| `dark_mode` | Dark Mode | 다크 모드 |
|
|
89
|
+
| `light_mode` | Light Mode | 라이트 모드 |
|
|
90
|
+
|
|
91
|
+
### Model Parameters
|
|
92
|
+
|
|
93
|
+
| Key | English | Korean |
|
|
94
|
+
|-----|---------|--------|
|
|
95
|
+
| `model` | Model | 모델 |
|
|
96
|
+
| `temperature` | Temperature | 온도 |
|
|
97
|
+
| `max_tokens` | Max Tokens | 최대 토큰 |
|
|
98
|
+
| `threshold` | Threshold | 임계값 |
|
|
99
|
+
| `steps` | Steps | 스텝 |
|
|
100
|
+
| `guidance` | Guidance Scale | 가이던스 스케일 |
|
|
101
|
+
| `seed` | Seed | 시드 |
|
|
102
|
+
| `random_seed` | Random Seed | 랜덤 시드 |
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Task-Specific Labels
|
|
107
|
+
|
|
108
|
+
### OCR/VLM
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
OCR_LABELS = {
|
|
112
|
+
"en": {
|
|
113
|
+
"title": "Document OCR",
|
|
114
|
+
"upload": "Upload Document",
|
|
115
|
+
"prompt": "Enter prompt",
|
|
116
|
+
"prompt_placeholder": "Extract text from this image...",
|
|
117
|
+
"result": "Extracted Text",
|
|
118
|
+
"tab_image": "Image",
|
|
119
|
+
"tab_video": "Video",
|
|
120
|
+
"tab_pdf": "PDF",
|
|
121
|
+
},
|
|
122
|
+
"ko": {
|
|
123
|
+
"title": "문서 OCR",
|
|
124
|
+
"upload": "문서 업로드",
|
|
125
|
+
"prompt": "프롬프트 입력",
|
|
126
|
+
"prompt_placeholder": "이 이미지에서 텍스트를 추출하세요...",
|
|
127
|
+
"result": "추출된 텍스트",
|
|
128
|
+
"tab_image": "이미지",
|
|
129
|
+
"tab_video": "비디오",
|
|
130
|
+
"tab_pdf": "PDF",
|
|
131
|
+
},
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Image Classification
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
CLASSIFY_LABELS = {
|
|
139
|
+
"en": {
|
|
140
|
+
"title": "Image Classification",
|
|
141
|
+
"upload": "Upload Image",
|
|
142
|
+
"result": "Classification Result",
|
|
143
|
+
"confidence": "Confidence",
|
|
144
|
+
"top_classes": "Top Classes",
|
|
145
|
+
},
|
|
146
|
+
"ko": {
|
|
147
|
+
"title": "이미지 분류",
|
|
148
|
+
"upload": "이미지 업로드",
|
|
149
|
+
"result": "분류 결과",
|
|
150
|
+
"confidence": "신뢰도",
|
|
151
|
+
"top_classes": "상위 클래스",
|
|
152
|
+
},
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Image Generation
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
GENERATE_LABELS = {
|
|
160
|
+
"en": {
|
|
161
|
+
"title": "Image Generation",
|
|
162
|
+
"prompt": "Prompt",
|
|
163
|
+
"prompt_placeholder": "Describe the image you want to create...",
|
|
164
|
+
"negative": "Negative Prompt",
|
|
165
|
+
"style": "Style",
|
|
166
|
+
"generate": "Generate",
|
|
167
|
+
"result": "Generated Image",
|
|
168
|
+
"used_seed": "Used Seed",
|
|
169
|
+
},
|
|
170
|
+
"ko": {
|
|
171
|
+
"title": "이미지 생성",
|
|
172
|
+
"prompt": "프롬프트",
|
|
173
|
+
"prompt_placeholder": "생성하고 싶은 이미지를 설명하세요...",
|
|
174
|
+
"negative": "네거티브 프롬프트",
|
|
175
|
+
"style": "스타일",
|
|
176
|
+
"generate": "생성",
|
|
177
|
+
"result": "생성된 이미지",
|
|
178
|
+
"used_seed": "사용된 시드",
|
|
179
|
+
},
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Segmentation
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
SEGMENT_LABELS = {
|
|
187
|
+
"en": {
|
|
188
|
+
"title": "Image Segmentation",
|
|
189
|
+
"input": "Input Image",
|
|
190
|
+
"output": "Segmentation Result",
|
|
191
|
+
"target": "Segmentation Target",
|
|
192
|
+
"target_placeholder": "e.g., person, car, dog",
|
|
193
|
+
"threshold": "Confidence Threshold",
|
|
194
|
+
},
|
|
195
|
+
"ko": {
|
|
196
|
+
"title": "이미지 세그멘테이션",
|
|
197
|
+
"input": "입력 이미지",
|
|
198
|
+
"output": "세그멘테이션 결과",
|
|
199
|
+
"target": "세그멘테이션 대상",
|
|
200
|
+
"target_placeholder": "예: 사람, 자동차, 개",
|
|
201
|
+
"threshold": "신뢰도 임계값",
|
|
202
|
+
},
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Detection
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
DETECT_LABELS = {
|
|
210
|
+
"en": {
|
|
211
|
+
"title": "Deepfake Detection",
|
|
212
|
+
"upload": "Upload Image",
|
|
213
|
+
"result": "Detection Result",
|
|
214
|
+
"real": "Real",
|
|
215
|
+
"fake": "Fake",
|
|
216
|
+
"ai_generated": "AI Generated",
|
|
217
|
+
"authentic": "Authentic",
|
|
218
|
+
},
|
|
219
|
+
"ko": {
|
|
220
|
+
"title": "딥페이크 탐지",
|
|
221
|
+
"upload": "이미지 업로드",
|
|
222
|
+
"result": "탐지 결과",
|
|
223
|
+
"real": "진짜",
|
|
224
|
+
"fake": "가짜",
|
|
225
|
+
"ai_generated": "AI 생성",
|
|
226
|
+
"authentic": "원본",
|
|
227
|
+
},
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Language Selector Component
|
|
234
|
+
|
|
235
|
+
### Dropdown Style
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
import gradio as gr
|
|
239
|
+
|
|
240
|
+
LANGUAGES = {
|
|
241
|
+
"en": "English",
|
|
242
|
+
"ko": "한국어",
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
def create_language_selector():
|
|
246
|
+
return gr.Dropdown(
|
|
247
|
+
choices=list(LANGUAGES.values()),
|
|
248
|
+
value="English",
|
|
249
|
+
label="Language",
|
|
250
|
+
elem_id="lang-selector",
|
|
251
|
+
scale=0,
|
|
252
|
+
min_width=120,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
# Usage
|
|
256
|
+
with gr.Blocks() as demo:
|
|
257
|
+
with gr.Row():
|
|
258
|
+
gr.Markdown("# App Title")
|
|
259
|
+
lang_selector = create_language_selector()
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Radio Style (Compact)
|
|
263
|
+
|
|
264
|
+
```python
|
|
265
|
+
def create_language_radio():
|
|
266
|
+
return gr.Radio(
|
|
267
|
+
choices=["EN", "KO"],
|
|
268
|
+
value="EN",
|
|
269
|
+
label="",
|
|
270
|
+
elem_id="lang-selector",
|
|
271
|
+
scale=0,
|
|
272
|
+
)
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## Dynamic Language Switching
|
|
278
|
+
|
|
279
|
+
Gradio doesn't support dynamic label changes without page reload. Use one of these approaches:
|
|
280
|
+
|
|
281
|
+
### Approach 1: Query Parameter (Recommended)
|
|
282
|
+
|
|
283
|
+
```python
|
|
284
|
+
# User accesses: ?lang=ko or ?lang=en
|
|
285
|
+
# Parse in app initialization
|
|
286
|
+
|
|
287
|
+
import gradio as gr
|
|
288
|
+
from urllib.parse import parse_qs
|
|
289
|
+
|
|
290
|
+
def get_lang_from_request(request: gr.Request) -> str:
|
|
291
|
+
if request and request.query_params:
|
|
292
|
+
return request.query_params.get("lang", "en")
|
|
293
|
+
return "en"
|
|
294
|
+
|
|
295
|
+
# In interface function
|
|
296
|
+
def process(image, request: gr.Request):
|
|
297
|
+
lang = get_lang_from_request(request)
|
|
298
|
+
# Use lang for error messages, etc.
|
|
299
|
+
...
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Approach 2: JavaScript-Based Text Replacement
|
|
303
|
+
|
|
304
|
+
```python
|
|
305
|
+
# For simple label changes without state
|
|
306
|
+
LANG_SWITCH_JS = """
|
|
307
|
+
(lang) => {
|
|
308
|
+
const labels = {
|
|
309
|
+
'run': { en: 'Run', ko: '실행' },
|
|
310
|
+
'clear': { en: 'Clear', ko: '초기화' },
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
document.querySelectorAll('[data-i18n]').forEach(el => {
|
|
314
|
+
const key = el.dataset.i18n;
|
|
315
|
+
if (labels[key]) {
|
|
316
|
+
el.textContent = labels[key][lang];
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
"""
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Approach 3: Separate App Instances
|
|
324
|
+
|
|
325
|
+
```python
|
|
326
|
+
# Create language-specific launchers
|
|
327
|
+
def create_app(lang: str):
|
|
328
|
+
L = LABELS[lang]
|
|
329
|
+
|
|
330
|
+
with gr.Blocks() as demo:
|
|
331
|
+
gr.Markdown(f"# {L['title']}")
|
|
332
|
+
# ... rest of UI with L[key]
|
|
333
|
+
|
|
334
|
+
return demo
|
|
335
|
+
|
|
336
|
+
# app_en.py
|
|
337
|
+
demo = create_app("en")
|
|
338
|
+
|
|
339
|
+
# app_ko.py
|
|
340
|
+
demo = create_app("ko")
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## Complete Example
|
|
346
|
+
|
|
347
|
+
```python
|
|
348
|
+
import gradio as gr
|
|
349
|
+
from gradio.themes import Soft
|
|
350
|
+
from gradio.themes.utils import colors, fonts
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
# === Labels ===
|
|
354
|
+
LABELS = {
|
|
355
|
+
"en": {
|
|
356
|
+
"title": "Image Classifier",
|
|
357
|
+
"upload": "Upload Image",
|
|
358
|
+
"result": "Classification Result",
|
|
359
|
+
"run": "Classify",
|
|
360
|
+
"clear": "Clear",
|
|
361
|
+
},
|
|
362
|
+
"ko": {
|
|
363
|
+
"title": "이미지 분류기",
|
|
364
|
+
"upload": "이미지 업로드",
|
|
365
|
+
"result": "분류 결과",
|
|
366
|
+
"run": "분류",
|
|
367
|
+
"clear": "초기화",
|
|
368
|
+
},
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def create_app(lang: str = "en"):
|
|
373
|
+
L = LABELS[lang]
|
|
374
|
+
|
|
375
|
+
def classify(image):
|
|
376
|
+
if image is None:
|
|
377
|
+
return None
|
|
378
|
+
# Classification logic here
|
|
379
|
+
return {"Cat": 0.9, "Dog": 0.1}
|
|
380
|
+
|
|
381
|
+
with gr.Blocks() as demo:
|
|
382
|
+
gr.Markdown(f"# {L['title']}")
|
|
383
|
+
|
|
384
|
+
with gr.Row():
|
|
385
|
+
input_img = gr.Image(label=L["upload"], type="pil")
|
|
386
|
+
output = gr.Label(label=L["result"])
|
|
387
|
+
|
|
388
|
+
with gr.Row():
|
|
389
|
+
clear_btn = gr.Button(L["clear"], variant="secondary")
|
|
390
|
+
run_btn = gr.Button(L["run"], variant="primary")
|
|
391
|
+
|
|
392
|
+
run_btn.click(fn=classify, inputs=input_img, outputs=output)
|
|
393
|
+
clear_btn.click(fn=lambda: (None, None), outputs=[input_img, output])
|
|
394
|
+
|
|
395
|
+
return demo
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
# Default: English
|
|
399
|
+
demo = create_app("en")
|
|
400
|
+
|
|
401
|
+
# For Korean version, deploy separately or use query param
|
|
402
|
+
# demo = create_app("ko")
|
|
403
|
+
|
|
404
|
+
if __name__ == "__main__":
|
|
405
|
+
demo.launch()
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## Best Practices
|
|
411
|
+
|
|
412
|
+
1. **Keep labels in constants**: Define all labels at the top of the file
|
|
413
|
+
2. **Use consistent keys**: Use snake_case for label keys
|
|
414
|
+
3. **Fallback to English**: Always provide English as fallback
|
|
415
|
+
4. **Separate content from code**: Consider external JSON files for larger apps
|
|
416
|
+
5. **Test both languages**: Ensure UI looks good in both languages (Korean text is often shorter)
|