bibox 0.2.2

Terminal-based bibliography manager with three-panel TUI and AI-agent-friendly Markdown notes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
#[derive(Debug, Clone)]
pub enum Lang {
    En,
    Ko,
}

#[derive(Debug, Clone)]
pub struct Msgs {
    pub lang: Lang,
}

impl Default for Msgs {
    fn default() -> Self {
        Self { lang: Lang::En }
    }
}

impl Msgs {
    pub fn new(lang: &str) -> Self {
        Self {
            lang: match lang {
                "ko" => Lang::Ko,
                _ => Lang::En,
            },
        }
    }

    // ── static strings ────────────────────────────────────────────────────────

    pub fn no_entries(&self) -> &'static str {
        match self.lang {
            Lang::En => "No entries.",
            Lang::Ko => "항목이 없습니다.",
        }
    }

    pub fn collections_header(&self) -> &'static str {
        match self.lang {
            Lang::En => "# collections:",
            Lang::Ko => "# 컬렉션:",
        }
    }

    pub fn entry_count(&self, n: usize) -> String {
        match self.lang {
            Lang::En => format!("{} {}", n, if n == 1 { "entry" } else { "entries" }),
            Lang::Ko => format!("{}", n),
        }
    }

    pub fn no_title(&self) -> &'static str {
        match self.lang {
            Lang::En => "(no title)",
            Lang::Ko => "(제목 없음)",
        }
    }

    pub fn no_key(&self) -> &'static str {
        match self.lang {
            Lang::En => "(no key)",
            Lang::Ko => "키 없음",
        }
    }

    pub fn cancelled(&self) -> &'static str {
        match self.lang {
            Lang::En => "Cancelled.",
            Lang::Ko => "취소됨.",
        }
    }

    pub fn sync_complete(&self) -> &'static str {
        match self.lang {
            Lang::En => "Sync complete.",
            Lang::Ko => "sync 완료.",
        }
    }

    pub fn fetching_crossref(&self) -> &'static str {
        match self.lang {
            Lang::En => "Fetching metadata from Crossref...",
            Lang::Ko => "Crossref에서 메타데이터를 가져오는 중...",
        }
    }

    pub fn searching_unpaywall(&self) -> &'static str {
        match self.lang {
            Lang::En => "Searching Unpaywall for open access PDF...",
            Lang::Ko => "Unpaywall에서 오픈 액세스 PDF를 검색하는 중...",
        }
    }

    pub fn no_oa_pdf(&self) -> &'static str {
        match self.lang {
            Lang::En => "  No open access PDF found. Saving metadata only.",
            Lang::Ko => "  오픈 액세스 PDF를 찾을 수 없습니다. 메타데이터만 저장합니다.",
        }
    }

    pub fn download_prompt(&self) -> &'static str {
        match self.lang {
            Lang::En => "Download?",
            Lang::Ko => "다운로드하시겠습니까?",
        }
    }

    pub fn downloading(&self) -> &'static str {
        match self.lang {
            Lang::En => "  Downloading...",
            Lang::Ko => "  다운로드 중...",
        }
    }

    pub fn done(&self) -> &'static str {
        match self.lang {
            Lang::En => " done",
            Lang::Ko => " 완료",
        }
    }

    pub fn doi_not_found(&self) -> &'static str {
        match self.lang {
            Lang::En => " DOI not found.",
            Lang::Ko => " DOI를 찾을 수 없습니다.",
        }
    }

    pub fn already_exists(&self, key: &str) -> String {
        match self.lang {
            Lang::En => format!("Already in library: [{}]. Skipping.", key),
            Lang::Ko => format!("이미 라이브러리에 있습니다: [{}]. 건너뜀.", key),
        }
    }

    pub fn already_exists_with_hint(&self, key: &str) -> String {
        match self.lang {
            Lang::En => format!(
                "Already in library: [{}]. Use 'bibox edit {} --doi <DOI>' to update metadata.",
                key, key
            ),
            Lang::Ko => format!(
                "이미 라이브러리에 있습니다: [{}]. 'bibox edit {} --doi <DOI>'로 메타데이터를 업데이트하세요.",
                key, key
            ),
        }
    }

    pub fn merged_fields(&self, key: &str, n: usize) -> String {
        match self.lang {
            Lang::En => format!("Merged [{}]: {} field(s) updated.", key, n),
            Lang::Ko => format!("병합됨 [{}]: {}개 필드 업데이트.", key, n),
        }
    }

    pub fn extracting_doi(&self) -> &'static str {
        match self.lang {
            Lang::En => "Extracting DOI from PDF...",
            Lang::Ko => "PDF에서 DOI를 추출하는 중...",
        }
    }

    pub fn no_file_or_doi(&self) -> &'static str {
        match self.lang {
            Lang::En => "File path, --doi, --url, or --search required.",
            Lang::Ko => "파일 경로, --doi, --url, 또는 --search가 필요합니다.",
        }
    }

    pub fn add_to_db_prompt(&self) -> &'static str {
        match self.lang {
            Lang::En => "Add to DB? (will attempt DOI extraction)",
            Lang::Ko => "DB에 추가하시겠습니까? (DOI 추출 시도)",
        }
    }

    pub fn sync_added_note(&self) -> &'static str {
        match self.lang {
            Lang::En => "Added via sync. Metadata required.",
            Lang::Ko => "bibox sync로 추가됨. 메타데이터 입력 필요.",
        }
    }

    pub fn label_key(&self) -> &'static str {
        match self.lang {
            Lang::En => "Key",
            Lang::Ko => "",
        }
    }

    pub fn label_type(&self) -> &'static str {
        match self.lang {
            Lang::En => "Type",
            Lang::Ko => "유형",
        }
    }

    pub fn label_author(&self) -> &'static str {
        match self.lang {
            Lang::En => "Author",
            Lang::Ko => "저자",
        }
    }

    pub fn label_year(&self) -> &'static str {
        match self.lang {
            Lang::En => "Year",
            Lang::Ko => "연도",
        }
    }

    pub fn label_title(&self) -> &'static str {
        match self.lang {
            Lang::En => "Title",
            Lang::Ko => "제목",
        }
    }

    pub fn label_journal(&self) -> &'static str {
        match self.lang {
            Lang::En => "Journal",
            Lang::Ko => "저널",
        }
    }

    pub fn label_publisher(&self) -> &'static str {
        match self.lang {
            Lang::En => "Publisher",
            Lang::Ko => "출판사",
        }
    }

    pub fn label_booktitle(&self) -> &'static str {
        match self.lang {
            Lang::En => "Booktitle",
            Lang::Ko => "학회",
        }
    }

    pub fn label_volume(&self) -> &'static str {
        match self.lang {
            Lang::En => "Volume",
            Lang::Ko => "",
        }
    }

    pub fn label_number(&self) -> &'static str {
        match self.lang {
            Lang::En => "Number",
            Lang::Ko => "",
        }
    }

    pub fn label_pages(&self) -> &'static str {
        match self.lang {
            Lang::En => "Pages",
            Lang::Ko => "페이지",
        }
    }

    pub fn label_doi(&self) -> &'static str {
        "DOI"
    }

    pub fn label_tags(&self) -> &'static str {
        match self.lang {
            Lang::En => "Tags",
            Lang::Ko => "태그",
        }
    }

    pub fn label_collections(&self) -> &'static str {
        match self.lang {
            Lang::En => "Collections",
            Lang::Ko => "컬렉션",
        }
    }

    pub fn label_file(&self) -> &'static str {
        match self.lang {
            Lang::En => "File",
            Lang::Ko => "파일",
        }
    }

    pub fn label_note(&self) -> &'static str {
        match self.lang {
            Lang::En => "Note",
            Lang::Ko => "노트",
        }
    }

    pub fn label_created(&self) -> &'static str {
        match self.lang {
            Lang::En => "Created",
            Lang::Ko => "생성",
        }
    }

    pub fn label_id(&self) -> &'static str {
        "ID"
    }

    pub fn no_required_fields(&self) -> &'static str {
        match self.lang {
            Lang::En => "(missing required fields)",
            Lang::Ko => "(필수 필드 없음)",
        }
    }

    // ── parameterized strings ─────────────────────────────────────────────────

    pub fn clipboard_init_failed(&self) -> &'static str {
        match self.lang {
            Lang::En => "Clipboard init failed",
            Lang::Ko => "클립보드 초기화 실패",
        }
    }

    pub fn clipboard_copy_failed(&self) -> &'static str {
        match self.lang {
            Lang::En => "Clipboard copy failed",
            Lang::Ko => "클립보드 복사 실패",
        }
    }

    pub fn found_title(&self, title: &str) -> String {
        match self.lang {
            Lang::En => format!("  Found: {}", title),
            Lang::Ko => format!("  찾음: {}", title),
        }
    }

    pub fn doi_lookup_failed(&self, e: &str) -> String {
        match self.lang {
            Lang::En => format!("DOI lookup failed: {}", e),
            Lang::Ko => format!("DOI 조회 실패: {}", e),
        }
    }

    pub fn oa_found(&self, source: &str) -> String {
        match self.lang {
            Lang::En => format!("  Open access PDF found. (source: {})", source),
            Lang::Ko => format!("  오픈 액세스 PDF를 찾았습니다. (출처: {})", source),
        }
    }

    pub fn unpaywall_failed(&self, e: &str) -> String {
        match self.lang {
            Lang::En => format!("  Unpaywall lookup failed: {}", e),
            Lang::Ko => format!("  Unpaywall 조회 실패: {}", e),
        }
    }

    pub fn doi_found(&self, doi: &str) -> String {
        match self.lang {
            Lang::En => format!(" found: {}", doi),
            Lang::Ko => format!(" 찾음: {}", doi),
        }
    }

    pub fn doi_extract_failed(&self, e: &str) -> String {
        match self.lang {
            Lang::En => format!(" extraction failed: {}", e),
            Lang::Ko => format!(" 추출 실패: {}", e),
        }
    }

    pub fn meta_lookup_failed(&self, e: &str) -> String {
        match self.lang {
            Lang::En => format!("  Metadata lookup failed: {}. Manual entry required.", e),
            Lang::Ko => format!("  메타데이터 조회 실패: {}. 수동 입력이 필요합니다.", e),
        }
    }

    pub fn file_moved(&self, name: &str) -> String {
        match self.lang {
            Lang::En => format!("  File moved: {}", name),
            Lang::Ko => format!("  파일 이동: {}", name),
        }
    }

    pub fn file_copy_failed(&self, src: &str, dst: &str) -> String {
        match self.lang {
            Lang::En => format!("File copy failed: {} -> {}", src, dst),
            Lang::Ko => format!("파일 복사 실패: {} -> {}", src, dst),
        }
    }

    pub fn added(&self, key: &str, title: &str) -> String {
        match self.lang {
            Lang::En => format!("Added: [{}] {}", key, title),
            Lang::Ko => format!("추가됨: [{}] {}", key, title),
        }
    }

    pub fn showing_of(&self, shown: usize, total: usize) -> String {
        match self.lang {
            Lang::En => format!(
                "  ... showing {} of {}. Use --limit to see more.",
                shown, total
            ),
            Lang::Ko => format!(
                "  ... {} 개 중 {} 개 표시. --limit 으로 더 볼 수 있습니다.",
                total, shown
            ),
        }
    }

    pub fn total(&self, n: usize) -> String {
        match self.lang {
            Lang::En => format!("Total: {}", n),
            Lang::Ko => format!("{}", n),
        }
    }

    pub fn no_results(&self, query: &str) -> String {
        match self.lang {
            Lang::En => format!("No results for: \"{}\"", query),
            Lang::Ko => format!("검색 결과가 없습니다: \"{}\"", query),
        }
    }

    pub fn copied_to_clipboard(&self, key: &str) -> String {
        match self.lang {
            Lang::En => format!("Copied to clipboard: {}", key),
            Lang::Ko => format!("클립보드에 복사됨: {}", key),
        }
    }

    pub fn entry_not_found(&self, key: &str) -> String {
        match self.lang {
            Lang::En => format!("Entry not found: {}", key),
            Lang::Ko => format!("항목을 찾을 수 없습니다: {}", key),
        }
    }

    pub fn file_rename_failed(&self, path: &str) -> String {
        match self.lang {
            Lang::En => format!("File rename failed: {}", path),
            Lang::Ko => format!("파일 이름 변경 실패: {}", path),
        }
    }

    pub fn file_renamed(&self, old: &str, new: &str) -> String {
        match self.lang {
            Lang::En => format!("File renamed: {} -> {}", old, new),
            Lang::Ko => format!("파일 이름 변경: {} -> {}", old, new),
        }
    }

    pub fn updated(&self, key: &str) -> String {
        match self.lang {
            Lang::En => format!("Updated: [{}]", key),
            Lang::Ko => format!("수정됨: [{}]", key),
        }
    }

    pub fn delete_prompt(&self, key: &str, title: &str) -> String {
        match self.lang {
            Lang::En => format!("Delete [{}] {}?", key, title),
            Lang::Ko => format!("[{}] {} 를 삭제하시겠습니까?", key, title),
        }
    }

    pub fn file_deleted(&self, name: &str) -> String {
        match self.lang {
            Lang::En => format!("File deleted: {}", name),
            Lang::Ko => format!("파일 삭제: {}", name),
        }
    }

    pub fn deleted(&self, key: &str) -> String {
        match self.lang {
            Lang::En => format!("Deleted: [{}]", key),
            Lang::Ko => format!("삭제됨: [{}]", key),
        }
    }

    pub fn collect_added(&self, key: &str, cols: &str) -> String {
        match self.lang {
            Lang::En => format!("[{}] added to collections: {}", key, cols),
            Lang::Ko => format!("[{}] 컬렉션 추가: {}", key, cols),
        }
    }

    pub fn collect_skipped(&self, key: &str, cols: &str) -> String {
        match self.lang {
            Lang::En => format!("[{}] already in collections (skipped): {}", key, cols),
            Lang::Ko => format!("[{}] 이미 소속됨 (스킵): {}", key, cols),
        }
    }

    pub fn not_in_collection(&self, key: &str, col: &str) -> String {
        match self.lang {
            Lang::En => format!("[{}] is not in collection '{}'", key, col),
            Lang::Ko => format!("[{}] 는 '{}' 컬렉션에 속해 있지 않습니다.", key, col),
        }
    }

    pub fn uncollected(&self, key: &str, col: &str) -> String {
        match self.lang {
            Lang::En => format!("[{}] removed from collection: {}", key, col),
            Lang::Ko => format!("[{}] 컬렉션 제거: {}", key, col),
        }
    }

    pub fn file_read_failed(&self, path: &str) -> String {
        match self.lang {
            Lang::En => format!("Cannot read file: {}", path),
            Lang::Ko => format!("파일을 읽을 수 없습니다: {}", path),
        }
    }

    pub fn import_complete(&self, n: usize) -> String {
        match self.lang {
            Lang::En => format!("Import complete: {} added", n),
            Lang::Ko => format!("임포트 완료: {} 개 추가됨", n),
        }
    }

    pub fn skipped_header(&self, n: usize) -> String {
        match self.lang {
            Lang::En => format!("Skipped ({}):", n),
            Lang::Ko => format!("스킵됨 ({} 개):", n),
        }
    }

    pub fn zip_created(&self, path: &str, n: usize) -> String {
        match self.lang {
            Lang::En => format!("ZIP created: {} ({} files)", path, n),
            Lang::Ko => format!("ZIP 생성: {} ({} 개 파일)", path, n),
        }
    }

    pub fn folder_created(&self, path: &str, n: usize) -> String {
        match self.lang {
            Lang::En => format!("Folder created: {} ({} files)", path, n),
            Lang::Ko => format!("폴더 생성: {} ({} 개 파일)", path, n),
        }
    }

    pub fn clipboard_copied_entries(&self, n: usize) -> String {
        match self.lang {
            Lang::En => format!("Copied to clipboard ({} entries)", n),
            Lang::Ko => format!("클립보드에 복사됨 ({} 개 항목)", n),
        }
    }

    pub fn bibtex_saved(&self, path: &str, n: usize) -> String {
        match self.lang {
            Lang::En => format!("BibTeX saved: {} ({} entries)", path, n),
            Lang::Ko => format!("BibTeX 저장: {} ({} 개 항목)", path, n),
        }
    }

    pub fn sync_file_missing(&self, name: &str) -> String {
        match self.lang {
            Lang::En => format!("'{}' not found on disk. Remove from DB?", name),
            Lang::Ko => format!("'{}' 파일이 없습니다. DB에서 삭제하시겠습니까?", name),
        }
    }

    pub fn sync_removed(&self, name: &str) -> String {
        match self.lang {
            Lang::En => format!("Removed: {}", name),
            Lang::Ko => format!("삭제됨: {}", name),
        }
    }

    pub fn sync_new_file(&self, name: &str) -> String {
        match self.lang {
            Lang::En => format!("New file found: {}", name),
            Lang::Ko => format!("새 파일 발견: {}", name),
        }
    }

    pub fn sync_entry_added(&self, key: &str) -> String {
        match self.lang {
            Lang::En => format!(
                "  Added: [{}] (metadata needed: bibox edit {} --doi <DOI>)",
                key, key
            ),
            Lang::Ko => format!(
                "  추가됨: [{}] (메타데이터 입력 필요: bibox edit {} --doi <DOI>)",
                key, key
            ),
        }
    }

    pub fn entry_block_meta(
        &self,
        entry_type: &str,
        author: &str,
        year: &str,
        tags: &str,
        collections: &str,
    ) -> String {
        match self.lang {
            Lang::En => format!(
                "  type: {} | author: {} | year: {}{}{}",
                entry_type, author, year, tags, collections
            ),
            Lang::Ko => format!(
                "  유형: {} | 저자: {} | 연도: {}{}{}",
                entry_type, author, year, tags, collections
            ),
        }
    }

    pub fn tag_inline(&self, tags: &str) -> String {
        match self.lang {
            Lang::En => format!(" | tags: {}", tags),
            Lang::Ko => format!(" | 태그: {}", tags),
        }
    }

    pub fn collection_inline(&self, cols: &str) -> String {
        match self.lang {
            Lang::En => format!(" | collections: {}", cols),
            Lang::Ko => format!(" | 컬렉션: {}", cols),
        }
    }

    pub fn searching_arxiv(&self) -> &'static str {
        match self.lang {
            Lang::En => "Searching arXiv by title...",
            Lang::Ko => "arXiv에서 제목으로 검색 중...",
        }
    }

    pub fn no_arxiv_results(&self) -> &'static str {
        match self.lang {
            Lang::En => "  No results found on arXiv.",
            Lang::Ko => "  arXiv에서 결과를 찾을 수 없습니다.",
        }
    }

    pub fn arxiv_failed(&self, e: &str) -> String {
        match self.lang {
            Lang::En => format!("  arXiv search failed: {}", e),
            Lang::Ko => format!("  arXiv 검색 실패: {}", e),
        }
    }

    pub fn arxiv_found(&self, n: usize) -> String {
        match self.lang {
            Lang::En => format!("  Found {} result(s) on arXiv. Select to download:", n),
            Lang::Ko => format!("  arXiv에서 {} 개 검색됨. 선택하여 다운로드:", n),
        }
    }

    pub fn searching_crossref_query(&self, query: &str) -> String {
        match self.lang {
            Lang::En => format!("Searching Crossref for \"{}\"...", query),
            Lang::Ko => format!("Crossref에서 \"{}\" 검색 중...", query),
        }
    }

    pub fn no_search_results(&self, query: &str) -> String {
        match self.lang {
            Lang::En => format!("No results found for '{}'.", query),
            Lang::Ko => format!("'{}'에 대한 검색 결과가 없습니다.", query),
        }
    }

    pub fn url_resolve_failed(&self) -> &'static str {
        match self.lang {
            Lang::En => "Could not extract metadata from URL. Try --doi or --search instead.",
            Lang::Ko => "URL에서 메타데이터를 추출할 수 없습니다. --doi 또는 --search를 사용하세요.",
        }
    }

    pub fn url_fetch_failed(&self, reason: &str) -> String {
        match self.lang {
            Lang::En => format!("Failed to fetch URL: {}", reason),
            Lang::Ko => format!("URL 가져오기 실패: {}", reason),
        }
    }

    pub fn note_saved(&self, path: &str) -> String {
        match self.lang {
            Lang::En => format!("Note saved: {}", path),
            Lang::Ko => format!("노트 저장됨: {}", path),
        }
    }

    pub fn note_not_found(&self, key: &str) -> String {
        match self.lang {
            Lang::En => format!("No note found for '{}'.", key),
            Lang::Ko => format!("'{}'에 대한 노트가 없습니다.", key),
        }
    }

    pub fn note_already_exists(&self) -> &'static str {
        match self.lang {
            Lang::En => "Note already exists. Use --force to overwrite with template.",
            Lang::Ko => "노트가 이미 존재합니다. --force를 사용하여 템플릿으로 덮어쓰세요.",
        }
    }

    pub fn section_requires_source(&self) -> &'static str {
        match self.lang {
            Lang::En => "--section requires --stdin or --from.",
            Lang::Ko => "--section은 --stdin 또는 --from이 필요합니다.",
        }
    }

    pub fn note_written_section(&self, section: &str, path: &str) -> String {
        match self.lang {
            Lang::En => format!("Section '{}' written to {}", section, path),
            Lang::Ko => format!("섹션 '{}'이(가) {}에 작성됨", section, path),
        }
    }

    pub fn note_appended(&self, path: &str) -> String {
        match self.lang {
            Lang::En => format!("Content appended to {}", path),
            Lang::Ko => format!("내용이 {}에 추가됨", path),
        }
    }

    pub fn note_template_applied(&self, template: &str, path: &str) -> String {
        match self.lang {
            Lang::En => format!("Template '{}' applied to {}", template, path),
            Lang::Ko => format!("템플릿 '{}'이(가) {}에 적용됨", template, path),
        }
    }
}