practicode 0.1.0 → 0.1.1

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/src/core.rs CHANGED
@@ -10,7 +10,7 @@ use std::{
10
10
  };
11
11
 
12
12
  pub const LANGUAGES: &[&str] = &["python", "ts", "java", "rust"];
13
- pub const UI_LANGUAGES: &[&str] = &["ko", "en"];
13
+ pub const UI_LANGUAGES: &[&str] = &["en", "ko", "ja", "zh", "es"];
14
14
  pub const THEMES: &[&str] = &["dark", "light"];
15
15
  pub const AI_PROVIDERS: &[&str] = &["codex", "claude"];
16
16
  pub const BANK_PATH: &str = ".practicode/problem_bank.json";
@@ -120,7 +120,7 @@ pub fn default_language() -> String {
120
120
  }
121
121
 
122
122
  pub fn default_ui_language() -> String {
123
- "ko".to_string()
123
+ "en".to_string()
124
124
  }
125
125
 
126
126
  pub fn default_theme() -> String {
@@ -163,20 +163,34 @@ pub fn starter_problem() -> Problem {
163
163
  slug: "hello-world".to_string(),
164
164
  difficulty: "easy".to_string(),
165
165
  topics: vec!["io".to_string()],
166
- title: map2("ko", "Hello World", "en", "Hello World"),
167
- statement: map2(
168
- "ko",
169
- "표준 출력으로 정확히 `Hello, World!`를 출력하세요.",
170
- "en",
171
- "Print exactly `Hello, World!` to stdout.",
172
- ),
173
- input: map2("ko", "입력은 없습니다.", "en", "No input."),
174
- output: map2(
175
- "ko",
176
- "`Hello, World!` 한 줄",
177
- "en",
178
- "One line: `Hello, World!`",
179
- ),
166
+ title: localized_map(&[
167
+ ("en", "Hello World"),
168
+ ("ko", "Hello World"),
169
+ ("ja", "Hello World"),
170
+ ("zh", "Hello World"),
171
+ ("es", "Hello World"),
172
+ ]),
173
+ statement: localized_map(&[
174
+ ("en", "Print exactly `Hello, World!` to stdout."),
175
+ ("ko", "표준 출력으로 정확히 `Hello, World!`를 출력하세요."),
176
+ ("ja", "標準出力に正確に `Hello, World!` を出力してください。"),
177
+ ("zh", "向标准输出准确打印 `Hello, World!`。"),
178
+ ("es", "Imprime exactamente `Hello, World!` en stdout."),
179
+ ]),
180
+ input: localized_map(&[
181
+ ("en", "No input."),
182
+ ("ko", "입력은 없습니다."),
183
+ ("ja", "入力はありません。"),
184
+ ("zh", "没有输入。"),
185
+ ("es", "No hay entrada."),
186
+ ]),
187
+ output: localized_map(&[
188
+ ("en", "One line: `Hello, World!`"),
189
+ ("ko", "`Hello, World!` 한 줄"),
190
+ ("ja", "1行: `Hello, World!`"),
191
+ ("zh", "一行: `Hello, World!`"),
192
+ ("es", "Una linea: `Hello, World!`"),
193
+ ]),
180
194
  examples: vec![IoCase {
181
195
  input: String::new(),
182
196
  output: "Hello, World!\n".to_string(),
@@ -210,6 +224,13 @@ pub fn map2(k1: &str, v1: &str, k2: &str, v2: &str) -> HashMap<String, String> {
210
224
  ])
211
225
  }
212
226
 
227
+ pub fn localized_map(entries: &[(&str, &str)]) -> HashMap<String, String> {
228
+ entries
229
+ .iter()
230
+ .map(|(key, value)| ((*key).to_string(), (*value).to_string()))
231
+ .collect()
232
+ }
233
+
213
234
  pub fn load_bank(root: &Path) -> Result<Vec<Problem>> {
214
235
  let path = root.join(BANK_PATH);
215
236
  if path.exists() {
@@ -339,9 +360,7 @@ pub fn save_state(root: &Path, state: &AppState) -> Result<()> {
339
360
 
340
361
  pub fn normalize_settings(settings: &mut Settings) {
341
362
  settings.language = normalize_language(&settings.language);
342
- if !UI_LANGUAGES.contains(&settings.ui_language.as_str()) {
343
- settings.ui_language = "ko".to_string();
344
- }
363
+ settings.ui_language = normalize_ui_language(&settings.ui_language);
345
364
  if !THEMES.contains(&settings.theme.as_str()) {
346
365
  settings.theme = "dark".to_string();
347
366
  }
@@ -364,6 +383,20 @@ pub fn normalize_language(language: &str) -> String {
364
383
  }
365
384
  }
366
385
 
386
+ pub fn normalize_ui_language(language: &str) -> String {
387
+ let lower = language.trim().to_lowercase();
388
+ let short = lower
389
+ .split(['-', '_'])
390
+ .next()
391
+ .filter(|value| !value.is_empty())
392
+ .unwrap_or("en");
393
+ if UI_LANGUAGES.contains(&short) {
394
+ short.to_string()
395
+ } else {
396
+ default_ui_language()
397
+ }
398
+ }
399
+
367
400
  pub fn normalize_next_source(source: &str) -> String {
368
401
  if source == "ai" {
369
402
  "ai".to_string()
@@ -381,14 +414,216 @@ pub fn normalize_ai_provider(provider: &str) -> String {
381
414
  }
382
415
 
383
416
  pub fn localized(map: &HashMap<String, String>, lang: &str) -> String {
384
- map.get(lang)
385
- .or_else(|| map.get("ko"))
417
+ let lang = normalize_ui_language(lang);
418
+ map.get(lang.as_str())
386
419
  .or_else(|| map.get("en"))
420
+ .or_else(|| map.get("ko"))
387
421
  .or_else(|| map.values().next())
388
422
  .cloned()
389
423
  .unwrap_or_default()
390
424
  }
391
425
 
426
+ pub fn ui_text(lang: &str, key: &str) -> &'static str {
427
+ let lang = normalize_ui_language(lang);
428
+ match (lang.as_str(), key) {
429
+ ("ko", "problem") => "문제",
430
+ ("ko", "output") => "출력",
431
+ ("ko", "command") => "명령",
432
+ ("ko", "commands") => "명령",
433
+ ("ko", "difficulty") => "난이도",
434
+ ("ko", "topics") => "주제",
435
+ ("ko", "input") => "입력",
436
+ ("ko", "examples") => "예시",
437
+ ("ko", "example") => "예시",
438
+ ("ko", "command_placeholder") => "/ 입력 후 ↑/↓로 선택, Enter 실행",
439
+ ("ko", "palette_hint") => "↑/↓ 선택 | Enter 실행 | Esc 취소",
440
+ ("ko", "hint_command") => "Enter 실행 | Esc 취소",
441
+ ("ko", "hint_list") => "↑/↓ 이동 | Enter 열기 | Esc 닫기",
442
+ ("ko", "hint_output") => "Esc 코드 | / 명령 | ? 도움말",
443
+ ("ko", "hint_code") => "Esc 후 / 명령",
444
+ ("ko", "hint_idle") => "/ 명령 | ? 도움말",
445
+ ("ko", "help_title") => "도움말",
446
+ ("ko", "daily_loop") => "기본 흐름",
447
+ ("ko", "keys") => "키",
448
+ ("ko", "debug_prints") => "디버그 출력",
449
+ ("ko", "cmd_run") => "현재 제출을 채점",
450
+ ("ko", "cmd_edit") => "코드 편집기로 돌아가기",
451
+ ("ko", "cmd_next") => "다음 문제 열기",
452
+ ("ko", "cmd_prev") => "이전 문제 열기",
453
+ ("ko", "cmd_list") => "문제 목록 열기",
454
+ ("ko", "cmd_open") => "번호, id, slug로 문제 열기",
455
+ ("ko", "cmd_giveup") => "정답 보기",
456
+ ("ko", "cmd_ai") => "현재 문제와 코드에 대해 AI에게 질문",
457
+ ("ko", "cmd_provider") => "AI provider 설정",
458
+ ("ko", "cmd_model") => "AI model 설정",
459
+ ("ko", "cmd_note") => "다음 문제 생성 메모 추가",
460
+ ("ko", "cmd_notes") => "저장된 메모 보기",
461
+ ("ko", "cmd_lang") => "코드 언어 설정",
462
+ ("ko", "cmd_ui") => "UI 언어 설정",
463
+ ("ko", "cmd_theme") => "테마 설정",
464
+ ("ko", "cmd_source") => "다음 문제 출처 설정",
465
+ ("ko", "cmd_exit") => "종료",
466
+ ("ko", "cmd_help") => "도움말 열기",
467
+
468
+ ("ja", "problem") => "問題",
469
+ ("ja", "output") => "出力",
470
+ ("ja", "command") => "コマンド",
471
+ ("ja", "commands") => "コマンド",
472
+ ("ja", "difficulty") => "難易度",
473
+ ("ja", "topics") => "トピック",
474
+ ("ja", "input") => "入力",
475
+ ("ja", "examples") => "例",
476
+ ("ja", "example") => "例",
477
+ ("ja", "command_placeholder") => "/ を入力し ↑/↓ で選択、Enter で実行",
478
+ ("ja", "palette_hint") => "↑/↓ 選択 | Enter 実行 | Esc キャンセル",
479
+ ("ja", "hint_command") => "Enter 実行 | Esc キャンセル",
480
+ ("ja", "hint_list") => "↑/↓ 移動 | Enter 開く | Esc 閉じる",
481
+ ("ja", "hint_output") => "Esc コード | / コマンド | ? ヘルプ",
482
+ ("ja", "hint_code") => "Esc の後 / コマンド",
483
+ ("ja", "hint_idle") => "/ コマンド | ? ヘルプ",
484
+ ("ja", "help_title") => "ヘルプ",
485
+ ("ja", "daily_loop") => "基本フロー",
486
+ ("ja", "keys") => "キー",
487
+ ("ja", "debug_prints") => "デバッグ出力",
488
+ ("ja", "cmd_run") => "現在の提出を判定",
489
+ ("ja", "cmd_edit") => "コードエディタに戻る",
490
+ ("ja", "cmd_next") => "次の問題を開く",
491
+ ("ja", "cmd_prev") => "前の問題を開く",
492
+ ("ja", "cmd_list") => "問題一覧を開く",
493
+ ("ja", "cmd_open") => "番号、id、slug で問題を開く",
494
+ ("ja", "cmd_giveup") => "解答を見る",
495
+ ("ja", "cmd_ai") => "現在の問題とコードについて AI に質問",
496
+ ("ja", "cmd_provider") => "AI provider を設定",
497
+ ("ja", "cmd_model") => "AI model を設定",
498
+ ("ja", "cmd_note") => "次の問題生成メモを追加",
499
+ ("ja", "cmd_notes") => "保存済みメモを見る",
500
+ ("ja", "cmd_lang") => "コード言語を設定",
501
+ ("ja", "cmd_ui") => "UI 言語を設定",
502
+ ("ja", "cmd_theme") => "テーマを設定",
503
+ ("ja", "cmd_source") => "次の問題の取得元を設定",
504
+ ("ja", "cmd_exit") => "終了",
505
+ ("ja", "cmd_help") => "ヘルプを開く",
506
+
507
+ ("zh", "problem") => "题目",
508
+ ("zh", "output") => "输出",
509
+ ("zh", "command") => "命令",
510
+ ("zh", "commands") => "命令",
511
+ ("zh", "difficulty") => "难度",
512
+ ("zh", "topics") => "主题",
513
+ ("zh", "input") => "输入",
514
+ ("zh", "examples") => "示例",
515
+ ("zh", "example") => "示例",
516
+ ("zh", "command_placeholder") => "输入 / 后用 ↑/↓ 选择,Enter 执行",
517
+ ("zh", "palette_hint") => "↑/↓ 选择 | Enter 执行 | Esc 取消",
518
+ ("zh", "hint_command") => "Enter 执行 | Esc 取消",
519
+ ("zh", "hint_list") => "↑/↓ 移动 | Enter 打开 | Esc 关闭",
520
+ ("zh", "hint_output") => "Esc 代码 | / 命令 | ? 帮助",
521
+ ("zh", "hint_code") => "Esc 后输入 / 命令",
522
+ ("zh", "hint_idle") => "/ 命令 | ? 帮助",
523
+ ("zh", "help_title") => "帮助",
524
+ ("zh", "daily_loop") => "日常流程",
525
+ ("zh", "keys") => "按键",
526
+ ("zh", "debug_prints") => "调试输出",
527
+ ("zh", "cmd_run") => "评测当前提交",
528
+ ("zh", "cmd_edit") => "回到代码编辑器",
529
+ ("zh", "cmd_next") => "打开下一题",
530
+ ("zh", "cmd_prev") => "打开上一题",
531
+ ("zh", "cmd_list") => "打开题目列表",
532
+ ("zh", "cmd_open") => "按编号、id 或 slug 打开题目",
533
+ ("zh", "cmd_giveup") => "显示参考答案",
534
+ ("zh", "cmd_ai") => "向 AI 询问当前题目和代码",
535
+ ("zh", "cmd_provider") => "设置 AI provider",
536
+ ("zh", "cmd_model") => "设置 AI model",
537
+ ("zh", "cmd_note") => "添加下次出题备注",
538
+ ("zh", "cmd_notes") => "查看保存的备注",
539
+ ("zh", "cmd_lang") => "设置代码语言",
540
+ ("zh", "cmd_ui") => "设置 UI 语言",
541
+ ("zh", "cmd_theme") => "设置主题",
542
+ ("zh", "cmd_source") => "设置下一题来源",
543
+ ("zh", "cmd_exit") => "退出",
544
+ ("zh", "cmd_help") => "打开帮助",
545
+
546
+ ("es", "problem") => "Problema",
547
+ ("es", "output") => "Salida",
548
+ ("es", "command") => "Comando",
549
+ ("es", "commands") => "Comandos",
550
+ ("es", "difficulty") => "Dificultad",
551
+ ("es", "topics") => "Temas",
552
+ ("es", "input") => "Entrada",
553
+ ("es", "examples") => "Ejemplos",
554
+ ("es", "example") => "Ejemplo",
555
+ ("es", "command_placeholder") => "Escribe /, elige con ↑/↓, Enter ejecuta",
556
+ ("es", "palette_hint") => "↑/↓ elegir | Enter ejecutar | Esc cancelar",
557
+ ("es", "hint_command") => "Enter ejecutar | Esc cancelar",
558
+ ("es", "hint_list") => "↑/↓ mover | Enter abrir | Esc cerrar",
559
+ ("es", "hint_output") => "Esc codigo | / comando | ? ayuda",
560
+ ("es", "hint_code") => "Esc y luego / comando",
561
+ ("es", "hint_idle") => "/ comando | ? ayuda",
562
+ ("es", "help_title") => "Ayuda",
563
+ ("es", "daily_loop") => "Flujo diario",
564
+ ("es", "keys") => "Teclas",
565
+ ("es", "debug_prints") => "Salida de depuracion",
566
+ ("es", "cmd_run") => "Evalua la solucion actual",
567
+ ("es", "cmd_edit") => "Volver al editor de codigo",
568
+ ("es", "cmd_next") => "Abrir el siguiente problema",
569
+ ("es", "cmd_prev") => "Abrir el problema anterior",
570
+ ("es", "cmd_list") => "Abrir la lista de problemas",
571
+ ("es", "cmd_open") => "Abrir por numero, id o slug",
572
+ ("es", "cmd_giveup") => "Mostrar la respuesta de referencia",
573
+ ("es", "cmd_ai") => "Preguntar a AI sobre el problema y codigo actuales",
574
+ ("es", "cmd_provider") => "Configurar AI provider",
575
+ ("es", "cmd_model") => "Configurar AI model",
576
+ ("es", "cmd_note") => "Agregar nota para generar problemas",
577
+ ("es", "cmd_notes") => "Ver notas guardadas",
578
+ ("es", "cmd_lang") => "Configurar lenguaje de codigo",
579
+ ("es", "cmd_ui") => "Configurar idioma de UI",
580
+ ("es", "cmd_theme") => "Configurar tema",
581
+ ("es", "cmd_source") => "Configurar fuente del siguiente problema",
582
+ ("es", "cmd_exit") => "Salir",
583
+ ("es", "cmd_help") => "Abrir ayuda",
584
+
585
+ (_, "problem") => "Problem",
586
+ (_, "output") => "Output",
587
+ (_, "command") => "Command",
588
+ (_, "commands") => "Commands",
589
+ (_, "difficulty") => "Difficulty",
590
+ (_, "topics") => "Topics",
591
+ (_, "input") => "Input",
592
+ (_, "examples") => "Examples",
593
+ (_, "example") => "Example",
594
+ (_, "command_placeholder") => "Type /, move with ↑/↓, Enter runs",
595
+ (_, "palette_hint") => "↑/↓ select | Enter run | Esc cancel",
596
+ (_, "hint_command") => "Enter submit | Esc cancel",
597
+ (_, "hint_list") => "up/down move | Enter open | Esc close",
598
+ (_, "hint_output") => "Esc code | / command | ? help",
599
+ (_, "hint_code") => "Esc then / command",
600
+ (_, "hint_idle") => "/ command | ? help",
601
+ (_, "help_title") => "Help",
602
+ (_, "daily_loop") => "Daily loop",
603
+ (_, "keys") => "Keys",
604
+ (_, "debug_prints") => "Debug prints",
605
+ (_, "cmd_run") => "Judge the current submission",
606
+ (_, "cmd_edit") => "Return to the code editor",
607
+ (_, "cmd_next") => "Open the next problem",
608
+ (_, "cmd_prev") => "Open the previous problem",
609
+ (_, "cmd_list") => "Browse problems",
610
+ (_, "cmd_open") => "Open by number, id, or slug",
611
+ (_, "cmd_giveup") => "Show the reference answer",
612
+ (_, "cmd_ai") => "Ask AI about the current problem and code",
613
+ (_, "cmd_provider") => "Set AI provider",
614
+ (_, "cmd_model") => "Set AI model",
615
+ (_, "cmd_note") => "Add a next-problem note",
616
+ (_, "cmd_notes") => "Show saved notes",
617
+ (_, "cmd_lang") => "Set code language",
618
+ (_, "cmd_ui") => "Set UI language",
619
+ (_, "cmd_theme") => "Set theme",
620
+ (_, "cmd_source") => "Set next-problem source",
621
+ (_, "cmd_exit") => "Quit",
622
+ (_, "cmd_help") => "Open help",
623
+ _ => "",
624
+ }
625
+ }
626
+
392
627
  pub fn template_for(language: &str) -> String {
393
628
  match normalize_language(language).as_str() {
394
629
  "python" => "# Read from stdin and print to stdout.\nimport sys\n\n\n".to_string(),
@@ -415,20 +650,19 @@ pub fn ensure_submission(root: &Path, problem: &Problem, settings: &Settings) ->
415
650
  }
416
651
 
417
652
  pub fn render_problem(problem: &Problem, ui_language: &str) -> String {
418
- let lang = if UI_LANGUAGES.contains(&ui_language) {
419
- ui_language
420
- } else {
421
- "ko"
422
- };
653
+ let lang = normalize_ui_language(ui_language);
423
654
  let examples = problem
424
655
  .examples
425
656
  .iter()
426
657
  .enumerate()
427
658
  .map(|(index, case)| {
428
659
  format!(
429
- "### Example {}\n\nInput\n\n{}\n\nOutput\n\n{}",
660
+ "### {} {}\n\n{}\n\n{}\n\n{}\n\n{}",
661
+ ui_text(&lang, "example"),
430
662
  index + 1,
663
+ ui_text(&lang, "input"),
431
664
  fenced_text(&case.input),
665
+ ui_text(&lang, "output"),
432
666
  fenced_text(&case.output)
433
667
  )
434
668
  })
@@ -440,13 +674,18 @@ pub fn render_problem(problem: &Problem, ui_language: &str) -> String {
440
674
  .map(|(number, _)| number)
441
675
  .unwrap_or(&problem.id);
442
676
  format!(
443
- "# {number}. {}\n\nDifficulty: {}\nTopics: {}\n\n{}\n\n## Input\n\n{}\n\n## Output\n\n{}\n\n## Examples\n\n{}",
444
- localized(&problem.title, lang),
677
+ "# {number}. {}\n\n{}: {}\n{}: {}\n\n{}\n\n## {}\n\n{}\n\n## {}\n\n{}\n\n## {}\n\n{}",
678
+ localized(&problem.title, &lang),
679
+ ui_text(&lang, "difficulty"),
445
680
  problem.difficulty,
681
+ ui_text(&lang, "topics"),
446
682
  problem.topics.join(", "),
447
- localized(&problem.statement, lang),
448
- localized(&problem.input, lang),
449
- localized(&problem.output, lang),
683
+ localized(&problem.statement, &lang),
684
+ ui_text(&lang, "input"),
685
+ localized(&problem.input, &lang),
686
+ ui_text(&lang, "output"),
687
+ localized(&problem.output, &lang),
688
+ ui_text(&lang, "examples"),
450
689
  examples
451
690
  )
452
691
  }