projectops 4.31.5 → 4.33.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 (57) hide show
  1. package/README.md +144 -145
  2. package/package.json +2 -2
  3. package/src/cli/args.js +65 -29
  4. package/src/cli/errors.js +9 -7
  5. package/src/cli/help.js +51 -3
  6. package/src/commands/doctor.js +80 -78
  7. package/src/commands/full.js +11 -2
  8. package/src/commands/interactive.js +71 -49
  9. package/src/commands/issues.js +4 -0
  10. package/src/commands/skills.js +8 -7
  11. package/src/commands/version.js +4 -2
  12. package/src/commands/workflows.js +3 -1
  13. package/src/context.js +4 -0
  14. package/src/core/breaking-check.js +6 -5
  15. package/src/core/copy/simple.js +4 -0
  16. package/src/core/copy/workflows.js +10 -5
  17. package/src/core/exclusions.js +8 -0
  18. package/src/core/ide/adapters/claude.js +18 -17
  19. package/src/core/ide/adapters/codex.js +12 -11
  20. package/src/core/ide/adapters/cursor.js +11 -10
  21. package/src/core/ide/adapters/gemini.js +11 -10
  22. package/src/core/ide/adapters/pi-common.js +2 -1
  23. package/src/core/ide/adapters/pi-harness.js +13 -16
  24. package/src/core/ide/adapters/pi.js +12 -11
  25. package/src/core/label-style.js +78 -0
  26. package/src/core/migration-guide.js +38 -38
  27. package/src/core/migrations/index.js +19 -15
  28. package/src/core/migrations/registry.js +70 -55
  29. package/src/core/options-ask.js +65 -64
  30. package/src/core/orphan-workflows.js +2 -1
  31. package/src/core/paths-resolve.js +31 -30
  32. package/src/core/repo-language.js +47 -0
  33. package/src/core/run-trace.js +10 -0
  34. package/src/core/version-yml.js +68 -36
  35. package/src/i18n/core.en.js +31 -0
  36. package/src/i18n/core.ko.js +31 -0
  37. package/src/i18n/doctor.en.js +89 -0
  38. package/src/i18n/doctor.ko.js +89 -0
  39. package/src/i18n/en.js +141 -0
  40. package/src/i18n/ide.en.js +102 -0
  41. package/src/i18n/ide.ko.js +102 -0
  42. package/src/i18n/index.js +73 -0
  43. package/src/i18n/ko.js +134 -0
  44. package/src/i18n/migrate.en.js +66 -0
  45. package/src/i18n/migrate.ko.js +66 -0
  46. package/src/i18n/options.en.js +104 -0
  47. package/src/i18n/options.ko.js +104 -0
  48. package/src/i18n/summary.en.js +70 -0
  49. package/src/i18n/summary.ko.js +70 -0
  50. package/src/index.js +46 -28
  51. package/src/ui/banner.js +8 -6
  52. package/src/ui/env-plan.js +19 -18
  53. package/src/ui/prompts.js +25 -24
  54. package/src/ui/readline-engine.js +18 -10
  55. package/src/ui/skills-prompts.js +8 -7
  56. package/src/ui/status-cards.js +38 -38
  57. package/src/ui/summary.js +103 -111
