@seanyao/roll 2026.524.2 → 2026.526.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/CHANGELOG.md +38 -8
- package/README.md +4 -2
- package/bin/roll +1022 -442
- package/conventions/config.yaml +9 -0
- package/lib/__pycache__/roll-home.cpython-314.pyc +0 -0
- package/lib/__pycache__/roll-loop-status.cpython-314.pyc +0 -0
- package/lib/__pycache__/roll_render.cpython-314.pyc +0 -0
- package/lib/i18n/agent.sh +21 -0
- package/lib/i18n/alert.sh +20 -0
- package/lib/i18n/backlog.sh +96 -0
- package/lib/i18n/brief.sh +5 -0
- package/lib/i18n/changelog.sh +3 -0
- package/lib/i18n/ci.sh +15 -0
- package/lib/i18n/init.sh +52 -0
- package/lib/i18n/lang.sh +10 -0
- package/lib/i18n/loop.sh +140 -0
- package/lib/i18n/migrate.sh +74 -0
- package/lib/i18n/offboard.sh +16 -0
- package/lib/i18n/peer.sh +34 -0
- package/lib/i18n/peer_help.sh +21 -0
- package/lib/i18n/peer_reset.sh +7 -0
- package/lib/i18n/peer_status.sh +5 -0
- package/lib/i18n/prices.sh +3 -0
- package/lib/i18n/prices_refresh.sh +17 -0
- package/lib/i18n/prices_show.sh +7 -0
- package/lib/i18n/setup.sh +3 -0
- package/lib/i18n/shared.sh +74 -0
- package/lib/i18n/skills/roll-brief.sh +27 -0
- package/lib/i18n/skills/roll-fix.sh +39 -0
- package/lib/i18n/skills/roll-onboard.sh +17 -0
- package/lib/i18n/slides.sh +3 -0
- package/lib/i18n/slides_build.sh +38 -0
- package/lib/i18n/slides_delete.sh +19 -0
- package/lib/i18n/slides_list.sh +14 -0
- package/lib/i18n/slides_logs.sh +12 -0
- package/lib/i18n/slides_new.sh +15 -0
- package/lib/i18n/slides_preview.sh +14 -0
- package/lib/i18n/slides_templates.sh +7 -0
- package/lib/i18n/status.sh +19 -0
- package/lib/i18n/update.sh +7 -0
- package/lib/i18n.sh +85 -4
- package/lib/roll-home.py +55 -0
- package/lib/roll-loop-status.py +196 -19
- package/lib/roll-loop-story.py +191 -0
- package/lib/roll_render.py +15 -1
- package/lib/slides/components/README.md +117 -0
- package/lib/slides/components/cards-2.html +9 -0
- package/lib/slides/components/cards-3.html +9 -0
- package/lib/slides/components/cards-4.html +9 -0
- package/lib/slides/components/compare.html +22 -0
- package/lib/slides/components/highlight.html +9 -0
- package/lib/slides/components/pipeline.html +12 -0
- package/lib/slides/components/plain.html +7 -0
- package/lib/slides/components/quote.html +4 -0
- package/lib/slides/components/timeline.html +9 -0
- package/lib/slides/templates/pitch.html +0 -0
- package/package.json +1 -1
- package/skills/roll-brief/SKILL.md +2 -2
- package/skills/roll-build/SKILL.md +40 -40
- package/skills/roll-fix/SKILL.md +22 -22
- package/skills/roll-loop/SKILL.md +26 -15
- package/skills/roll-onboard/SKILL.md +6 -6
package/lib/i18n/shared.sh
CHANGED
|
@@ -81,3 +81,77 @@ _i18n_set zh main.unknown_command "未知命令: %s"
|
|
|
81
81
|
# ── dashboard status ──
|
|
82
82
|
_i18n_set en dashboard.pending "pending"
|
|
83
83
|
_i18n_set zh dashboard.pending "待办"
|
|
84
|
+
|
|
85
|
+
_i18n_set en shared.added_missing_config_entry "Added missing config entry: %s"
|
|
86
|
+
_i18n_set zh shared.added_missing_config_entry "已添加缺失配置项: %s"
|
|
87
|
+
_i18n_set en shared.config_updated_with_new_entries "Config updated with %s new entries"
|
|
88
|
+
_i18n_set zh shared.config_updated_with_new_entries "配置已更新,新增 %s 条目"
|
|
89
|
+
_i18n_set en shared.wrote "Wrote: %s"
|
|
90
|
+
_i18n_set zh shared.wrote "已写入: %s"
|
|
91
|
+
_i18n_set en shared.file_exists_and_differs "File exists and differs: %s"
|
|
92
|
+
_i18n_set zh shared.file_exists_and_differs "文件已存在且内容不同: %s"
|
|
93
|
+
_i18n_set en shared.skipped "Skipped: %s"
|
|
94
|
+
_i18n_set zh shared.skipped "已跳过: %s"
|
|
95
|
+
_i18n_set en shared.wrote_2 "Wrote: %s"
|
|
96
|
+
_i18n_set zh shared.wrote_2 "已写入: %s"
|
|
97
|
+
_i18n_set en shared.removed_stale "Removed stale %s: %s/%s"
|
|
98
|
+
_i18n_set zh shared.removed_stale "已删除过时%s: %s/%s"
|
|
99
|
+
_i18n_set en shared.skills_source_not_found_at_skills "Skills source not found at: %s/skills"
|
|
100
|
+
_i18n_set zh shared.skills_source_not_found_at_skills "技能源目录未找到: %s/skills"
|
|
101
|
+
_i18n_set en shared.removed_stale_skill "Removed stale skill: %s"
|
|
102
|
+
_i18n_set zh shared.removed_stale_skill "已删除过时技能: %s"
|
|
103
|
+
_i18n_set en shared.convention_source_not_found_at "Convention source not found at: %s"
|
|
104
|
+
_i18n_set zh shared.convention_source_not_found_at "约定源文件未找到: %s"
|
|
105
|
+
_i18n_set en shared.copying_global_conventions "Copying global conventions..."
|
|
106
|
+
_i18n_set zh shared.copying_global_conventions "正在复制全局约定..."
|
|
107
|
+
_i18n_set en shared.copying_project_templates "Copying project templates..."
|
|
108
|
+
_i18n_set zh shared.copying_project_templates "正在复制项目模板..."
|
|
109
|
+
_i18n_set en shared.convention_source_not_found_at_2 "Convention source not found at: %s"
|
|
110
|
+
_i18n_set zh shared.convention_source_not_found_at_2 "约定源文件未找到: %s"
|
|
111
|
+
_i18n_set en shared.run_this_from_the_roll_repo "Run this from the roll repo, or symlink bin/roll to PATH."
|
|
112
|
+
_i18n_set zh shared.run_this_from_the_roll_repo "请在 roll 仓库目录下运行,或将 bin/roll 软链接到 PATH。"
|
|
113
|
+
_i18n_set en shared.config_has_no_ai_entries_recreating "Config has no ai_* entries — recreating with defaults (backup saved)"
|
|
114
|
+
_i18n_set zh shared.config_has_no_ai_entries_recreating "配置无 ai_* 条目,将重建(已备份)"
|
|
115
|
+
_i18n_set en shared.backup_saved_roll_config_yaml_bak "Backup saved: ~/.roll/config.yaml.bak"
|
|
116
|
+
_i18n_set zh shared.backup_saved_roll_config_yaml_bak "备份已保存: ~/.roll/config.yaml.bak"
|
|
117
|
+
_i18n_set en shared.creating_default_config "Creating default config..."
|
|
118
|
+
_i18n_set zh shared.creating_default_config "正在创建默认配置..."
|
|
119
|
+
_i18n_set en shared.created_roll_config_yaml "Created: ~/.roll/config.yaml"
|
|
120
|
+
_i18n_set zh shared.created_roll_config_yaml "已创建: ~/.roll/config.yaml"
|
|
121
|
+
_i18n_set en shared.skipped_resolves_to_repo_refusing_to "Skipped ~/%s (resolves to repo — refusing to manage skills inside roll worktree)"
|
|
122
|
+
_i18n_set zh shared.skipped_resolves_to_repo_refusing_to "已跳过 ~/%s(解析到仓库目录 — 拒绝在 roll worktree 内管理技能)"
|
|
123
|
+
_i18n_set en shared.removing_legacy_symlink_skills "Removing legacy symlink ~/%s/skills -> %s"
|
|
124
|
+
_i18n_set zh shared.removing_legacy_symlink_skills "正在移除遗留软链接 ~/%s/skills -> %s"
|
|
125
|
+
_i18n_set en shared.skipped_skills_unknown_symlink_target "Skipped ~/%s/skills -> %s (unknown symlink target)"
|
|
126
|
+
_i18n_set zh shared.skipped_skills_unknown_symlink_target "已跳过 ~/%s/skills -> %s(未知软链接目标)"
|
|
127
|
+
_i18n_set en shared.skipped_skills_created_path_resolves_to "Skipped ~/%s/skills (created path resolves to repo — refusing to write)"
|
|
128
|
+
_i18n_set zh shared.skipped_skills_created_path_resolves_to "已跳过 ~/%s/skills(创建路径解析到仓库 — 拒绝写入)"
|
|
129
|
+
_i18n_set en shared.skills_linked_in_skills_new_repaired "Skills linked in ~/%s/skills (+%s new, ~%s repaired, -%s pruned)"
|
|
130
|
+
_i18n_set zh shared.skills_linked_in_skills_new_repaired "已在 ~/%s/skills 中创建软链接(新增 +%s,修复 ~%s,清理 -%s)"
|
|
131
|
+
_i18n_set en shared.wrote_3 "Wrote: %s"
|
|
132
|
+
_i18n_set zh shared.wrote_3 "已写入: %s"
|
|
133
|
+
_i18n_set en shared.created "Created: %s"
|
|
134
|
+
_i18n_set zh shared.created "已创建: %s"
|
|
135
|
+
_i18n_set en shared.appended_roll_md_to "Appended @roll.md to: %s"
|
|
136
|
+
_i18n_set zh shared.appended_roll_md_to "已将 @roll.md 追加至: %s"
|
|
137
|
+
_i18n_set en shared.already_included "Already included: %s"
|
|
138
|
+
_i18n_set zh shared.already_included "已包含: %s"
|
|
139
|
+
_i18n_set en shared.updating_skills "Updating skills..."
|
|
140
|
+
_i18n_set zh shared.updating_skills "正在更新技能..."
|
|
141
|
+
_i18n_set en shared.skills_updated_in_roll_skills "Skills updated in ~/.roll/skills"
|
|
142
|
+
_i18n_set zh shared.skills_updated_in_roll_skills "技能已更新至 ~/.roll/skills"
|
|
143
|
+
_i18n_set en shared.creating_skill_symlinks_for_ai_tools "Creating skill symlinks for AI tools..."
|
|
144
|
+
_i18n_set zh shared.creating_skill_symlinks_for_ai_tools "正在为 AI 工具创建技能软链接..."
|
|
145
|
+
_i18n_set en shared.tmux_not_found_installing_via_brew "tmux not found — installing via brew..."
|
|
146
|
+
_i18n_set zh shared.tmux_not_found_installing_via_brew "未安装 tmux,正在通过 brew 安装..."
|
|
147
|
+
_i18n_set en shared.tmux_installed_tmux "tmux installed. tmux"
|
|
148
|
+
_i18n_set zh shared.tmux_installed_tmux "已安装。"
|
|
149
|
+
_i18n_set en shared.brew_install_tmux_failed_install_manually "brew install tmux failed — install manually: brew install tmux brew"
|
|
150
|
+
_i18n_set zh shared.brew_install_tmux_failed_install_manually "安装失败,请手动 'brew install tmux'"
|
|
151
|
+
_i18n_set en shared.tmux_required_but_brew_not_available "tmux required but brew not available — install manually: brew install tmux"
|
|
152
|
+
_i18n_set zh shared.tmux_required_but_brew_not_available "缺少 brew,请手动 'brew install tmux'"
|
|
153
|
+
_i18n_set en shared.tmux_required_install_via_your_package "tmux required — install via your package manager (e.g. apt install tmux / pacman -S tmux)"
|
|
154
|
+
_i18n_set zh shared.tmux_required_install_via_your_package "请用系统包管理器安装 tmux"
|
|
155
|
+
|
|
156
|
+
_i18n_set en shared.skipped_resolves_to_repo "Skipped ~/%s/skills (resolves to repo — check if ~/%s symlinks to roll repo)"
|
|
157
|
+
_i18n_set zh shared.skipped_resolves_to_repo "已跳过 ~/%s/skills(解析到仓库 — 检查 ~/%s 是否软链接到 roll 仓库)"
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# Roll i18n catalog — roll-brief skill user-facing strings (US-I18N-003).
|
|
3
|
+
# These are the templates shown to users by agents executing roll-brief.
|
|
4
|
+
|
|
5
|
+
# ── Brief content headers ──
|
|
3
6
|
_i18n_set en brief.title "Roll Brief — %s"
|
|
4
7
|
_i18n_set zh brief.title "Roll 简报 — %s"
|
|
5
8
|
_i18n_set en brief.summary "Summary"
|
|
@@ -18,3 +21,27 @@ _i18n_set en brief.ready "Ready to release"
|
|
|
18
21
|
_i18n_set zh brief.ready "可以发版"
|
|
19
22
|
_i18n_set en brief.not_ready "Not ready — %s blockers"
|
|
20
23
|
_i18n_set zh brief.not_ready "暂不可发版 — %s 个阻塞项"
|
|
24
|
+
|
|
25
|
+
# ── Section headers in generated brief ──
|
|
26
|
+
_i18n_set en brief.section_completed "Completed (%s items)"
|
|
27
|
+
_i18n_set zh brief.section_completed "已完成(%s 项)"
|
|
28
|
+
_i18n_set en brief.section_in_progress "In Progress"
|
|
29
|
+
_i18n_set zh brief.section_in_progress "进行中"
|
|
30
|
+
_i18n_set en brief.section_queue "Pending Queue (%s items)"
|
|
31
|
+
_i18n_set zh brief.section_queue "待处理队列(%s 项)"
|
|
32
|
+
_i18n_set en brief.section_insights "Insights from Dream"
|
|
33
|
+
_i18n_set zh brief.section_insights "悟见"
|
|
34
|
+
_i18n_set en brief.section_escalations "Requires Human Attention"
|
|
35
|
+
_i18n_set zh brief.section_escalations "需人工介入"
|
|
36
|
+
_i18n_set en brief.section_doc_coverage "Documentation Coverage"
|
|
37
|
+
_i18n_set zh brief.section_doc_coverage "文档覆盖度"
|
|
38
|
+
_i18n_set en brief.section_release "Release Readiness"
|
|
39
|
+
_i18n_set zh brief.section_release "发版就绪"
|
|
40
|
+
|
|
41
|
+
# ── Step 5: Notification ──
|
|
42
|
+
_i18n_set en brief.generated "Brief generated: %s"
|
|
43
|
+
_i18n_set zh brief.generated "简报已生成:%s"
|
|
44
|
+
_i18n_set en brief.release_ready_status "Ready to release"
|
|
45
|
+
_i18n_set zh brief.release_ready_status "可发版"
|
|
46
|
+
_i18n_set en brief.release_hold_status "Hold — %s"
|
|
47
|
+
_i18n_set zh brief.release_hold_status "暂缓 — %s"
|
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# Roll i18n catalog — roll-fix skill user-facing strings (US-I18N-003).
|
|
3
|
+
# These are templates/output blocks shown to users by agents executing roll-fix.
|
|
4
|
+
|
|
5
|
+
# ── Section 3: Test Design Review ──
|
|
6
|
+
_i18n_set en fix.test_design "Test Design for Fix"
|
|
7
|
+
_i18n_set zh fix.test_design "修复测试设计"
|
|
8
|
+
_i18n_set en fix.verification_approach "Verification Approach"
|
|
9
|
+
_i18n_set zh fix.verification_approach "验证方式"
|
|
10
|
+
_i18n_set en fix.test_scenarios "Test Scenarios"
|
|
11
|
+
_i18n_set zh fix.test_scenarios "测试场景"
|
|
12
|
+
_i18n_set en fix.fix_verification "Fix verification"
|
|
13
|
+
_i18n_set zh fix.fix_verification "修复验证"
|
|
14
|
+
_i18n_set en fix.regression_check "Regression check"
|
|
15
|
+
_i18n_set zh fix.regression_check "回归检查"
|
|
16
|
+
|
|
17
|
+
# ── Section 4: TCR Implementation ──
|
|
18
|
+
_i18n_set en fix.tcr_cycle "TCR CYCLE FOR FIX"
|
|
19
|
+
_i18n_set zh fix.tcr_cycle "修复 TCR 循环"
|
|
20
|
+
_i18n_set en fix.micro_step "MICRO-STEP %s: %s"
|
|
21
|
+
_i18n_set zh fix.micro_step "微步骤 %s: %s"
|
|
22
|
+
|
|
23
|
+
# ── Section 6: Quality Review ──
|
|
24
|
+
_i18n_set en fix.self_review "Self Review Report"
|
|
25
|
+
_i18n_set zh fix.self_review "自审报告"
|
|
26
|
+
_i18n_set en fix.scope "Scope"
|
|
27
|
+
_i18n_set zh fix.scope "范围"
|
|
28
|
+
_i18n_set en fix.critical "Critical"
|
|
29
|
+
_i18n_set zh fix.critical "严重"
|
|
30
|
+
_i18n_set en fix.warnings "Warnings"
|
|
31
|
+
_i18n_set zh fix.warnings "警告"
|
|
32
|
+
_i18n_set en fix.suggestions "Suggestions"
|
|
33
|
+
_i18n_set zh fix.suggestions "建议"
|
|
34
|
+
_i18n_set en fix.passed_dimensions "Passed dimensions"
|
|
35
|
+
_i18n_set zh fix.passed_dimensions "通过维度"
|
|
36
|
+
|
|
37
|
+
# ── Section 10.5: Verification Gate ──
|
|
38
|
+
_i18n_set en fix.issue_resolved "Issue resolved"
|
|
39
|
+
_i18n_set zh fix.issue_resolved "问题已解决"
|
|
40
|
+
|
|
41
|
+
# ── Section 11: Report ──
|
|
3
42
|
_i18n_set en fix.bug_identified "Bug identified: %s"
|
|
4
43
|
_i18n_set zh fix.bug_identified "Bug 已识别: %s"
|
|
5
44
|
_i18n_set en fix.root_cause "Root cause"
|
|
@@ -1,15 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# Roll i18n catalog — roll-onboard skill user-facing strings (US-I18N-003).
|
|
3
|
+
# These are the templates/questions shown to users by agents executing roll-onboard.
|
|
4
|
+
|
|
5
|
+
# ── Step 0-2: Setup ──
|
|
3
6
|
_i18n_set en onboard.welcome "Welcome to Roll Onboard"
|
|
4
7
|
_i18n_set zh onboard.welcome "欢迎使用 Roll Onboard"
|
|
5
8
|
_i18n_set en onboard.scanning "Scanning project..."
|
|
6
9
|
_i18n_set zh onboard.scanning "正在扫描项目..."
|
|
10
|
+
_i18n_set en onboard.stop_migrate_first "This project has legacy Roll structure (BACKLOG.md or docs/features/). Run 'roll migrate' first before onboarding."
|
|
11
|
+
_i18n_set zh onboard.stop_migrate_first "此项目还保留着旧版 Roll 结构(BACKLOG.md 或 docs/features/)。请先运行 'roll migrate' 再 onboard。"
|
|
12
|
+
|
|
13
|
+
# ── Step 3: Nine Questions ──
|
|
7
14
|
_i18n_set en onboard.questions_group1 "Group 1/3: Understanding your project"
|
|
8
15
|
_i18n_set zh onboard.questions_group1 "第 1/3 组: 理解你的项目"
|
|
9
16
|
_i18n_set en onboard.questions_group2 "Group 2/3: Defining scope"
|
|
10
17
|
_i18n_set zh onboard.questions_group2 "第 2/3 组: 定义范围"
|
|
11
18
|
_i18n_set en onboard.questions_group3 "Group 3/3: Privacy & preferences"
|
|
12
19
|
_i18n_set zh onboard.questions_group3 "第 3/3 组: 隐私与偏好"
|
|
20
|
+
_i18n_set en onboard.q1 "I see this is a %s project doing %s — correct?"
|
|
21
|
+
_i18n_set zh onboard.q1 "我看到这是一个 %s 项目,做的是 %s — 对吗?"
|
|
22
|
+
_i18n_set en onboard.q2 "The main business domains look like %s — anything to add or correct?"
|
|
23
|
+
_i18n_set zh onboard.q2 "主要业务领域看起来有 %s — 有需要补充或纠正的吗?"
|
|
24
|
+
_i18n_set en onboard.q3 "The key modules are %s — any missed or mis-identified?"
|
|
25
|
+
_i18n_set zh onboard.q3 "关键模块是 %s — 有遗漏或识别错误的吗?"
|
|
26
|
+
_i18n_set en onboard.q4 "Which artifacts should I generate?"
|
|
27
|
+
_i18n_set zh onboard.q4 "需要生成哪些产出物?"
|
|
28
|
+
|
|
29
|
+
# ── Step 4: Plan complete ──
|
|
13
30
|
_i18n_set en onboard.plan_written "Onboard plan written to .roll/onboard-plan.yaml"
|
|
14
31
|
_i18n_set zh onboard.plan_written "Onboard 计划已写入 .roll/onboard-plan.yaml"
|
|
15
32
|
_i18n_set en onboard.next_step "Next: run 'roll init --apply' to execute this plan"
|
package/lib/i18n/slides.sh
CHANGED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
|
|
2
|
+
_i18n_set en slides_build.unknown_option_1 "Unknown option: %s"
|
|
3
|
+
_i18n_set zh slides_build.unknown_option_1 "未知选项: %s"
|
|
4
|
+
_i18n_set en slides_build.unexpected_argument_1 "Unexpected argument: %s"
|
|
5
|
+
_i18n_set zh slides_build.unexpected_argument_1 "多余参数: %s"
|
|
6
|
+
_i18n_set en slides_build.slides_toolchain_missing_re_run_roll "Slides toolchain missing — re-run 'roll setup'"
|
|
7
|
+
_i18n_set zh slides_build.slides_toolchain_missing_re_run_roll "渲染工具缺失,请运行 roll setup"
|
|
8
|
+
_i18n_set en slides_build.deck_md " deck.md"
|
|
9
|
+
_i18n_set zh slides_build.deck_md "校验失败,请先修复上方提示再重试。"
|
|
10
|
+
_i18n_set en slides_build.template_not_found "Template not found: %s"
|
|
11
|
+
_i18n_set zh slides_build.template_not_found "未找到模板:%s"
|
|
12
|
+
_i18n_set en slides_build.render_failed_for "Render failed for %s"
|
|
13
|
+
_i18n_set zh slides_build.render_failed_for "渲染失败:%s"
|
|
14
|
+
_i18n_set en slides_build.rendered "Rendered → %s"
|
|
15
|
+
_i18n_set zh slides_build.rendered "渲染完成 → %s"
|
|
16
|
+
|
|
17
|
+
# US-DECK-012: build failure recovery paths
|
|
18
|
+
_i18n_set en slides_build.validation_failed_for "Validation failed for %s"
|
|
19
|
+
_i18n_set zh slides_build.validation_failed_for "校验失败:%s"
|
|
20
|
+
_i18n_set en slides_build.hint_fix_and_rerun "Hint: fix the issues above in %s and re-run 'roll slides build %s'"
|
|
21
|
+
_i18n_set zh slides_build.hint_fix_and_rerun "提示:请按上方提示修复 %s,然后重新运行 'roll slides build %s'"
|
|
22
|
+
_i18n_set en slides_build.available_templates "Available templates:"
|
|
23
|
+
_i18n_set zh slides_build.available_templates "可用模板:"
|
|
24
|
+
_i18n_set en slides_build.templates_list_hint "Hint: 'roll slides templates' lists all installed templates"
|
|
25
|
+
_i18n_set zh slides_build.templates_list_hint "提示:'roll slides templates' 列出所有已安装模板"
|
|
26
|
+
_i18n_set en slides_build.renderer_crashed_for "Renderer crashed for %s"
|
|
27
|
+
_i18n_set zh slides_build.renderer_crashed_for "渲染器崩溃:%s"
|
|
28
|
+
_i18n_set en slides_build.see_full_error_logs "See full error: roll slides logs %s"
|
|
29
|
+
_i18n_set zh slides_build.see_full_error_logs "查看完整错误:roll slides logs %s"
|
|
30
|
+
_i18n_set en slides_build.last_5_lines_of_renderer_output "Last 5 lines of renderer output:"
|
|
31
|
+
_i18n_set zh slides_build.last_5_lines_of_renderer_output "渲染器最后 5 行:"
|
|
32
|
+
|
|
33
|
+
_i18n_set en slides_build.usage_roll_slides_build_slug_no "Usage: roll slides build <slug> [--no-open]"
|
|
34
|
+
_i18n_set zh slides_build.usage_roll_slides_build_slug_no "用法: roll slides build <slug> [--no-open]"
|
|
35
|
+
_i18n_set en slides_build.en_deck "[EN: 未找到 deck 文件:%s...]"
|
|
36
|
+
_i18n_set zh slides_build.en_deck " 未找到 deck 文件:%s"
|
|
37
|
+
_i18n_set en slides_build.en_roll_slides_new "[EN: 提示:先运行 'roll slides new \"<主题>\"' 生成新的幻灯片。...]"
|
|
38
|
+
_i18n_set zh slides_build.en_roll_slides_new " 提示:先运行 'roll slides new \"<主题>\"' 生成新的幻灯片。"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
|
|
2
|
+
_i18n_set en slides_delete.unknown_option_1 "Unknown option: %s"
|
|
3
|
+
_i18n_set zh slides_delete.unknown_option_1 "未知选项: %s"
|
|
4
|
+
_i18n_set en slides_delete.unexpected_argument_1 "Unexpected argument: %s"
|
|
5
|
+
_i18n_set zh slides_delete.unexpected_argument_1 "多余参数: %s"
|
|
6
|
+
_i18n_set en slides_delete.deck_not_found "Deck not found: %s"
|
|
7
|
+
_i18n_set zh slides_delete.deck_not_found "未找到幻灯片:%s"
|
|
8
|
+
_i18n_set en slides_delete.non_interactive_terminal_must_use_force "Non-interactive terminal: must use --force to delete"
|
|
9
|
+
_i18n_set zh slides_delete.non_interactive_terminal_must_use_force "非交互终端:需使用 --force 参数"
|
|
10
|
+
_i18n_set en slides_delete.cancelled "Cancelled"
|
|
11
|
+
_i18n_set zh slides_delete.cancelled "已取消"
|
|
12
|
+
_i18n_set en slides_delete.deleted "Deleted %s"
|
|
13
|
+
_i18n_set zh slides_delete.deleted "已删除 %s"
|
|
14
|
+
|
|
15
|
+
_i18n_set en slides_delete.usage_roll_slides_delete_slug_force "Usage: roll slides delete <slug> [--force]"
|
|
16
|
+
_i18n_set zh slides_delete.usage_roll_slides_delete_slug_force "用法: roll slides delete <slug> [--force]"
|
|
17
|
+
|
|
18
|
+
_i18n_set en slides_delete.prompt 'Delete deck "%s"? (y/N)'
|
|
19
|
+
_i18n_set zh slides_delete.prompt '删除幻灯片 "%s"?(y/N)'
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
|
|
2
|
+
_i18n_set en slides_list.unknown_option_1 "Unknown option: %s"
|
|
3
|
+
_i18n_set zh slides_list.unknown_option_1 "未知选项: %s"
|
|
4
|
+
_i18n_set en slides_list.unexpected_argument_1 "Unexpected argument: %s"
|
|
5
|
+
_i18n_set zh slides_list.unexpected_argument_1 "多余参数: %s"
|
|
6
|
+
_i18n_set en slides_list.no_decks_found_under_roll_slides "No decks found under .roll/slides/"
|
|
7
|
+
_i18n_set zh slides_list.no_decks_found_under_roll_slides "无幻灯片"
|
|
8
|
+
_i18n_set en slides_list.no_decks_found_under_roll_slides_2 "No decks found under .roll/slides/"
|
|
9
|
+
_i18n_set zh slides_list.no_decks_found_under_roll_slides_2 "无幻灯片"
|
|
10
|
+
|
|
11
|
+
_i18n_set en slides_list.en_roll_slides_new "[EN: 提示:运行 'roll slides new \"<主题>\"' 创建第一个幻灯片。...]"
|
|
12
|
+
_i18n_set zh slides_list.en_roll_slides_new " 提示:运行 'roll slides new \"<主题>\"' 创建第一个幻灯片。"
|
|
13
|
+
_i18n_set en slides_list.en_roll_slides_new_2 "[EN: 提示:运行 'roll slides new \"<主题>\"' 创建第一个幻灯片。...]"
|
|
14
|
+
_i18n_set zh slides_list.en_roll_slides_new_2 " 提示:运行 'roll slides new \"<主题>\"' 创建第一个幻灯片。"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
|
|
2
|
+
_i18n_set en slides_logs.unknown_option_1 "Unknown option: %s"
|
|
3
|
+
_i18n_set zh slides_logs.unknown_option_1 "未知选项: %s"
|
|
4
|
+
_i18n_set en slides_logs.unexpected_argument_1 "Unexpected argument: %s"
|
|
5
|
+
_i18n_set zh slides_logs.unexpected_argument_1 "多余参数: %s"
|
|
6
|
+
_i18n_set en slides_logs.deck_not_found "Deck not found: %s"
|
|
7
|
+
_i18n_set zh slides_logs.deck_not_found "未找到幻灯片:%s"
|
|
8
|
+
_i18n_set en slides_logs.no_failure_records_for "No failure records for %s"
|
|
9
|
+
_i18n_set zh slides_logs.no_failure_records_for "该幻灯片没有失败记录"
|
|
10
|
+
|
|
11
|
+
_i18n_set en slides_logs.usage_roll_slides_logs_slug "Usage: roll slides logs <slug>"
|
|
12
|
+
_i18n_set zh slides_logs.usage_roll_slides_logs_slug "用法: roll slides logs <slug>"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
|
|
2
|
+
_i18n_set en slides_new.unknown_option_1 "Unknown option: %s"
|
|
3
|
+
_i18n_set zh slides_new.unknown_option_1 "未知选项: %s"
|
|
4
|
+
_i18n_set en slides_new.unexpected_argument_1 "Unexpected argument: %s"
|
|
5
|
+
_i18n_set zh slides_new.unexpected_argument_1 "多余参数: %s"
|
|
6
|
+
|
|
7
|
+
_i18n_set en slides_new.en_roll_slides_new_template "[EN: 用法:roll slides new \"<主题>\" [--template <模板名>] [...]"
|
|
8
|
+
_i18n_set zh slides_new.en_roll_slides_new_template " 用法:roll slides new \"<主题>\" [--template <模板名>] [--quiet] [--no-build]"
|
|
9
|
+
_i18n_set en slides_new.en_slug "[EN: 无法从主题派生 slug:%s...]"
|
|
10
|
+
_i18n_set zh slides_new.en_slug " 无法从主题派生 slug:%s"
|
|
11
|
+
_i18n_set en slides_new.en_roll_slides_build "[EN:下一步:roll slides build %s...]"
|
|
12
|
+
_i18n_set zh slides_new.en_roll_slides_build "下一步:roll slides build %s"
|
|
13
|
+
|
|
14
|
+
_i18n_set en slides_new.template_requires_value "--template requires a value"
|
|
15
|
+
_i18n_set zh slides_new.template_requires_value "--template 需要一个值"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
|
|
2
|
+
_i18n_set en slides_preview.unknown_option_1 "Unknown option: %s"
|
|
3
|
+
_i18n_set zh slides_preview.unknown_option_1 "未知选项: %s"
|
|
4
|
+
_i18n_set en slides_preview.unexpected_argument_1 "Unexpected argument: %s"
|
|
5
|
+
_i18n_set zh slides_preview.unexpected_argument_1 "多余参数: %s"
|
|
6
|
+
_i18n_set en slides_preview.preview "Preview → %s"
|
|
7
|
+
_i18n_set zh slides_preview.preview "打开预览 → %s"
|
|
8
|
+
|
|
9
|
+
_i18n_set en slides_preview.usage_roll_slides_preview_slug_no "Usage: roll slides preview <slug> [--no-open]"
|
|
10
|
+
_i18n_set zh slides_preview.usage_roll_slides_preview_slug_no "用法: roll slides preview <slug> [--no-open]"
|
|
11
|
+
_i18n_set en slides_preview.en_html "[EN: 未找到已渲染的 HTML:%s...]"
|
|
12
|
+
_i18n_set zh slides_preview.en_html " 未找到已渲染的 HTML:%s"
|
|
13
|
+
_i18n_set en slides_preview.en_roll_slides_build "[EN: 提示:先运行 'roll slides build %s' 渲染幻灯片。...]"
|
|
14
|
+
_i18n_set zh slides_preview.en_roll_slides_build " 提示:先运行 'roll slides build %s' 渲染幻灯片。"
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
|
|
2
|
+
_i18n_set en slides_templates.unknown_option_1 "Unknown option: %s"
|
|
3
|
+
_i18n_set zh slides_templates.unknown_option_1 "未知选项: %s"
|
|
4
|
+
_i18n_set en slides_templates.unexpected_argument_1 "Unexpected argument: %s"
|
|
5
|
+
_i18n_set zh slides_templates.unexpected_argument_1 "多余参数: %s"
|
|
6
|
+
_i18n_set en slides_templates.no_templates_found "No templates found"
|
|
7
|
+
_i18n_set zh slides_templates.no_templates_found "无可用模板"
|
package/lib/i18n/status.sh
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
|
|
2
|
+
_i18n_set en status.loop_overview "%sLoop Overview:"
|
|
3
|
+
_i18n_set zh status.loop_overview "所有项目 loop 状态%s"
|
|
4
|
+
_i18n_set en status.not_synced " %s-%s %s: %s (not synced /"
|
|
5
|
+
_i18n_set zh status.not_synced "未同步)"
|
|
6
|
+
_i18n_set en status.out_of_sync_roll_md_missing " %s~%s %s: %s (out of sync — roll.md missing / roll.md"
|
|
7
|
+
_i18n_set zh status.out_of_sync_roll_md_missing "缺失)"
|
|
8
|
+
_i18n_set en status.out_of_sync_roll_md_outdated " %s~%s %s: %s (out of sync — roll.md outdated / roll.md"
|
|
9
|
+
_i18n_set zh status.out_of_sync_roll_md_outdated "已过期)"
|
|
10
|
+
_i18n_set en status.out_of_sync_roll_md_not " %s~%s %s: %s (out of sync — @roll.md not in config /"
|
|
11
|
+
_i18n_set zh status.out_of_sync_roll_md_not "未包含 @roll.md)"
|
|
12
|
+
_i18n_set en status.in_sync " %s=%s %s: %s (in sync /"
|
|
13
|
+
_i18n_set zh status.in_sync "已同步)"
|
|
14
|
+
_i18n_set en status.peer_call_timeout_s_peer "Peer call timeout: %ss Peer"
|
|
15
|
+
_i18n_set zh status.peer_call_timeout_s_peer "调用超时: %ss"
|
|
16
|
+
_i18n_set en status.unsupported_peer "Unsupported peer: %s"
|
|
17
|
+
_i18n_set zh status.unsupported_peer "不支持的 peer: %s"
|
|
18
|
+
_i18n_set en status.unsupported_peer_2 "Unsupported peer: %s"
|
|
19
|
+
_i18n_set zh status.unsupported_peer_2 "不支持的 peer: %s"
|
package/lib/i18n/update.sh
CHANGED
|
@@ -7,3 +7,10 @@ _i18n_set en update.still_mismatch "Still on %s after retry — registry may not
|
|
|
7
7
|
_i18n_set zh update.still_mismatch "重试后仍为 %s,注册表可能尚未同步,请稍后再试。"
|
|
8
8
|
_i18n_set en update.current_version "Current version: roll v%s"
|
|
9
9
|
_i18n_set zh update.current_version "当前版本: roll v%s"
|
|
10
|
+
|
|
11
|
+
_i18n_set en update.upgrading_via_npm "Upgrading via npm..."
|
|
12
|
+
_i18n_set zh update.upgrading_via_npm "正在通过 npm 升级..."
|
|
13
|
+
_i18n_set en update.npm_install_failed_check_network_proxy "npm install failed. Check network/proxy and try again. npm"
|
|
14
|
+
_i18n_set zh update.npm_install_failed_check_network_proxy "安装失败,请检查网络/代理后重试。"
|
|
15
|
+
_i18n_set en update.re_syncing_to_ai_tools "Re-syncing to AI tools..."
|
|
16
|
+
_i18n_set zh update.re_syncing_to_ai_tools "正在重新同步到 AI 工具..."
|
package/lib/i18n.sh
CHANGED
|
@@ -22,14 +22,25 @@ _i18n_safe_key() {
|
|
|
22
22
|
echo "${1//[^A-Za-z0-9_]/_}"
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
# Uppercase a lang code without forking for the EN/ZH common case.
|
|
26
|
+
# macOS still ships bash 3.2 which doesn't have ${var^^}; this helper keeps
|
|
27
|
+
# the no-subshell goal for the languages we actually ship.
|
|
28
|
+
_i18n_upper() {
|
|
29
|
+
case "$1" in
|
|
30
|
+
en|EN) printf 'EN' ;;
|
|
31
|
+
zh|ZH) printf 'ZH' ;;
|
|
32
|
+
*) printf '%s' "$1" | tr '[:lower:]' '[:upper:]' ;;
|
|
33
|
+
esac
|
|
34
|
+
}
|
|
35
|
+
|
|
25
36
|
# Fill the catalog. Modules call this at source-time:
|
|
26
37
|
# _i18n_set en hello "Hello, %s!"
|
|
27
38
|
# _i18n_set zh hello "你好,%s!"
|
|
28
39
|
_i18n_set() {
|
|
29
40
|
local lang="$1" key="$2" val="$3"
|
|
30
41
|
local upper safe varname
|
|
31
|
-
upper
|
|
32
|
-
safe
|
|
42
|
+
upper="$(_i18n_upper "$lang")"
|
|
43
|
+
safe="${key//[^A-Za-z0-9_]/_}" # inline param-expansion — no subshell fork
|
|
33
44
|
varname="MSG_${upper}_${safe}"
|
|
34
45
|
printf -v "$varname" '%s' "$val"
|
|
35
46
|
export "$varname"
|
|
@@ -92,8 +103,13 @@ _i18n_resolve_lang() {
|
|
|
92
103
|
msg() {
|
|
93
104
|
local key="$1"; shift || true
|
|
94
105
|
local lang safe
|
|
95
|
-
|
|
96
|
-
|
|
106
|
+
# FIX: avoid subshell forks — check cached value first, inline key sanitize
|
|
107
|
+
if [[ -n "${ROLL_LANG_RESOLVED:-}" ]]; then
|
|
108
|
+
lang="$ROLL_LANG_RESOLVED"
|
|
109
|
+
else
|
|
110
|
+
lang=$(_i18n_resolve_lang)
|
|
111
|
+
fi
|
|
112
|
+
safe="${key//[^A-Za-z0-9_]/_}" # FIX: inline — no subshell fork
|
|
97
113
|
|
|
98
114
|
local zh_var="MSG_ZH_${safe}"
|
|
99
115
|
local en_var="MSG_EN_${safe}"
|
|
@@ -112,9 +128,73 @@ msg() {
|
|
|
112
128
|
echo
|
|
113
129
|
}
|
|
114
130
|
|
|
131
|
+
# Look up message catalog entry with an explicit language, bypassing ROLL_LANG env.
|
|
132
|
+
# Usage: msg_lang <lang> <key> [printf-args...]
|
|
133
|
+
# Useful when the caller tracks language independently of the process env (e.g.
|
|
134
|
+
# _loop_schedule_desc passes an explicit lang= parameter).
|
|
135
|
+
msg_lang() {
|
|
136
|
+
local lang="$1" key="$2"; shift 2 || true
|
|
137
|
+
local upper safe varname tmpl
|
|
138
|
+
upper="$(_i18n_upper "$lang")"
|
|
139
|
+
safe="${key//[^A-Za-z0-9_]/_}"
|
|
140
|
+
varname="MSG_${upper}_${safe}"
|
|
141
|
+
tmpl="${!varname:-}"
|
|
142
|
+
if [[ -z "$tmpl" ]]; then
|
|
143
|
+
varname="MSG_EN_${safe}"
|
|
144
|
+
tmpl="${!varname:-$key}"
|
|
145
|
+
fi
|
|
146
|
+
# shellcheck disable=SC2059 — template comes from our own catalog
|
|
147
|
+
printf "$tmpl" "$@"
|
|
148
|
+
echo
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
# ── CJK display-width helpers (US-I18N-006) ──────────────────────────────
|
|
152
|
+
# Compute visual display width of a string (CJK fullwidth = 2 cells,
|
|
153
|
+
# ASCII = 1 cell). Delegates to python3 which is already a dependency.
|
|
154
|
+
_strw() {
|
|
155
|
+
python3 -c "
|
|
156
|
+
import sys
|
|
157
|
+
from unicodedata import east_asian_width
|
|
158
|
+
s = sys.argv[1]
|
|
159
|
+
w = sum(2 if east_asian_width(c) in ('F','W') else 1 for c in s)
|
|
160
|
+
print(w)
|
|
161
|
+
" "$1"
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
# Left-pad a string to a target visual display width (CJK-aware).
|
|
165
|
+
_pad_l() {
|
|
166
|
+
local s="$1" target="$2"
|
|
167
|
+
local sw
|
|
168
|
+
sw=$(_strw "$s")
|
|
169
|
+
local pad=$((target - sw))
|
|
170
|
+
if ((pad <= 0)); then
|
|
171
|
+
printf '%s' "$s"
|
|
172
|
+
else
|
|
173
|
+
printf '%s%*s' "$s" "$pad" ''
|
|
174
|
+
fi
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
# Right-pad a string to a target visual display width (CJK-aware).
|
|
178
|
+
_pad_r() {
|
|
179
|
+
local s="$1" target="$2"
|
|
180
|
+
local sw
|
|
181
|
+
sw=$(_strw "$s")
|
|
182
|
+
local pad=$((target - sw))
|
|
183
|
+
if ((pad <= 0)); then
|
|
184
|
+
printf '%s' "$s"
|
|
185
|
+
else
|
|
186
|
+
printf '%*s%s' "$pad" '' "$s"
|
|
187
|
+
fi
|
|
188
|
+
}
|
|
189
|
+
|
|
115
190
|
# ── Load per-command message catalogs (US-I18N-002) ──
|
|
116
191
|
# Source all lib/i18n/*.sh files (skip self). Called once at bin/roll startup.
|
|
117
192
|
_i18n_load_catalogs() {
|
|
193
|
+
# FIX: guard — skip re-load when catalogs already loaded in this shell.
|
|
194
|
+
# Each bats test sources bin/roll in its own subshell so the guard resets
|
|
195
|
+
# automatically; but within a single shell (e.g. integration tests) this
|
|
196
|
+
# prevents 2.6s of catalog I/O on every subsequent source.
|
|
197
|
+
[[ -n "${_I18N_CATALOGS_LOADED:-}" ]] && return 0
|
|
118
198
|
local i18n_dir
|
|
119
199
|
i18n_dir="$(dirname "${BASH_SOURCE[0]:-$0}")/i18n"
|
|
120
200
|
if [[ -d "$i18n_dir" ]]; then
|
|
@@ -134,5 +214,6 @@ _i18n_load_catalogs() {
|
|
|
134
214
|
done
|
|
135
215
|
fi
|
|
136
216
|
fi
|
|
217
|
+
_I18N_CATALOGS_LOADED=1
|
|
137
218
|
}
|
|
138
219
|
_i18n_load_catalogs
|
package/lib/roll-home.py
CHANGED
|
@@ -31,7 +31,41 @@ from roll_render import COLS, c, row, section_head, strw, pad
|
|
|
31
31
|
# ════════════════════════════════════════════════════════════════════════════
|
|
32
32
|
# Paths
|
|
33
33
|
# ════════════════════════════════════════════════════════════════════════════
|
|
34
|
+
def _git_remote_url(repo_path: str) -> Optional[str]:
|
|
35
|
+
"""Mirror lib/roll-loop-status.py::_git_remote_url — origin first, then any."""
|
|
36
|
+
try:
|
|
37
|
+
url = subprocess.check_output(
|
|
38
|
+
["git", "-C", repo_path, "remote", "get-url", "origin"],
|
|
39
|
+
stderr=subprocess.DEVNULL, text=True,
|
|
40
|
+
).strip()
|
|
41
|
+
if url:
|
|
42
|
+
return url
|
|
43
|
+
except Exception:
|
|
44
|
+
pass
|
|
45
|
+
try:
|
|
46
|
+
remotes = subprocess.check_output(
|
|
47
|
+
["git", "-C", repo_path, "remote"],
|
|
48
|
+
stderr=subprocess.DEVNULL, text=True,
|
|
49
|
+
).strip().splitlines()
|
|
50
|
+
if remotes:
|
|
51
|
+
url = subprocess.check_output(
|
|
52
|
+
["git", "-C", repo_path, "remote", "get-url", remotes[0]],
|
|
53
|
+
stderr=subprocess.DEVNULL, text=True,
|
|
54
|
+
).strip()
|
|
55
|
+
if url:
|
|
56
|
+
return url
|
|
57
|
+
except Exception:
|
|
58
|
+
pass
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
|
|
34
62
|
def _project_slug(path: Optional[str] = None) -> str:
|
|
63
|
+
# US-LOOP-006: cycle wrapper exports ROLL_MAIN_SLUG — honour it (parity with
|
|
64
|
+
# bin/roll _project_slug and lib/roll-loop-status.py project_slug).
|
|
65
|
+
env_slug = os.environ.get("ROLL_MAIN_SLUG", "").strip()
|
|
66
|
+
if env_slug:
|
|
67
|
+
return env_slug
|
|
68
|
+
|
|
35
69
|
path = os.path.realpath(path or os.getcwd())
|
|
36
70
|
try:
|
|
37
71
|
common = subprocess.check_output(
|
|
@@ -42,6 +76,27 @@ def _project_slug(path: Optional[str] = None) -> str:
|
|
|
42
76
|
path = common[:-5]
|
|
43
77
|
except Exception:
|
|
44
78
|
pass
|
|
79
|
+
|
|
80
|
+
# US-OBS-010: derive slug from git remote URL for stable cross-machine
|
|
81
|
+
# identity. Mirror normalization in bin/roll + lib/roll-loop-status.py
|
|
82
|
+
# so all three callers agree on the slug. Without this, `roll` home dash
|
|
83
|
+
# looks up plists at the old path-based slug while `roll loop status`
|
|
84
|
+
# looks them up at the new remote-based slug — dashboards diverge and
|
|
85
|
+
# the home page falsely reports the loop as "missing".
|
|
86
|
+
remote_url = _git_remote_url(path)
|
|
87
|
+
if remote_url:
|
|
88
|
+
remote_url = remote_url.rstrip("/")
|
|
89
|
+
if remote_url.endswith(".git"):
|
|
90
|
+
remote_url = remote_url[:-4]
|
|
91
|
+
m = re.match(r"^git@([^:]+):(.+)$", remote_url)
|
|
92
|
+
if m:
|
|
93
|
+
remote_url = f"https://{m.group(1)}/{m.group(2)}"
|
|
94
|
+
remote_url = remote_url.lower()
|
|
95
|
+
base = re.sub(r"[^A-Za-z0-9]+", "-", os.path.basename(remote_url)).strip("-")
|
|
96
|
+
h = hashlib.md5(remote_url.encode()).hexdigest()[:6]
|
|
97
|
+
return f"{base}-{h}"
|
|
98
|
+
|
|
99
|
+
# Fallback: path-based (pre-US-OBS-010 behaviour) when no remote configured.
|
|
45
100
|
base = re.sub(r"[^A-Za-z0-9]+", "-", os.path.basename(path)).strip("-")
|
|
46
101
|
h = hashlib.md5(path.encode()).hexdigest()[:6]
|
|
47
102
|
return f"{base}-{h}"
|