Conversation
There was a problem hiding this comment.
Pull request overview
This PR refactors the report-generation pipeline to reduce duplication by centralizing bilingual (zh/en) strings, splitting prompt builders by domain, and extracting “save” logic into dedicated modules.
Changes:
- Introduce
src/i18n.tsas a centralized home for bilingual strings, labels, and issue title helpers, and wire it into report generation/notifications/manifest generation. - Split prompt builders into
src/prompts.ts(repo-level) andsrc/prompts-data.ts(data-source + rollups), updating imports and tests accordingly. - Extract report saving (LLM call + file save + optional GitHub issue) into
src/report-savers.ts, and consolidatesleep()intosrc/date.ts.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/web.ts | Reuses shared sleep() from date.ts. |
| src/rollup.ts | Uses prompts-data.ts + i18n titles for weekly/monthly rollups. |
| src/report.ts | Uses centralized i18n footer text + shared sleep(). |
| src/report-savers.ts | New module extracting save/report/issue creation logic from index.ts. |
| src/report-builders.ts | Uses i18n titles/headings for CLI/OpenClaw report assembly. |
| src/prompts.ts | Keeps repo-level prompt builders and switches to Lang type from i18n. |
| src/prompts-data.ts | New module for trending/web/HN + weekly/monthly prompt builders. |
| src/notify.ts | Uses i18n-provided notification labels instead of inline maps. |
| src/index.ts | Orchestration updated to use extracted builders/savers and i18n helpers. |
| src/i18n.ts | New centralized bilingual strings, labels, and helper functions. |
| src/github.ts | Moves LABEL_COLORS to module scope for reuse/clarity. |
| src/generate-manifest.ts | Uses centralized REPORT_LABELS from i18n. |
| src/date.ts | Expands date utilities to include sleep(). |
| src/tests/prompt-builders.test.ts | Updates imports due to prompt module split. |
| src/tests/i18n.test.ts | Adds unit tests for i18n maps/helpers. |
| CLAUDE.md | Updates documentation to reflect new module split and i18n conventions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| const enContent = | ||
| `# AI Tools Ecosystem Weekly Report ${weekStr}\n\n` + | ||
| `> Coverage: ${last7[last7.length - 1]} ~ ${last7[0]} | Generated: ${utcStr} UTC\n\n` + | ||
| `# ${WEEKLY_REPORT.title.en} ${weekStr}\n\n` + | ||
| `> ${WEEKLY_REPORT.coverage.en}: ${last7[last7.length - 1]} ~ ${last7[0]} | Generated: ${utcStr} UTC\n\n` + | ||
| `---\n\n` + |
There was a problem hiding this comment.
Weekly rollup EN output still hardcodes the timestamp label ("Generated") while the ZH output hardcodes "生成时间". Since bilingual strings are meant to be centralized in src/i18n.ts (CLAUDE.md:99), consider adding a WEEKLY_REPORT.generated (or shared) helper and using it here for both languages.
| const zhContent = | ||
| `# AI 工具生态月报 ${monthStr}\n\n` + | ||
| `# ${MONTHLY_REPORT.title.zh} ${monthStr}\n\n` + | ||
| `> 数据来源: ${sourceLabel.zh} | 生成时间: ${utcStr} UTC\n\n` + | ||
| `---\n\n` + | ||
| zhSummary + | ||
| footer; | ||
|
|
||
| const enContent = | ||
| `# AI Tools Ecosystem Monthly Report ${monthStr}\n\n` + | ||
| `# ${MONTHLY_REPORT.title.en} ${monthStr}\n\n` + | ||
| `> Sources: ${sourceLabel.en} | Generated: ${utcStr} UTC\n\n` + | ||
| `---\n\n` + |
There was a problem hiding this comment.
Monthly rollup output still hardcodes bilingual strings inline ("Sources"/"数据来源", "Generated"/"生成时间"). To match the new i18n convention (CLAUDE.md:99), please move these labels into src/i18n.ts (e.g., MONTHLY_REPORT.sourcesLabel/generated helper) and reference them here.
src/prompts-data.ts
Outdated
| if (newItems.length === 0) return `## ${siteName}\n\n(${mode},暂无可供分析的内容。)`; | ||
|
|
||
| const unableToExtract = lang === "en" ? "(Unable to extract text content)" : "(无法提取文本内容)"; | ||
| const itemsText = newItems | ||
| .map((item) => | ||
| [ | ||
| `### [${item.title || item.url}](${item.url})`, | ||
| `- 分类: ${item.category} | 发布/更新: ${item.lastmod.slice(0, 10) || "未知"}`, | ||
| `- 内容节选: ${item.content || unableToExtract}`, | ||
| ].join("\n"), | ||
| ) | ||
| .join("\n\n"); | ||
|
|
||
| return `## ${siteName}(${mode})\n\n${itemsText}`; |
There was a problem hiding this comment.
buildWebReportPrompt mixes Chinese-only labels even when lang === "en" (e.g., "(${mode},暂无可供分析的内容。)", "分类", "发布/更新", "内容节选", and Chinese parentheses in the section header). This makes the English prompt internally inconsistent and can degrade LLM output quality. Please provide English equivalents when lang === "en".
| if (newItems.length === 0) return `## ${siteName}\n\n(${mode},暂无可供分析的内容。)`; | |
| const unableToExtract = lang === "en" ? "(Unable to extract text content)" : "(无法提取文本内容)"; | |
| const itemsText = newItems | |
| .map((item) => | |
| [ | |
| `### [${item.title || item.url}](${item.url})`, | |
| `- 分类: ${item.category} | 发布/更新: ${item.lastmod.slice(0, 10) || "未知"}`, | |
| `- 内容节选: ${item.content || unableToExtract}`, | |
| ].join("\n"), | |
| ) | |
| .join("\n\n"); | |
| return `## ${siteName}(${mode})\n\n${itemsText}`; | |
| if (newItems.length === 0) { | |
| return lang === "en" | |
| ? `## ${siteName}\n\n(${mode}, no content available for analysis.)` | |
| : `## ${siteName}\n\n(${mode},暂无可供分析的内容。)`; | |
| } | |
| const unableToExtract = lang === "en" ? "(Unable to extract text content)" : "(无法提取文本内容)"; | |
| const itemsText = newItems | |
| .map((item) => | |
| [ | |
| `### [${item.title || item.url}](${item.url})`, | |
| lang === "en" | |
| ? `- Category: ${item.category} | Published/Updated: ${item.lastmod.slice(0, 10) || "Unknown"}` | |
| : `- 分类: ${item.category} | 发布/更新: ${item.lastmod.slice(0, 10) || "未知"}`, | |
| lang === "en" | |
| ? `- Content excerpt: ${item.content || unableToExtract}` | |
| : `- 内容节选: ${item.content || unableToExtract}`, | |
| ].join("\n"), | |
| ) | |
| .join("\n\n"); | |
| return lang === "en" | |
| ? `## ${siteName} (${mode})\n\n${itemsText}` | |
| : `## ${siteName}(${mode})\n\n${itemsText}`; |
src/prompts-data.ts
Outdated
| ? `${i + 1}. **${s.title}**\n` + | ||
| ` Link: ${s.url}\n` + | ||
| ` Discussion: ${s.hnUrl}\n` + | ||
| ` Score: ${s.points} | Comments: ${s.comments} | Author: @${s.author} | 时间: ${s.createdAt.slice(0, 16)}` |
There was a problem hiding this comment.
In the English HN prompt, the story metadata line still contains a Chinese label ("时间"). This should be English (e.g., "Time") when lang === "en" to keep the prompt language consistent.
| ` Score: ${s.points} | Comments: ${s.comments} | Author: @${s.author} | 时间: ${s.createdAt.slice(0, 16)}` | |
| ` Score: ${s.points} | Comments: ${s.comments} | Author: @${s.author} | Time: ${s.createdAt.slice(0, 16)}` |
| const fileName = lang === "en" ? "ai-trending-en.md" : "ai-trending.md"; | ||
| const header = | ||
| `# ${TRENDING_REPORT.title[lang]} ${dateStr}\n\n` + | ||
| `> ${TRENDING_REPORT.sources[lang]} | ${lang === "en" ? "Generated" : "生成时间"}: ${utcStr} UTC\n\n---\n\n`; |
There was a problem hiding this comment.
In the trending report header, the timestamp label is still selected via an inline zh/en ternary ("Generated" vs "生成时间"). This conflicts with the repo convention to centralize bilingual strings in src/i18n.ts (see CLAUDE.md:99). Please move this label (or a full TRENDING_REPORT.meta/header helper) into i18n.ts and reference it here.
| `> ${TRENDING_REPORT.sources[lang]} | ${lang === "en" ? "Generated" : "生成时间"}: ${utcStr} UTC\n\n---\n\n`; | |
| `> ${TRENDING_REPORT.sources[lang]} | ${TRENDING_REPORT.generatedLabel[lang]}: ${utcStr} UTC\n\n---\n\n`; |
| const header = | ||
| lang === "en" | ||
| ? `# ${HN_REPORT.title[lang]} ${dateStr}\n\n` + | ||
| `> Source: [Hacker News](https://news.ycombinator.com/) | ` + | ||
| `${hnData.stories.length} stories | Generated: ${utcStr} UTC\n\n` + | ||
| `---\n\n` | ||
| : `# ${HN_REPORT.title[lang]} ${dateStr}\n\n` + | ||
| `> 数据来源: [Hacker News](https://news.ycombinator.com/) | ` + | ||
| `共 ${hnData.stories.length} 条 | 生成时间: ${utcStr} UTC\n\n` + | ||
| `---\n\n`; |
There was a problem hiding this comment.
The HN report header still hardcodes several bilingual strings via a large lang === "en" ? ... : ... block ("Source", "Generated", "stories", etc.). Per the repo convention (CLAUDE.md:99), these should be centralized in src/i18n.ts (e.g., HN_REPORT.meta/header helper) to avoid scattered translation logic.
| const meta = | ||
| lang === "en" | ||
| ? { | ||
| title: `# OpenClaw Ecosystem Digest ${dateStr}\n\n`, | ||
| meta: `> Issues: ${issues.length} | PRs: ${prs.length} | Projects covered: ${1 + openclawPeers.length} | Generated: ${utcStr} UTC\n\n`, | ||
| deepDive: `## OpenClaw Deep Dive\n\n`, | ||
| comparison: `## Cross-Ecosystem Comparison\n\n`, | ||
| peers: `## Peer Project Reports\n\n`, | ||
| } | ||
| : { | ||
| title: `# OpenClaw 生态日报 ${dateStr}\n\n`, | ||
| meta: `> Issues: ${issues.length} | PRs: ${prs.length} | 覆盖项目: ${1 + openclawPeers.length} 个 | 生成时间: ${utcStr} UTC\n\n`, | ||
| deepDive: `## OpenClaw 项目深度报告\n\n`, | ||
| comparison: `## 横向生态对比\n\n`, | ||
| peers: `## 同赛道项目详细报告\n\n`, | ||
| }; | ||
| ? `> Issues: ${issues.length} | PRs: ${prs.length} | Projects covered: ${1 + openclawPeers.length} | Generated: ${utcStr} UTC\n\n` | ||
| : `> Issues: ${issues.length} | PRs: ${prs.length} | 覆盖项目: ${1 + openclawPeers.length} 个 | 生成时间: ${utcStr} UTC\n\n`; |
There was a problem hiding this comment.
OpenClaw report metadata is still built with an inline zh/en ternary ("Generated"/"生成时间", "Projects covered"/"覆盖项目"). This reintroduces inline bilingual strings; please add an OPENCLAW_REPORT.meta helper in src/i18n.ts (similar to CLI_REPORT.meta) and use it here to follow CLAUDE.md:99.
No description provided.