@@ -0,0 +1,102 @@
1
+ // ide 영역 메시지 (en). 키는 <영역>.<의미> 형식.
2
+ export default {
3
+ "ide.claude.legacy": " Removed legacy plugin: {name} (scope: {scope})",
4
+ "ide.claude.marketAdding": "Registering the Claude Code marketplace...",
5
+ "ide.claude.marketAdded": " Marketplace registered",
6
+ "ide.claude.marketSkipped": " Marketplace is already registered or registration was skipped",
7
+ "ide.claude.installing": "Installing the Claude Code plugin (scope: {scope})...",
8
+ "ide.claude.installed": " Claude Code plugin installed (scope: {scope})",
9
+ "ide.claude.installFailed": " Plugin install failed. Manual: claude plugin install {plugin} --scope {scope}",
10
+ "ide.claude.updating": "Updating the plugin...",
11
+ "ide.claude.updateFailed": " Update failed. Manual: claude plugin update {plugin} --scope {scope}",
12
+ "ide.claude.updated": " Update complete (scope: {scope})",
13
+ "ide.claude.removeNone": " No Claude Code plugin is installed, skipping",
14
+ "ide.claude.removeTarget": " Removing: {plugin} (scope: {scope})",
15
+ "ide.claude.removed": " Plugin uninstalled",
16
+ "ide.claude.removeFailed": " Removal failed. Manual: claude plugin uninstall {plugin} --scope {scope}",
17
+ "ide.claude.manual": " Tip for Claude Code users: claude plugin marketplace add {market}\n claude plugin install {plugin} --scope user",
18
+ "ide.claude.configMigrated": " config.json migrated (settings from the previous version are kept)",
19
+ "ide.claude.dataRemoved": " Plugin data (config) removed",
20
+ "ide.installable": "Installable (CLI detected)",
21
+ "ide.codex.marketAdding": "Registering the Codex plugin marketplace...",
22
+ "ide.codex.marketAdded": " Codex marketplace registered",
23
+ "ide.codex.marketSkipped": " Codex marketplace is already registered or registration was skipped",
24
+ "ide.codex.marketUpgrading": "Updating the Codex plugin marketplace...",
25
+ "ide.codex.marketDone": " Codex marketplace registered (check with /plugins)",
26
+ "ide.codex.marketError": " Codex marketplace error. Manual: codex plugin marketplace add {market}",
27
+ "ide.codex.skillsRemoved": " Codex native skills removed ({path})",
28
+ "ide.codex.removeNone": " No Codex skills to remove, skipping",
29
+ "ide.codex.unregisterManual": " To unregister the marketplace, run manually: codex plugin marketplace remove {plugin}",
30
+ "ide.codex.legacy": " Removed legacy Codex skills: {name}",
31
+ "ide.cursor.noSource": " Could not find the skills source (a skills/ folder is required).",
32
+ "ide.cursor.copying": "Copying Cursor Skills...",
33
+ "ide.cursor.obsolete": " Removed obsolete skill: {name}",
34
+ "ide.cursor.installed": " Cursor Skills installed ({dest}/, v{version})",
35
+ "ide.cursor.copyFailed": " Cursor Skills copy failed. Check the skills/ folder.",
36
+ "ide.cursor.removeNone": " No Cursor Skills are installed, skipping",
37
+ "ide.cursor.removed": " Cursor Skills removed (projectops-owned only, {dir}/)",
38
+ "ide.cursor.removeFailed": " Cursor Skills removal failed. Check manually: {dir}",
39
+ "ide.cursor.legacy": " Cleaned up legacy Cursor Skills (selective): name={name}, v={version}, reinstalling",
40
+ "ide.cursor.manual": " Tip for Cursor: copy the skills/ folder to ~/.cursor/skills/.",
41
+ "ide.gemini.updating": "Updating the Gemini CLI extension...",
42
+ "ide.gemini.updated": " Gemini CLI extension updated",
43
+ "ide.gemini.installing": "Installing the Gemini CLI extension...",
44
+ "ide.gemini.installed": " Gemini CLI extension installed",
45
+ "ide.gemini.error": " Gemini extension error. Manual: gemini extensions install {url}",
46
+ "ide.gemini.notFound": " gemini CLI not detected, skipping",
47
+ "ide.gemini.removed": " Gemini CLI extension removed",
48
+ "ide.gemini.removeFailed": " No Gemini extension to remove, or removal failed. Manual: gemini extensions uninstall {ext}",
49
+ "ide.pi.legacyClone": " Removed legacy PI clone: SUH-DEVOPS-TEMPLATE",
50
+ "ide.pi.updating": "Updating the PI package...",
51
+ "ide.pi.installing": "Installing the PI package...",
52
+ "ide.pi.done": " PI package installed / updated",
53
+ "ide.pi.afterInstall": " Restart 'pi', check with 'pi list', then call /projectops:analyze etc. in the chat",
54
+ "ide.pi.failed": " PI install/update failed. Manual: pi install {url}",
55
+ "ide.pi.notFound": " pi CLI not detected, skipping",
56
+ "ide.pi.removeNone": " No PI package is installed, skipping",
57
+ "ide.pi.stillThere": " The package is still there after removal. Check with 'pi list'.",
58
+ "ide.pi.removed": " PI package removed",
59
+ "ide.pi.harnessAlso": " The Persona Harness registration is removed as well.",
60
+ "ide.harness.noPi": "PI not found",
61
+ "ide.harness.noLoader": "loader not found (PI install required)",
62
+ "ide.harness.enabled": "enabled",
63
+ "ide.harness.disabled": "disabled",
64
+ "ide.harness.needPi": " The harness loader is missing. Install PI first.",
65
+ "ide.harness.already": " Persona Harness: already enabled (kept)",
66
+ "ide.harness.enabledDone": " Persona Harness enabled. Restart PI to apply.",
67
+ "ide.harness.noSettings": " PI settings.json is missing. Run PI once and try again.",
68
+ "ide.harness.needPackage": " The harness loader is missing. Install or update the PI package first.",
69
+ "ide.harness.removeNone": " Persona Harness is not enabled, skipping",
70
+ "ide.harness.keepSkill": " Leaving the PI skill as is and only unregistering the harness.",
71
+ "ide.harness.removed": " Persona Harness unregistered. Restart PI to apply.",
72
+ "ide.harness.manual": " Tip for PI Persona Harness: after installing PI, register harness-loader.ts under extensions in settings.json",
73
+ "ide.harness.desc": "The Persona Harness automatically injects the 'expert personas' and the 'SDLC workflow'\ninto the system prompt every time PI starts a conversation (it can be turned on or off independently of the skill).",
74
+ "skills.action.prompt": "What should we do with the AI skills?",
75
+ "skills.action.apply": "Install / update (bring them up to date)",
76
+ "skills.action.remove": "Remove (delete installed skills)",
77
+ "skills.action.skip": "Leave as is",
78
+ "skills.target.undetected": " (not detected)",
79
+ "skills.target.promptApply": "Choose the IDEs to install / update (Space toggles, Enter confirms)",
80
+ "skills.target.promptRemove": "Choose the IDEs to remove (Space toggles, Enter confirms)",
81
+ "skills.status.noCli": "skill not installed (CLI not found)",
82
+ "skills.status.installed": "skill installed",
83
+ "skills.status.notInstalled": "skill not installed",
84
+ "skills.statusTitle": "-- IDE Skills status --",
85
+ "skills.unchanged": "Leaving IDE Skills unchanged.",
86
+ "skills.noneSelected": "No IDE selected, skipping (run again whenever you like).",
87
+ "skills.header.apply": "[ {label} install/update ]",
88
+ "skills.header.remove": "[ {label} remove ]",
89
+ "envPlan.example": " e.g. {ex}",
90
+ "envPlan.default": " Default: {value}",
91
+ "envPlan.enterValues": " Enter a value. To keep the default, type nothing and press Enter.",
92
+ "envPlan.valuePrompt": "> Enter a value (Enter keeps the default \"{def}\"):",
93
+ "envPlan.title": "Filling in the deploy workflow environment settings",
94
+ "envPlan.intro1": " These are the values the installed deploy workflows will use. Each item shows what it is used for,",
95
+ "envPlan.intro2": " a description, an example and the default. You can keep them all or change only what you want.",
96
+ "envPlan.how": "How do you want to fill them in?",
97
+ "envPlan.optAll": "1) Install everything with the defaults above (no input needed)",
98
+ "envPlan.optEach": "2) Enter them one by one (every item in order)",
99
+ "envPlan.optSome": "3) Pick a few to change (only those are asked, the rest keep defaults)",
100
+ "envPlan.defaultTag": "(default: {value})",
101
+ "envPlan.pickPrompt": "Choose the items to change (Space selects, Enter confirms)",
102
+ };
@@ -0,0 +1,102 @@
1
+ // ide 영역 메시지 (ko). 키는 <영역>.<의미> 형식.
2
+ export default {
3
+ "ide.claude.legacy": " 레거시 플러그인 정리: {name} (scope: {scope})",
4
+ "ide.claude.marketAdding": "Claude Code 마켓플레이스 등록 중...",
5
+ "ide.claude.marketAdded": " 마켓플레이스 등록 완료",
6
+ "ide.claude.marketSkipped": " 마켓플레이스 이미 등록되어 있거나 등록 생략",
7
+ "ide.claude.installing": "Claude Code 플러그인 설치 중 (scope: {scope})...",
8
+ "ide.claude.installed": " Claude Code 플러그인 설치 완료 (scope: {scope})",
9
+ "ide.claude.installFailed": " 플러그인 설치 실패. 수동: claude plugin install {plugin} --scope {scope}",
10
+ "ide.claude.updating": "플러그인 업데이트 중...",
11
+ "ide.claude.updateFailed": " 업데이트 실패. 수동: claude plugin update {plugin} --scope {scope}",
12
+ "ide.claude.updated": " 업데이트 완료 (scope: {scope})",
13
+ "ide.claude.removeNone": " 설치된 Claude Code 플러그인이 없어 건너뜁니다",
14
+ "ide.claude.removeTarget": " 제거할 대상: {plugin} (scope: {scope})",
15
+ "ide.claude.removed": " 플러그인 uninstall 완료",
16
+ "ide.claude.removeFailed": " 삭제 실패. 수동: claude plugin uninstall {plugin} --scope {scope}",
17
+ "ide.claude.manual": " 💡 Claude Code 사용자: claude plugin marketplace add {market}\n claude plugin install {plugin} --scope user",
18
+ "ide.claude.configMigrated": " config.json 마이그레이션 완료 (이전 버전 설정 유지)",
19
+ "ide.claude.dataRemoved": " 플러그인 데이터(config) 삭제 완료",
20
+ "ide.installable": "설치 가능 (CLI 감지됨)",
21
+ "ide.codex.marketAdding": "Codex plugin marketplace 등록 중...",
22
+ "ide.codex.marketAdded": " Codex marketplace 등록 완료",
23
+ "ide.codex.marketSkipped": " Codex marketplace 이미 등록되어 있거나 등록 생략",
24
+ "ide.codex.marketUpgrading": "Codex plugin marketplace 업데이트 중...",
25
+ "ide.codex.marketDone": " Codex marketplace 등록 완료 (/plugins 확인)",
26
+ "ide.codex.marketError": " Codex marketplace 관리 오류 — 수동: codex plugin marketplace add {market}",
27
+ "ide.codex.skillsRemoved": " Codex native skills 제거 완료 ({path})",
28
+ "ide.codex.removeNone": " 제거할 Codex skills가 없어 건너뜁니다",
29
+ "ide.codex.unregisterManual": " marketplace 등록 해제는 수동: codex plugin marketplace remove {plugin}",
30
+ "ide.codex.legacy": " 레거시 Codex skills 정리: {name}",
31
+ "ide.cursor.noSource": " 설치할 스킬 소스를 찾지 못했습니다 (skills/ 폴더 필요).",
32
+ "ide.cursor.copying": "Cursor Skills 복사 중...",
33
+ "ide.cursor.obsolete": " 폐기된 스킬 제거: {name}",
34
+ "ide.cursor.installed": " Cursor Skills 설치 완료 ({dest}/, v{version})",
35
+ "ide.cursor.copyFailed": " Cursor Skills 복사 실패 — skills/ 폴더를 확인하세요.",
36
+ "ide.cursor.removeNone": " 설치된 Cursor Skills가 없어 건너뜁니다",
37
+ "ide.cursor.removed": " Cursor Skills 제거 완료 (projectops 소유만, {dir}/)",
38
+ "ide.cursor.removeFailed": " Cursor Skills 제거 실패 — 수동 확인: {dir}",
39
+ "ide.cursor.legacy": " 레거시 Cursor Skills 정리(선별): name={name}, v={version} → 재설치",
40
+ "ide.cursor.manual": " 💡 Cursor: skills/ 폴더를 ~/.cursor/skills/ 로 복사하면 됩니다.",
41
+ "ide.gemini.updating": "Gemini CLI extension 업데이트 중...",
42
+ "ide.gemini.updated": " Gemini CLI extension 업데이트 완료",
43
+ "ide.gemini.installing": "Gemini CLI extension 설치 중...",
44
+ "ide.gemini.installed": " Gemini CLI extension 설치 완료",
45
+ "ide.gemini.error": " Gemini extension 관리 오류 — 수동: gemini extensions install {url}",
46
+ "ide.gemini.notFound": " gemini CLI 미감지 — 건너뜁니다",
47
+ "ide.gemini.removed": " Gemini CLI extension 제거 완료",
48
+ "ide.gemini.removeFailed": " 제거할 Gemini extension이 없거나 실패 — 수동: gemini extensions uninstall {ext}",
49
+ "ide.pi.legacyClone": " 레거시 PI clone 정리: SUH-DEVOPS-TEMPLATE",
50
+ "ide.pi.updating": "PI 패키지 업데이트 중...",
51
+ "ide.pi.installing": "PI 패키지 설치 중...",
52
+ "ide.pi.done": " PI 패키지 설치 / 업데이트 완료",
53
+ "ide.pi.afterInstall": " → 'pi' 재실행 후 'pi list' 로 확인, 채팅창에서 /projectops:analyze 등 호출",
54
+ "ide.pi.failed": " PI 설치/업데이트 실패 — 수동: pi install {url}",
55
+ "ide.pi.notFound": " pi CLI 미감지 — 건너뜁니다",
56
+ "ide.pi.removeNone": " 설치된 PI 패키지가 없어 건너뜁니다",
57
+ "ide.pi.stillThere": " 제거 후에도 패키지가 남아있습니다 — 'pi list'로 확인하세요.",
58
+ "ide.pi.removed": " PI 패키지 제거 완료",
59
+ "ide.pi.harnessAlso": " Persona Harness 등록도 함께 해제됩니다.",
60
+ "ide.harness.noPi": "PI 없음",
61
+ "ide.harness.noLoader": "loader 없음 (PI 설치 필요)",
62
+ "ide.harness.enabled": "활성화됨",
63
+ "ide.harness.disabled": "비활성화",
64
+ "ide.harness.needPi": " harness loader가 없습니다 — 먼저 PI를 설치하세요.",
65
+ "ide.harness.already": " Persona Harness: 이미 활성화됨 (유지)",
66
+ "ide.harness.enabledDone": " Persona Harness 활성화 완료 — PI 재시작 후 적용됩니다.",
67
+ "ide.harness.noSettings": " PI settings.json이 없습니다 — PI를 한 번 실행한 뒤 다시 시도하세요.",
68
+ "ide.harness.needPackage": " harness loader가 없습니다 — 먼저 PI 패키지를 설치/업데이트하세요.",
69
+ "ide.harness.removeNone": " Persona Harness가 활성화돼 있지 않아 건너뜁니다",
70
+ "ide.harness.keepSkill": " PI skill은 그대로 두고 harness 등록만 해제합니다.",
71
+ "ide.harness.removed": " Persona Harness 해제 완료 — PI 재시작 후 적용됩니다.",
72
+ "ide.harness.manual": " 💡 PI Persona Harness: PI 설치 후 settings.json extensions 에 harness-loader.ts 등록",
73
+ "ide.harness.desc": "Persona Harness는 PI가 대화를 시작할 때마다 '전문가 페르소나'와 'SDLC 워크플로우'를\n시스템 프롬프트에 자동 주입하는 기능입니다 (skill과 독립적으로 켜고/끌 수 있음).",
74
+ "skills.action.prompt": "AI 스킬을 어떻게 할까요?",
75
+ "skills.action.apply": "설치 / 업데이트 — 최신 상태로 맞추기",
76
+ "skills.action.remove": "제거 — 설치된 스킬 삭제하기",
77
+ "skills.action.skip": "그대로 두기",
78
+ "skills.target.undetected": " (미감지)",
79
+ "skills.target.promptApply": "설치 / 업데이트할 IDE를 고르세요 (Space 토글, Enter 확정)",
80
+ "skills.target.promptRemove": "제거할 IDE를 고르세요 (Space 토글, Enter 확정)",
81
+ "skills.status.noCli": "skill 미설치 (CLI 없음)",
82
+ "skills.status.installed": "skill 설치됨",
83
+ "skills.status.notInstalled": "skill 미설치",
84
+ "skills.statusTitle": "── IDE Skills 현재 상태 ──",
85
+ "skills.unchanged": "IDE Skills는 변경하지 않고 넘어갑니다.",
86
+ "skills.noneSelected": "선택한 IDE가 없어 건너뜁니다 (원할 때 다시 실행하세요).",
87
+ "skills.header.apply": "[ {label} 설치/업데이트 ]",
88
+ "skills.header.remove": "[ {label} 제거 ]",
89
+ "envPlan.example": " 예) {ex}",
90
+ "envPlan.default": " 기본값: {value}",
91
+ "envPlan.enterValues": " 값을 입력하세요. 그대로 두려면 아무것도 입력하지 말고 Enter를 누르면 기본값이 적용됩니다.",
92
+ "envPlan.valuePrompt": "↳ 값 입력 (Enter=기본값 «{def}» 유지):",
93
+ "envPlan.title": "▶ 배포 워크플로우 환경설정을 채웁니다",
94
+ "envPlan.intro1": " 설치되는 배포 워크플로우가 사용할 값입니다. 항목마다 '무엇에 쓰이는지·설명·예시'와",
95
+ "envPlan.intro2": " 기본값을 함께 보여드립니다. 그대로 둬도 되고, 원하는 것만 바꿀 수 있습니다.",
96
+ "envPlan.how": "어떻게 채울까요?",
97
+ "envPlan.optAll": "① 위 기본값 그대로 전부 설치 (입력 없이 바로 진행)",
98
+ "envPlan.optEach": "② 하나씩 직접 입력 (모든 항목을 순서대로)",
99
+ "envPlan.optSome": "③ 몇 개만 골라서 바꾸기 (고른 것만 입력, 나머지는 기본값)",
100
+ "envPlan.defaultTag": "(기본: {value})",
101
+ "envPlan.pickPrompt": "바꿀 항목을 고르세요 (Space로 선택 · Enter로 확정)",
102
+ };
@@ -0,0 +1,73 @@
1
+ // Message catalog + language resolution for the CLI.
2
+ // English is the source of truth: every key must exist in en.js, ko.js may lag behind
3
+ // and falls back to English (never to the raw key unless the key is unknown everywhere).
4
+ import en from "./en.js";
5
+ import ko from "./ko.js";
6
+
7
+ export const SUPPORTED_LANGS = ["en", "ko"];
8
+ const CATALOGS = { en, ko };
9
+
10
+ let current = "en";
11
+ let source = "default";
12
+
13
+ // "ko", "ko-KR", "ko_KR.UTF-8" -> "ko". Anything else -> "en". Empty -> null (= not specified).
14
+ export function normalizeLang(value) {
15
+ const v = String(value ?? "").trim().toLowerCase();
16
+ if (!v) return null;
17
+ return v.startsWith("ko") ? "ko" : "en";
18
+ }
19
+
20
+ // Language of the OS, as Node sees it. Intl works the same on macOS and Windows,
21
+ // whereas LANG/LC_ALL are usually empty on Windows, so they are only a fallback.
22
+ export function systemLocale(env = process.env) {
23
+ try {
24
+ const l = Intl.DateTimeFormat().resolvedOptions().locale;
25
+ if (l) return l;
26
+ } catch { /* Intl unavailable: fall through */ }
27
+ return env.LC_ALL || env.LC_MESSAGES || env.LANG || "";
28
+ }
29
+
30
+ // Resolution order: --lang > PROJECTOPS_LANG > (CI / non-interactive => en) > system locale > en.
31
+ // CI and --force are pinned to English so logs do not change with the runner's locale.
32
+ // Returns { lang, source } where source is "flag" | "env" | "ci" | "system" | "default".
33
+ export function resolveLang({ flag = null, env = process.env, pinEnglish = false, locale } = {}) {
34
+ const fromFlag = normalizeLang(flag);
35
+ if (fromFlag) return { lang: fromFlag, source: "flag" };
36
+ const fromEnv = normalizeLang(env.PROJECTOPS_LANG);
37
+ if (fromEnv) return { lang: fromEnv, source: "env" };
38
+ if (pinEnglish || env.CI === "true" || env.CI === "1") return { lang: "en", source: "ci" };
39
+ const fromSystem = normalizeLang(locale ?? systemLocale(env));
40
+ if (fromSystem) return { lang: fromSystem, source: "system" };
41
+ return { lang: "en", source: "default" };
42
+ }
43
+
44
+ export function setLang(lang, src = "flag") {
45
+ current = SUPPORTED_LANGS.includes(lang) ? lang : "en";
46
+ source = src;
47
+ }
48
+ export const getLang = () => current;
49
+ export const getLangSource = () => source;
50
+
51
+ // t("key", { name: "x" }) -> message in the current language. "{name}" placeholders are replaced.
52
+ export function t(key, vars) {
53
+ const raw = CATALOGS[current]?.[key] ?? CATALOGS.en[key] ?? key;
54
+ if (!vars) return raw;
55
+ return raw.replace(/\{(\w+)\}/g, (m, k) => (k in vars ? String(vars[k]) : m));
56
+ }
57
+
58
+ // Message in English no matter which language is active. Used for run logs,
59
+ // which agents read later and which must not change with the user's locale.
60
+ export function tEn(key, vars) {
61
+ const raw = CATALOGS.en[key] ?? key;
62
+ if (!vars) return raw;
63
+ return raw.replace(/\{(\w+)\}/g, (m, k) => (k in vars ? String(vars[k]) : m));
64
+ }
65
+
66
+ // Run fn with another language and restore the previous one afterwards.
67
+ // Used to render the run log in English while the screen is in the user's language.
68
+ export function withLang(lang, fn) {
69
+ const prev = current;
70
+ const prevSource = source;
71
+ current = SUPPORTED_LANGS.includes(lang) ? lang : "en";
72
+ try { return fn(); } finally { current = prev; source = prevSource; }
73
+ }
package/src/i18n/ko.js ADDED
@@ -0,0 +1,134 @@
1
+ // 한국어 메시지. 키는 en.js와 동일하게 유지하고, 빠진 키는 영문으로 대체된다.
2
+ import summary from "./summary.ko.js";
3
+ import doctor from "./doctor.ko.js";
4
+ import options from "./options.ko.js";
5
+ import migrate from "./migrate.ko.js";
6
+ import core from "./core.ko.js";
7
+ import ide from "./ide.ko.js";
8
+
9
+ export default {
10
+ ...summary,
11
+ ...doctor,
12
+ ...options,
13
+ ...migrate,
14
+ ...core,
15
+ ...ide,
16
+ "engine.hint.select": "↑/↓ 이동, Enter 확정, ESC 취소",
17
+ "engine.hint.multiselect": "↑/↓ 이동, Space 토글, a 전체, Enter 확정, ESC 취소",
18
+ "engine.hint.confirm": "←/→ 또는 y/n, Enter 확정, ESC 취소",
19
+ "engine.yes": "예",
20
+ "engine.no": "아니오",
21
+ "engine.none": "(없음)",
22
+ "engine.disabled": "선택할 수 없는 항목입니다.",
23
+ "engine.required": "최소 1개 이상 선택하세요.",
24
+ "engine.cancelled": "취소했습니다.",
25
+
26
+ "mode.prompt": "무엇을 설치할까요?",
27
+ "mode.update": "업데이트 ({range})",
28
+ "mode.full": "전체 설치 (버전관리 + 워크플로우 + 템플릿)",
29
+ "mode.version": "버전 관리 전용 (자동화 시스템)",
30
+ "mode.workflows": "워크플로우 전용 (GitHub Actions 빌드, 배포)",
31
+ "mode.issues": "이슈/PR 템플릿 전용",
32
+ "mode.skills": "AI 스킬 전용 (Claude, Cursor, Gemini, Codex, PI)",
33
+
34
+ "confirm.prompt": "이 정보로 진행할까요?",
35
+ "confirm.continue": "예, 계속 진행",
36
+ "confirm.edit": "수정하기",
37
+ "confirm.cancel": "아니오, 취소",
38
+ "edit.prompt": "어떤 항목을 수정할까요?",
39
+ "edit.type": "프로젝트 타입",
40
+ "edit.version": "버전",
41
+ "edit.branch": "기본 브랜치",
42
+ "language.propose": "이 레포의 이슈/PR 템플릿을 영문으로 바꿀까요? 지금은 한국어입니다. 아니오를 고르면 그대로 둡니다.",
43
+ "labelStyle.propose": "한글 상태 라벨(작업전, 작업중 ...)을 영문(status: todo ...)으로 바꿀까요? 이슈에 붙은 라벨은 그대로 옮겨지고 Projects 동기화는 둘 다 인식합니다.",
44
+ "edit.intent": "프로젝트 성격 (배포 유형)",
45
+ "edit.deploy": "배포 방식 (서버 실행물)",
46
+ "edit.publish": "라이브러리 배포(publish) 타겟",
47
+ "edit.codeReview": "CodeRabbit 코드 리뷰",
48
+ "edit.changelog": "릴리스 노트(changelog) 생성기",
49
+ "edit.releaseBranch": "릴리스 소스(개발) 브랜치",
50
+ "edit.secret": "Secret 백업 포함 여부",
51
+ "edit.done": "모두 맞음, 계속",
52
+ "types.prompt": "프로젝트 타입을 선택하세요 (Space 토글, Enter 확정)",
53
+
54
+ "cli.nonTty": "대화형 입력이 불가능한 환경입니다. --mode <full|version|workflows|issues> 와 --force 를 지정하세요.",
55
+ "cli.needForce": "비대화형 환경에서는 --force 옵션이 필요합니다.",
56
+ "cli.langInvalid": "--lang 값은 {values} 중 하나여야 합니다 (입력: '{value}')",
57
+ "cli.langSource.flag": "--lang",
58
+ "cli.langSource.env": "PROJECTOPS_LANG",
59
+ "cli.langSource.ci": "CI/--force 에서는 고정",
60
+ "cli.langSource.system": "시스템 설정",
61
+ "cli.langSource.default": "기본값",
62
+
63
+ "banner.interactive": "대화형 통합 마법사",
64
+ "banner.compactMode": "({mode} 모드, --force)",
65
+
66
+ "detect.title": "프로젝트를 살펴보는 중...",
67
+ "detect.found": "{marker} 발견, {type} 감지",
68
+ "detect.foundNoMarker": "{type} 감지",
69
+ "detect.none": "마커 파일 없음, basic 으로 진행 (직접 선택 가능)",
70
+ "detect.versionBranch": "버전: v{version}, 브랜치: {branch}",
71
+ "card.title": "요약",
72
+ "card.type": "타입",
73
+ "card.typeMulti": "타입(멀티)",
74
+ "card.version": "버전",
75
+ "card.branch": "브랜치",
76
+ "card.mode": "통합 모드",
77
+ "card.deploy": "배포",
78
+ "card.publish": "Publish",
79
+ "card.secret": "Secret 백업",
80
+ "card.paths": "경로",
81
+ "card.none": "없음",
82
+ "card.included": "포함",
83
+ "card.excluded": "제외",
84
+
85
+ "ide.title": "AI 에이전트 스킬 상태",
86
+ "ide.installed": "설치됨",
87
+ "ide.notInstalled": "미설치",
88
+ "ide.cliMissing": "CLI 없음",
89
+ "ide.detectFailed": "감지 실패",
90
+ "kind.update": "업데이트",
91
+ "kind.updateFrom": "템플릿 v{from} → v{to}",
92
+ "kind.updateWhy": "version.yml에 이전 통합 기록이 있습니다. 메뉴 맨 위 '업데이트'가 저장된 설정 그대로 반영합니다",
93
+ "kind.new": "신규 통합",
94
+ "kind.newTemplate": "이 프로젝트에 처음 설치합니다 (템플릿 v{to})",
95
+ "kind.newWhy": "version.yml에 이전 통합 기록이 없어 신규로 봅니다",
96
+
97
+ // interactive flow
98
+ "flow.cancelInstall": "설치를 취소했습니다.",
99
+ "flow.cancelBreaking": "통합을 안전하게 취소했습니다.",
100
+ "flow.doneSkills": "AI 스킬 설치를 마쳤습니다.",
101
+ "flow.doneIssues": "이슈·PR 템플릿을 설치했습니다.",
102
+ "flow.title.update": "업데이트, 저장된 설정으로 진행",
103
+ "flow.title.summary": "요약",
104
+ "flow.askVersion": "새 버전 (예: 1.0.0)",
105
+ "flow.askBranch": "기본 브랜치",
106
+ "flow.badVersion": "버전 형식이 올바르지 않습니다 (x.y.z 형태). 기존 값을 유지합니다.",
107
+ "flow.badVersionTitle": "버전",
108
+ "flow.askSkills": "AI 에이전트 스킬(Claude, Cursor, Gemini, Codex, PI)도 설치/업데이트할까요?",
109
+ "flow.done": "통합 완료. {mode} 모드로 설치했습니다.",
110
+ "flow.updateScope": "업데이트 ({scope} 범위)",
111
+ "flow.conflictsTitle": "템플릿이 갱신된 워크플로우 {count}개",
112
+ "flow.conflictsAsk": "위 {count}개를 .bak 백업 후 새 버전으로 교체할까요? (기존 설정값은 유지됩니다)",
113
+ "flow.conflictType": "기존 워크플로우와 내용이 다른 파일이 있습니다 ({type}). 어떻게 할까요?",
114
+ "flow.opt.skip": "건너뛰기: 기존 파일 유지 (기본)",
115
+ "flow.opt.backup": "`.bak` 백업 후 새 버전으로 교체",
116
+ "flow.opt.template": "기존 유지 + 새 버전을 .template.yaml로 참고 추가",
117
+ "flow.orphanLine": "{file} ({type} 타입, 현재 미선택)",
118
+ "flow.orphanTitle": "선택되지 않은 타입의 워크플로우 {count}개 발견",
119
+ "flow.orphanAsk": "위 {count}개를 정리할까요? (.bak 무해화, 복원 가능)",
120
+ "flow.orphanDone": "고아 워크플로우 정리: {ok}개{failed}",
121
+ "flow.orphanFailed": " (실패 {count}개)",
122
+ "flow.orphanDoneTitle": "정리 완료",
123
+ "sum.mode": "통합 모드",
124
+ "sum.types": "프로젝트 타입",
125
+ "sum.multi": " (멀티)",
126
+ "sum.deploy": "배포 방식",
127
+ "sum.publish": "Publish",
128
+ "sum.secret": "Secret 백업",
129
+ "sum.changelog": "Changelog",
130
+ "sum.coderabbit": "CodeRabbit 리뷰",
131
+ "sum.on": "사용",
132
+ "sum.off": "미사용",
133
+ "mode.update.short": "업데이트",
134
+ };
@@ -0,0 +1,66 @@
1
+ // migrate 영역 메시지 (en). 키는 <영역>.<의미> 형식.
2
+ export default {
3
+ "migrate.reason.gen1VersionControl": "1st-gen rename: running both bumps the version twice",
4
+ "migrate.reason.releasePublish1": "removed from the template; its trunk-based branch calls a command that no longer exists (changelog_manager.py ai-summary) and fails at release time",
5
+ "migrate.reason.gen1Readme": "1st-gen rename: running both commits README twice",
6
+ "migrate.reason.gen1Labels": "1st-gen rename: running both duplicates label sync",
7
+ "migrate.reason.gen0Labels": "0th-gen rename: running both duplicates label sync",
8
+ "migrate.reason.helperGen1": "Issue helper gen 1: running both duplicates issue comments",
9
+ "migrate.reason.helperGen2": "Issue helper gen 2: running both duplicates issue comments",
10
+ "migrate.reason.helperApi": "Issue helper API version retired (inactive dispatch-only leftover)",
11
+ "migrate.reason.helperModule": "External action inlined (#478): running both duplicates issue comments",
12
+ "migrate.reason.releaseGen1": "Release workflow gen 1: running both handles the release PR twice",
13
+ "migrate.reason.releaseRename": "Renamed to RELEASE-CHANGELOG: running both handles the release PR twice",
14
+ "migrate.reason.qaBot": "Old QA bot name: running both creates duplicate QA issues",
15
+ "migrate.reason.utilSync": "Old name: running both duplicates util version sync",
16
+ "migrate.reason.projectsSync": "Replaced by Projects sync: running both updates board status twice",
17
+ "migrate.reason.buildTrigger": "Rebranding rename: running both duplicates the build trigger",
18
+ "migrate.reason.nextType": "next type removed (merged into react)",
19
+ "migrate.reason.ymlExt": "Old .yml extension: running both with the current .yaml duplicates CI",
20
+ "migrate.reason.mergedCi": "Merged into the unified CI: running both duplicates PR CI",
21
+ "migrate.reason.synSsh": "SYNOLOGY retired: replaced by the SSH+Docker engine",
22
+ "migrate.reason.synNonstop": "SYNOLOGY retired: zero-downtime deploy is replaced by the NGINX/TRAEFIK versions",
23
+ "migrate.reason.syn": "SYNOLOGY retired",
24
+ "migrate.reason.synSmb": "SYNOLOGY retired: replaced by SELFHOSTED (SMB)",
25
+ "migrate.reason.synSecret": "SYNOLOGY retired: replaced by the common Secret upload",
26
+ "migrate.reason.springCi1": "1st-gen Spring CI: no current replacement (merged into CICD)",
27
+ "migrate.reason.springDeploy1": "1st-gen Spring deploy: may be a live deployment",
28
+ "migrate.reason.pythonDeploy1": "Old Python deploy: may be a live deployment",
29
+ "migrate.reason.androidDeploy1": "1st-gen Android deploy: may be a live deployment",
30
+ "migrate.reason.iosDeploy1": "1st-gen iOS deploy: may be a live deployment",
31
+ "migrate.reason.iosTestflight": "Replaced by the TESTFLIGHT workflow: may be a live deployment",
32
+ "migrate.reason.secretUpload": "Replaced by the common Secret upload: may be a live upload path",
33
+ "migrate.reason.nexusPublish": "Old Nexus publish: may be a live library publish",
34
+ "migrate.reason.nexusCi": "Old Nexus CI",
35
+ "migrate.reason.libCi0": "0th-gen library CI",
36
+ "migrate.reason.libPublish0": "0th-gen library publish: may be a live deployment",
37
+ "migrate.reason.deployTrigger": "Retired deploy trigger",
38
+ "migrate.reason.guideRebrand": "Old guide replaced by rebranding: leftover document",
39
+ "migrate.reason.guideOld": "Old guide document: leftover",
40
+ "migrate.reason.docsDir": "Rebranding: old name of the skill output folder (issues, reports). New skills save to docs/projectops/",
41
+ "migrate.reason.wizSetup": "Wizard scripts unified in Python: replaced by the setup subcommand",
42
+ "migrate.reason.wizApply": "Wizard scripts unified in Python: replaced by the apply subcommand",
43
+ "migrate.reason.wizDetect": "Wizard scripts unified in Python: replaced by the detect-app-id subcommand",
44
+ "migrate.reason.wizPatch": "Wizard scripts unified in Python: absorbed as an internal function",
45
+ "migrate.reason.wizTest": "Wizard tests unified in Python",
46
+ "migrate.safe.found": "Found {n} legacy template file(s), replaced by newer ones and risky to run side by side:",
47
+ "migrate.safe.reason": "({reason}, retired since {since})",
48
+ "migrate.safe.ask": "Clean up the {n} file(s) above? (workflows are neutralized as .bak and can be restored)",
49
+ "migrate.safe.done": "Legacy cleanup done: {n}{failed}",
50
+ "migrate.safe.failed": " ({n} failed)",
51
+ "migrate.safe.skip": "Skipping legacy cleanup (you will be asked again on the next update)",
52
+ "migrate.dir.found": "Found {n} output folder(s) with an old name. They contain your documents, so they are moved only after confirmation:",
53
+ "migrate.dir.ask": "Move the folders above to the new name? (no files are lost, the original is kept on conflict)",
54
+ "migrate.dir.moved": " {from}/ → {to}/ (moved {moved}{kept})",
55
+ "migrate.dir.kept": ", {n} kept on conflict",
56
+ "migrate.dir.skip": "Skipping the folder move (you will be asked again on the next update)",
57
+ "migrate.dir.nonInteractive": " Not moved automatically in a non-interactive run. Confirm and move them in the interactive wizard (npx projectops).",
58
+ "migrate.confirm.found": "Found {n} old-generation deploy workflow(s). They may be live deployments, so they are left untouched:",
59
+ "migrate.confirm.newer": " (new: {name})",
60
+ "migrate.confirm.hint": " Delete the old files yourself once you have switched to the new workflows.",
61
+ "breaking.title": "BREAKING CHANGES (v{current} -> v{target}) - CRITICAL {critical}, WARNING {warnings}",
62
+ "breaking.askDetail": "Show the details (action steps) for each item?",
63
+ "breaking.seeJson": " Details and action steps: see .github/config/breaking-changes.json",
64
+ "breaking.askContinue": "Have you reviewed the breaking changes above and want to continue?",
65
+ "breaking.nonInteractive": "There are CRITICAL breaking changes. Continuing because this is a non-interactive run. Be sure to review the list above.",
66
+ };
@@ -0,0 +1,66 @@
1
+ // migrate 영역 메시지 (ko). 키는 <영역>.<의미> 형식.
2
+ export default {
3
+ "migrate.reason.gen1VersionControl": "1세대 리네임 — 공존 시 버전 이중 증가",
4
+ "migrate.reason.releasePublish1": "템플릿에서 사라진 릴리스 워크플로우: trunk-based 분기가 지금은 없는 명령(changelog_manager.py ai-summary)을 불러 릴리스 때 실패합니다",
5
+ "migrate.reason.gen1Readme": "1세대 리네임 — 공존 시 README 이중 커밋",
6
+ "migrate.reason.gen1Labels": "1세대 리네임 — 공존 시 라벨 동기화 중복",
7
+ "migrate.reason.gen0Labels": "0세대 리네임 — 공존 시 라벨 동기화 중복",
8
+ "migrate.reason.helperGen1": "이슈 헬퍼 1세대 — 공존 시 이슈 댓글 중복",
9
+ "migrate.reason.helperGen2": "이슈 헬퍼 2세대 — 공존 시 이슈 댓글 중복",
10
+ "migrate.reason.helperApi": "이슈 헬퍼 API 버전 폐기 (dispatch 전용 비활성 잔재)",
11
+ "migrate.reason.helperModule": "외부 액션 내재화(#478) — 공존 시 이슈 댓글 중복",
12
+ "migrate.reason.releaseGen1": "릴리스 워크플로우 1세대 — 공존 시 릴리스 PR 이중 처리",
13
+ "migrate.reason.releaseRename": "RELEASE-CHANGELOG로 리네임 — 공존 시 릴리스 PR 이중 처리",
14
+ "migrate.reason.qaBot": "QA 봇 구명칭 — 공존 시 QA 이슈 중복 생성",
15
+ "migrate.reason.utilSync": "구명칭 — 공존 시 util 버전 동기화 중복",
16
+ "migrate.reason.projectsSync": "Projects 동기화로 대체 — 공존 시 보드 상태 중복 갱신",
17
+ "migrate.reason.buildTrigger": "리브랜딩 리네임 — 공존 시 빌드 트리거 중복",
18
+ "migrate.reason.nextType": "next 타입 폐지 (react 흡수)",
19
+ "migrate.reason.ymlExt": "구 확장자(.yml) — 현행 .yaml과 공존 시 CI 중복 실행",
20
+ "migrate.reason.mergedCi": "통합 CI로 흡수 — 공존 시 PR CI 중복 실행",
21
+ "migrate.reason.synSsh": "SYNOLOGY 폐기 — SSH+Docker 엔진으로 대체됨",
22
+ "migrate.reason.synNonstop": "SYNOLOGY 폐기 — 무중단 배포는 NGINX/TRAEFIK 버전으로 대체됨",
23
+ "migrate.reason.syn": "SYNOLOGY 폐기",
24
+ "migrate.reason.synSmb": "SYNOLOGY 폐기 — SELFHOSTED(SMB)로 대체됨",
25
+ "migrate.reason.synSecret": "SYNOLOGY 폐기 — 공통 Secret 업로드로 대체됨",
26
+ "migrate.reason.springCi1": "1세대 Spring CI — 현행 대체본 없음(CICD에 통합)",
27
+ "migrate.reason.springDeploy1": "1세대 Spring 배포 — 현역 배포일 수 있음",
28
+ "migrate.reason.pythonDeploy1": "구 Python 배포 — 현역 배포일 수 있음",
29
+ "migrate.reason.androidDeploy1": "1세대 Android 배포 — 현역 배포일 수 있음",
30
+ "migrate.reason.iosDeploy1": "1세대 iOS 배포 — 현역 배포일 수 있음",
31
+ "migrate.reason.iosTestflight": "TESTFLIGHT 워크플로우로 대체 — 현역 배포일 수 있음",
32
+ "migrate.reason.secretUpload": "공통 Secret 업로드로 대체 — 운영 업로드 경로일 수 있음",
33
+ "migrate.reason.nexusPublish": "구 Nexus 배포 — 현역 라이브러리 배포일 수 있음",
34
+ "migrate.reason.nexusCi": "구 Nexus CI",
35
+ "migrate.reason.libCi0": "0세대 라이브러리 CI",
36
+ "migrate.reason.libPublish0": "0세대 라이브러리 배포 — 현역 배포일 수 있음",
37
+ "migrate.reason.deployTrigger": "폐기된 배포 트리거",
38
+ "migrate.reason.guideRebrand": "리브랜딩으로 구 가이드 대체 — 잔재 문서",
39
+ "migrate.reason.guideOld": "구 가이드 문서 — 잔재",
40
+ "migrate.reason.docsDir": "리브랜딩 — 스킬 산출물(이슈·보고서) 폴더 구명칭. 신 스킬은 docs/projectops/에 저장",
41
+ "migrate.reason.wizSetup": "마법사 스크립트 Python 단일화 — setup 서브커맨드로 대체",
42
+ "migrate.reason.wizApply": "마법사 스크립트 Python 단일화 — apply 서브커맨드로 대체",
43
+ "migrate.reason.wizDetect": "마법사 스크립트 Python 단일화 — detect-app-id 서브커맨드로 대체",
44
+ "migrate.reason.wizPatch": "마법사 스크립트 Python 단일화 — 내부 함수로 흡수",
45
+ "migrate.reason.wizTest": "마법사 테스트 Python 단일화",
46
+ "migrate.safe.found": "🧹 레거시 템플릿 파일 {n}개 감지 — 신형으로 대체되어 공존 시 중복 실행 위험:",
47
+ "migrate.safe.reason": "({reason}, {since}부터 폐기)",
48
+ "migrate.safe.ask": "위 {n}개를 정리할까요? (워크플로우는 .bak 무해화 — 복원 가능)",
49
+ "migrate.safe.done": "✅ 레거시 정리 완료: {n}개{failed}",
50
+ "migrate.safe.failed": " (실패 {n}개)",
51
+ "migrate.safe.skip": "→ 레거시 정리를 건너뜁니다 (다음 업데이트 때 다시 안내)",
52
+ "migrate.dir.found": "📁 구명칭 산출물 폴더 {n}개 감지 — 사용자 문서가 들어 있어 확인 후 이동합니다:",
53
+ "migrate.dir.ask": "위 폴더를 새 이름으로 이동할까요? (파일 손실 없음 — 충돌 시 원본 유지)",
54
+ "migrate.dir.moved": " ✅ {from}/ → {to}/ (이동 {moved}개{kept})",
55
+ "migrate.dir.kept": ", 충돌 유지 {n}개",
56
+ "migrate.dir.skip": "→ 폴더 이동을 건너뜁니다 (다음 업데이트 때 다시 안내)",
57
+ "migrate.dir.nonInteractive": " 비대화형 실행이라 자동으로 이동하지 않습니다 — 대화형 마법사(npx projectops)에서 확인 후 이동하세요.",
58
+ "migrate.confirm.found": "⚠️ 구세대 배포 워크플로우 {n}개 발견 — 현역 배포일 수 있어 자동으로 건드리지 않습니다:",
59
+ "migrate.confirm.newer": " (신형: {name})",
60
+ "migrate.confirm.hint": " 신형 워크플로우로 전환을 마친 뒤 구 파일을 직접 삭제하세요.",
61
+ "breaking.title": "⚠️ BREAKING CHANGES (v{current} -> v{target}) - CRITICAL {critical}건, WARNING {warnings}건",
62
+ "breaking.askDetail": "각 항목의 상세 내용(조치 방법)을 볼까요?",
63
+ "breaking.seeJson": " 상세 내용, 조치 방법: .github/config/breaking-changes.json 참고",
64
+ "breaking.askContinue": "위 호환성 변경을 확인했고 계속 진행할까요?",
65
+ "breaking.nonInteractive": "⚠️ CRITICAL 호환성 변경이 있습니다 — 비대화형 실행이라 계속 진행합니다. 위 내용을 꼭 확인하세요.",
66
+ };