siluzan-tso-cli 1.1.60-beta.2 → 1.1.60

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 (54) hide show
  1. package/README.md +1 -2
  2. package/dist/index.js +999 -256
  3. package/dist/skill/_meta.json +2 -2
  4. package/dist/skill/assets/pmax-patch-campaign-template.json +1 -1
  5. package/dist/skill/references/accounts/accounts-list.md +1 -1
  6. package/dist/skill/references/accounts/accounts-permissions.md +2 -2
  7. package/dist/skill/references/accounts/finance.md +1 -1
  8. package/dist/skill/references/accounts/open-account-by-media.md +1 -1
  9. package/dist/skill/references/accounts/open-account-google-ui.md +1 -1
  10. package/dist/skill/references/analytics/rag.md +1 -1
  11. package/dist/skill/references/analytics/reporting.md +5 -5
  12. package/dist/skill/references/core/agent-conventions.md +10 -0
  13. package/dist/skill/references/core/playbooks.md +12 -11
  14. package/dist/skill/references/core/setup.md +5 -5
  15. package/dist/skill/references/core/workflows.md +1 -1
  16. package/dist/skill/references/google-ads/google-ads-write.md +3 -3
  17. package/dist/skill/references/google-ads/pmax-api.md +4 -4
  18. package/dist/skill/references/misc/tso-home.md +2 -2
  19. package/dist/skill/references/report-templates/README.md +2 -0
  20. package/dist/skill/references/report-templates/bing-period-report.md +2 -0
  21. package/dist/skill/references/report-templates/google-ads-diagnosis.md +2 -0
  22. package/dist/skill/references/report-templates/google-inquiry-analysis.md +11 -0
  23. package/dist/skill/references/report-templates/google-period-report-excel.md +2 -0
  24. package/dist/skill/references/report-templates/google-period-report.md +4 -1
  25. package/dist/skill/references/report-templates/google-quarterly-report.md +2 -0
  26. package/dist/skill/references/report-templates/market-analysis-report.md +2 -0
  27. package/dist/skill/references/report-templates/meta-account-diagnosis-report.md +2 -0
  28. package/dist/skill/references/report-templates/meta-period-report.md +2 -0
  29. package/dist/skill/references/report-templates/okki-weekly-google-client.md +21 -0
  30. package/dist/skill/references/report-templates/tiktok-period-report.md +2 -0
  31. package/dist/skill/references/report-templates/website-diagnosis-report.md +2 -0
  32. package/dist/skill/references/report-templates/yandex-period-report.md +2 -0
  33. package/dist/skill/report-templates/README.md +2 -0
  34. package/dist/skill/report-templates/bing-period-report.html +16 -3
  35. package/dist/skill/report-templates/bing-period-report.md +2 -0
  36. package/dist/skill/report-templates/google-ads-diagnosis.md +2 -0
  37. package/dist/skill/report-templates/google-inquiry-analysis.md +11 -0
  38. package/dist/skill/report-templates/google-period-report-excel.md +2 -0
  39. package/dist/skill/report-templates/google-period-report.html +35 -6
  40. package/dist/skill/report-templates/google-period-report.md +4 -1
  41. package/dist/skill/report-templates/google-quarterly-report.html +1 -1
  42. package/dist/skill/report-templates/google-quarterly-report.md +2 -0
  43. package/dist/skill/report-templates/market-analysis-report.md +2 -0
  44. package/dist/skill/report-templates/meta-account-diagnosis-report.md +2 -0
  45. package/dist/skill/report-templates/meta-period-report.md +2 -0
  46. package/dist/skill/report-templates/okki-weekly-google-client.md +21 -0
  47. package/dist/skill/report-templates/tiktok-period-report.html +4 -2
  48. package/dist/skill/report-templates/tiktok-period-report.md +2 -0
  49. package/dist/skill/report-templates/website-diagnosis-report.md +2 -0
  50. package/dist/skill/report-templates/yandex-period-report.html +4 -2
  51. package/dist/skill/report-templates/yandex-period-report.md +2 -0
  52. package/dist/skill/scripts/install.ps1 +3 -3
  53. package/dist/skill/scripts/install.sh +3 -3
  54. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4051,7 +4051,7 @@ var DEFAULT_API_BASE;
4051
4051
  var init_defaults = __esm({
4052
4052
  "src/config/defaults.ts"() {
4053
4053
  "use strict";
4054
- DEFAULT_API_BASE = "https://tso-api-ci.siluzan.com";
4054
+ DEFAULT_API_BASE = "https://tso-api.siluzan.com";
4055
4055
  }
4056
4056
  });
4057
4057
 
@@ -5212,6 +5212,63 @@ var init_media_account_batch = __esm({
5212
5212
  }
5213
5213
  });
5214
5214
 
5215
+ // src/report-i18n/lang.ts
5216
+ function resolveReportLang(opts) {
5217
+ const flag = opts.flag?.trim();
5218
+ if (flag) return parseLang(flag, "--lang");
5219
+ if (opts.meta && typeof opts.meta === "object" && !Array.isArray(opts.meta)) {
5220
+ const raw = opts.meta.lang;
5221
+ if (raw == null || raw === "") return "zh";
5222
+ return parseLang(String(raw), "meta.lang");
5223
+ }
5224
+ return "zh";
5225
+ }
5226
+ function htmlLangAttr(lang) {
5227
+ return lang === "en" ? "en" : "zh-CN";
5228
+ }
5229
+ function stampMetaLang(payload, lang) {
5230
+ const prev = payload.meta;
5231
+ const meta = prev && typeof prev === "object" && !Array.isArray(prev) ? { ...prev, lang } : { lang };
5232
+ return { ...payload, meta };
5233
+ }
5234
+ function readReportLangOrExit(opts) {
5235
+ try {
5236
+ return resolveReportLang(opts);
5237
+ } catch (err) {
5238
+ const message = err instanceof Error ? err.message : String(err);
5239
+ console.error(`
5240
+ \u274C ${message}
5241
+ `);
5242
+ process.exit(1);
5243
+ }
5244
+ }
5245
+ function assertHtmlLangMatches(html, lang) {
5246
+ const match = html.match(/<html\b[^>]*\blang\s*=\s*["']([^"']+)["']/i);
5247
+ const found = (match?.[1] ?? "").trim().toLowerCase();
5248
+ const ok = lang === "en" ? found === "en" || found.startsWith("en-") : found === "zh" || found.startsWith("zh-");
5249
+ if (!ok) {
5250
+ throw new ReportLangError(
5251
+ `html lang="${found || "(missing)"}" \u4E0E\u7EC8\u7A3F\u8BED\u8A00 ${lang} \u4E0D\u4E00\u81F4\uFF08\u4E2D\u6587\u5199 zh-CN\uFF0C\u82F1\u6587\u5199 en\uFF09`
5252
+ );
5253
+ }
5254
+ }
5255
+ function parseLang(raw, source) {
5256
+ if (raw === "zh" || raw === "en") return raw;
5257
+ throw new ReportLangError(`${source} \u53EA\u63A5\u53D7 zh \u6216 en\uFF0C\u6536\u5230 ${raw}`);
5258
+ }
5259
+ var ReportLangError;
5260
+ var init_lang = __esm({
5261
+ "src/report-i18n/lang.ts"() {
5262
+ "use strict";
5263
+ ReportLangError = class extends Error {
5264
+ constructor(message) {
5265
+ super(message);
5266
+ this.name = "ReportLangError";
5267
+ }
5268
+ };
5269
+ }
5270
+ });
5271
+
5215
5272
  // src/commands/accounts-digest/list-fetch.ts
5216
5273
  async function fetchAccountsByMedia(media, config, opts) {
5217
5274
  const cfg = DIGEST_PLATFORM_CONFIG[media];
@@ -5317,11 +5374,12 @@ function escapeMdCell(s) {
5317
5374
  function accountLabel(row) {
5318
5375
  return row.advertiserName || row.name || "-";
5319
5376
  }
5320
- function buildHitTable(rows, maxCpa) {
5321
- const header = "| \u8D26\u6237\u540D\u79F0 | \u8D26\u6237ID | \u533A\u95F4\u6D88\u8017 | \u8F6C\u5316\u6B21\u6570 | \u8F6C\u5316\u6210\u672C | \u5E01\u79CD | \u547D\u4E2D\u539F\u56E0 |\n| --- | --- | ---: | ---: | ---: | --- | --- |";
5377
+ function buildHitTable(rows, maxCpa, lang = "zh") {
5378
+ const en = lang === "en";
5379
+ const header = en ? "| Account | Account ID | Spend | Conversions | CPA | Currency | Reason |\n| --- | --- | ---: | ---: | ---: | --- | --- |" : "| \u8D26\u6237\u540D\u79F0 | \u8D26\u6237ID | \u533A\u95F4\u6D88\u8017 | \u8F6C\u5316\u6B21\u6570 | \u8F6C\u5316\u6210\u672C | \u5E01\u79CD | \u547D\u4E2D\u539F\u56E0 |\n| --- | --- | ---: | ---: | ---: | --- | --- |";
5322
5380
  const lines = rows.map((r) => {
5323
- const reason = classifyDigestHit(r, maxCpa) === "zeroConversions" ? "\u96F6\u8F6C\u5316\u4E14\u6D88\u8017\u5DF2\u8D85\u9608\u503C" : "CPA\u9AD8\u4E8E\u9608\u503C";
5324
- const conv = r.conversions == null ? "\u8F6C\u5316\u672A\u8FD4\u56DE" : String(r.conversions);
5381
+ const reason = classifyDigestHit(r, maxCpa) === "zeroConversions" ? en ? "Zero conversions and spend over the threshold" : "\u96F6\u8F6C\u5316\u4E14\u6D88\u8017\u5DF2\u8D85\u9608\u503C" : en ? "CPA above the threshold" : "CPA\u9AD8\u4E8E\u9608\u503C";
5382
+ const conv = r.conversions == null ? en ? "Conversions missing" : "\u8F6C\u5316\u672A\u8FD4\u56DE" : String(r.conversions);
5325
5383
  const cpa = r.cpa == null ? "\u2014" : String(r.cpa);
5326
5384
  const spend = r.spend == null ? "\u2014" : String(r.spend);
5327
5385
  return `| ${escapeMdCell(accountLabel(r))} | ${r.mediaCustomerId} | ${spend} | ${conv} | ${cpa} | ${r.currencyCode ?? "\u2014"} | ${reason} |`;
@@ -5330,6 +5388,7 @@ function buildHitTable(rows, maxCpa) {
5330
5388
  }
5331
5389
  function buildDigestAgentBrief(opts) {
5332
5390
  const { rows, scanned, validAccountCount, spendPositiveCount, maxCpa, zeroConversions } = opts;
5391
+ const en = opts.lang === "en";
5333
5392
  let highCpa = 0;
5334
5393
  let zeroConvHits = 0;
5335
5394
  for (const r of rows) {
@@ -5343,11 +5402,11 @@ function buildDigestAgentBrief(opts) {
5343
5402
  const allClear = patrol && rows.length === 0;
5344
5403
  let userDelivery;
5345
5404
  if (allClear && maxCpa != null) {
5346
- userDelivery = `\u4ECA\u65E5\u6240\u6709\u8D26\u6237\u8F6C\u5316\u6210\u672C\u6B63\u5E38\uFF0C\u5747\u4F4E\u4E8E${maxCpa}\uFF08\u5404\u8D26\u6237\u6309\u81EA\u8EAB currencyCode \u6BD4\u8F83\uFF0C\u52FF\u8DE8\u5E01\u79CD\u5F53\u540C\u4E00\u5355\u4F4D\uFF09`;
5405
+ userDelivery = en ? `Every account's CPA is under ${maxCpa} today (compare each account in its own currencyCode)` : `\u4ECA\u65E5\u6240\u6709\u8D26\u6237\u8F6C\u5316\u6210\u672C\u6B63\u5E38\uFF0C\u5747\u4F4E\u4E8E${maxCpa}\uFF08\u5404\u8D26\u6237\u6309\u81EA\u8EAB currencyCode \u6BD4\u8F83\uFF0C\u52FF\u8DE8\u5E01\u79CD\u5F53\u540C\u4E00\u5355\u4F4D\uFF09`;
5347
5406
  } else if (allClear && zeroConversions) {
5348
- userDelivery = "\u533A\u95F4\u5185\u6CA1\u6709\u6D88\u8017>0\u4E14\u96F6\u8F6C\u5316\uFF08\u6216\u8F6C\u5316\u672A\u8FD4\u56DE\uFF09\u7684\u8D26\u6237";
5407
+ userDelivery = en ? "No account in this range has spend above 0 and zero conversions (or missing conversions)" : "\u533A\u95F4\u5185\u6CA1\u6709\u6D88\u8017>0\u4E14\u96F6\u8F6C\u5316\uFF08\u6216\u8F6C\u5316\u672A\u8FD4\u56DE\uFF09\u7684\u8D26\u6237";
5349
5408
  } else if (patrol && rows.length > 0) {
5350
- userDelivery = buildHitTable(rows, maxCpa);
5409
+ userDelivery = buildHitTable(rows, maxCpa, opts.lang ?? "zh");
5351
5410
  } else {
5352
5411
  userDelivery = "\u672A\u5F00 --max-cpa / --zero-conversions\u3002\u6309 data.items \u51FA\u8868\uFF1Bmeta.totals \u53EA\u5408\u8BA1 returned \u884C\uFF0C\u7981\u6B62\u5199\u6210\u5168\u90E8\u8D26\u6237\u3002";
5353
5412
  }
@@ -6114,7 +6173,8 @@ async function runAccountsDigest(opts) {
6114
6173
  validAccountCount: validIds.length,
6115
6174
  spendPositiveCount,
6116
6175
  maxCpa: opts.maxCpa,
6117
- zeroConversions: !!opts.zeroConversions
6176
+ zeroConversions: !!opts.zeroConversions,
6177
+ lang: readReportLangOrExit({ flag: opts.lang })
6118
6178
  });
6119
6179
  const digestPayload = { ok: true, data: { items: rows }, meta, agentBrief };
6120
6180
  if (await emitCliJsonOrSnapshot(opts, {
@@ -6201,6 +6261,7 @@ var init_run = __esm({
6201
6261
  init_list_fetch();
6202
6262
  init_login_customer_id();
6203
6263
  init_brief();
6264
+ init_lang();
6204
6265
  init_campaign_types();
6205
6266
  init_shared();
6206
6267
  init_enabled_campaigns();
@@ -6248,7 +6309,7 @@ function register10(program2) {
6248
6309
  "--json-out <path>",
6249
6310
  "\u843D\u76D8\uFF08\u76EE\u5F55\u6216 *.json \u6587\u4EF6\u8DEF\u5F84\uFF09\u5E76\u66F4\u65B0 cli-manifest[-<\u67E5\u8BE2id>].json\uFF1B\u76EE\u5F55\u6A21\u5F0F\u6587\u4EF6\u540D\u4E3A `<section>[-<\u67E5\u8BE2id>].json`\uFF1Bstdout \u4E00\u884C\u6458\u8981 JSON\uFF0C\u542B outlineFile\uFF08TS \u5F0F\u7C7B\u578B\u5728 `*.outline.txt`\uFF09",
6250
6311
  void 0
6251
- ).option("--refresh-dp", "\u5F3A\u5236\u91CD\u62C9 Datapermission \u540E\u518D\u8BF7\u6C42", false).option("--verbose", "\u663E\u793A\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
6312
+ ).option("--refresh-dp", "\u5F3A\u5236\u91CD\u62C9 Datapermission \u540E\u518D\u8BF7\u6C42", false).option("--verbose", "\u663E\u793A\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).option("--lang <lang>", "\u4EA4\u7ED9\u7528\u6237\u7684\u53E5\u5B50\u8BED\u8A00\uFF1Azh \u6216 en\uFF0C\u9ED8\u8BA4 zh").action(
6252
6313
  async (opts) => {
6253
6314
  await runAccountsDigest({
6254
6315
  token: opts.token,
@@ -6269,7 +6330,8 @@ function register10(program2) {
6269
6330
  withCampaignTypes: opts.withCampaignTypes,
6270
6331
  campaignTypeConcurrency: opts.campaignTypeConcurrency,
6271
6332
  withEnabledCampaigns: opts.withEnabledCampaigns,
6272
- enabledCampaignConcurrency: opts.enabledCampaignConcurrency
6333
+ enabledCampaignConcurrency: opts.enabledCampaignConcurrency,
6334
+ lang: opts.lang
6273
6335
  });
6274
6336
  }
6275
6337
  );
@@ -7229,6 +7291,372 @@ var init_xlsx_style_engine = __esm({
7229
7291
  }
7230
7292
  });
7231
7293
 
7294
+ // src/report-i18n/labels.ts
7295
+ function kpiLabels(lang) {
7296
+ return { lang, ...lang === "en" ? EN : ZH };
7297
+ }
7298
+ var ZH, EN;
7299
+ var init_labels = __esm({
7300
+ "src/report-i18n/labels.ts"() {
7301
+ "use strict";
7302
+ ZH = {
7303
+ cost: "\u6D88\u8017",
7304
+ impressions: "\u5C55\u793A\u6B21\u6570",
7305
+ clicks: "\u70B9\u51FB\u6B21\u6570",
7306
+ conversions: "\u8F6C\u5316\u6B21\u6570",
7307
+ ctr: "\u70B9\u51FB\u7387 CTR",
7308
+ cpc: "\u5E73\u5747\u70B9\u51FB\u8D39\u7528",
7309
+ cpa: "\u6BCF\u6B21\u8F6C\u5316\u8D39\u7528 CPA",
7310
+ analysis: "\u5206\u6790",
7311
+ suggestion: "\u5EFA\u8BAE",
7312
+ account: "\u8D26\u6237",
7313
+ currency: "\u8D27\u5E01",
7314
+ mom: "\u73AF\u6BD4"
7315
+ };
7316
+ EN = {
7317
+ cost: "Spend",
7318
+ impressions: "Impressions",
7319
+ clicks: "Clicks",
7320
+ conversions: "Conversions",
7321
+ ctr: "CTR",
7322
+ cpc: "Avg. CPC",
7323
+ cpa: "CPA",
7324
+ analysis: "Analysis",
7325
+ suggestion: "Recommendation",
7326
+ account: "Account",
7327
+ currency: "Currency",
7328
+ mom: "vs prior"
7329
+ };
7330
+ }
7331
+ });
7332
+
7333
+ // src/report-i18n/phrases.en.ts
7334
+ var ENGLISH_PHRASES;
7335
+ var init_phrases_en = __esm({
7336
+ "src/report-i18n/phrases.en.ts"() {
7337
+ "use strict";
7338
+ ENGLISH_PHRASES = {
7339
+ "Google Ads \u5468\u671F\u5206\u6790\u62A5\u544A": "Google Ads period report",
7340
+ "Google Ads \u8D26\u6237\u8BCA\u65AD\u62A5\u544A": "Google Ads account diagnosis",
7341
+ "Google \u5B63\u5EA6\u7ECF\u8425\u62A5\u544A": "Google quarterly business report",
7342
+ "Google \u6708\u5EA6\u7ECF\u8425\u62A5\u544A": "Google monthly business report",
7343
+ "Google \u7ECF\u8425\u62A5\u544A": "Google business report",
7344
+ "Bing \u5E7F\u544A\u8D26\u6237\u5206\u6790\u62A5\u544A": "Bing Ads account report",
7345
+ "Yandex Ads \u8D26\u6237\u5206\u6790\u62A5\u544A": "Yandex Ads account report",
7346
+ "TikTok \u5E7F\u544A\u4E3B\u8D26\u6237\u5206\u6790\u62A5\u544A": "TikTok Ads account report",
7347
+ "Meta / Facebook \u5468\u671F\u5206\u6790\u62A5\u544A": "Meta / Facebook period report",
7348
+ "Meta\uFF08Facebook\uFF09\u8D26\u6237\u8BCA\u65AD\u62A5\u544A": "Meta (Facebook) account diagnosis",
7349
+ "\u7F51\u7AD9\u8BCA\u65AD\u62A5\u544A": "Website diagnosis report",
7350
+ "\u5DE6\u53F3\u6ED1\u52A8\u67E5\u770B\u8868\u683C": "Swipe sideways to see the table",
7351
+ "\u5DE6\u53F3\u6ED1\u52A8\u67E5\u770B\u5176\u4F59\u5217": "Swipe sideways to see more columns",
7352
+ "\u7EDF\u8BA1\u533A\u95F4\u672A\u63D0\u4F9B": "Date range not provided",
7353
+ "\u672A\u627E\u5230\u62A5\u544A\u6570\u636E": "Report data was not found",
7354
+ "\u672A\u6CE8\u5165\u62A5\u544A\u6570\u636E\uFF0C\u8BF7\u901A\u8FC7 facebook-analysis render \u751F\u6210\u3002": "Report data was not injected. Generate this file with facebook-analysis render.",
7355
+ "\uFF08\u5F85 Agent \u64B0\u5199\u6267\u884C\u6458\u8981\uFF09": "(Executive summary not written yet)",
7356
+ "\uFF08\u5F85 Agent \u64B0\u5199\u4F18\u5316\u5EFA\u8BAE\uFF09": "(Recommendations not written yet)",
7357
+ "\uFF08\u5F85 Agent \u64B0\u5199\u884C\u52A8\u9879\uFF09": "(Action items not written yet)",
7358
+ "\u6570\u636E\u4E0D\u53EF\u7528\uFF1A\u672C\u8282\u6682\u65E0\u5206\u6790": "Unavailable: no analysis for this section",
7359
+ "\u6570\u636E\u4E0D\u53EF\u7528\uFF1Adaily-metrics \u672A\u62C9\u53D6\u6216\u533A\u95F4\u5185\u65E0\u6570\u636E": "Unavailable: daily-metrics was not pulled or the range is empty",
7360
+ "\u6570\u636E\u4E0D\u53EF\u7528\uFF1Adimension-summary \u672A\u62C9\u53D6": "Unavailable: dimension-summary was not pulled",
7361
+ "\u6570\u636E\u4E0D\u53EF\u7528\uFF1Acampaigns \u672A\u62C9\u53D6": "Unavailable: campaigns was not pulled",
7362
+ "\u6570\u636E\u4E0D\u53EF\u7528\uFF1Adevices \u672A\u62C9\u53D6": "Unavailable: devices was not pulled",
7363
+ "\u6570\u636E\u4E0D\u53EF\u7528\uFF1Ageographic \u672A\u62C9\u53D6": "Unavailable: geographic was not pulled",
7364
+ "\u6570\u636E\u4E0D\u53EF\u7528\uFF1Akeywords \u672A\u62C9\u53D6": "Unavailable: keywords was not pulled",
7365
+ "\u6570\u636E\u4E0D\u53EF\u7528\uFF1Anegative-keywords \u672A\u62C9\u53D6": "Unavailable: negative-keywords was not pulled",
7366
+ "\u6570\u636E\u4E0D\u53EF\u7528\uFF1Aaudience \u672A\u62C9\u53D6\u6216\u672C\u671F\u95F4\u65E0\u53D7\u4F17\u884C": "Unavailable: audience was not pulled or this period has no audience rows",
7367
+ "\u6570\u636E\u4E0D\u53EF\u7528\uFF1Acreative \u672A\u62C9\u53D6": "Unavailable: creative was not pulled",
7368
+ "\u6570\u636E\u4E0D\u53EF\u7528\uFF1Amaterial \u672A\u62C9\u53D6\uFF08\u6807\u51C6\u8D26\u6237\u65E0 Dynamic Creative \u65F6\u5C5E\u6B63\u5E38\uFF09": "Unavailable: material was not pulled (normal when a standard account has no Dynamic Creative)",
7369
+ "\u5F53\u524D\u8D26\u6237\u5426\u8BCD 0 \u6761": "This account has 0 negative keywords",
7370
+ "\u672A\u8FBE\u5230\u8BE5\u9879\u9EC4\u91D1\u8D26\u6237\u6807\u51C6": "Does not meet this Gold Account criterion",
7371
+ "\u5DF2\u8FBE\u6807\uFF0C\u7EE7\u7EED\u4FDD\u6301": "Met \u2014 keep it up",
7372
+ "\u81EA\u9002\u5E94\u641C\u7D22\u5E7F\u544A": "Responsive search ads",
7373
+ "\u81EA\u9002\u5E94\u5C55\u793A\u5E7F\u544A": "Responsive display ads",
7374
+ "\u8F6C\u5316\u8FFD\u8E2A\u8BBE\u7F6E": "Conversion tracking",
7375
+ "G A\u4E0E\u5E7F\u544A\u8D26\u6237\u8054\u7ED3": "GA linked to the ads account",
7376
+ "\u9644\u52A0\u7ED3\u6784\u5316\u4FE1\u606F\u6458\u8981": "Structured snippets",
7377
+ "\u9644\u52A0\u94FE\u63A5": "Sitelinks",
7378
+ "\u9644\u52A0\u5BA3\u4F20\u4FE1\u606F": "Callouts",
7379
+ "\u9644\u52A0\u7535\u8BDD\u4FE1\u606F": "Call extensions",
7380
+ "\u53D7\u4F17\u7FA4\u4F53\u8BBE\u7F6E": "Audiences",
7381
+ "\u51FA\u4EF7\u7B56\u7565": "Bidding strategy",
7382
+ "\u8D26\u6237\u57FA\u672C\u4FE1\u606F\u4E0E\u76EE\u6807\u8BBE\u5B9A": "Account basics and goals",
7383
+ "\u8D26\u6237\u8BCA\u65AD\u6982\u89C8": "Diagnosis overview",
7384
+ "\u6838\u5FC3\u4E1A\u7EE9\u6307\u6807\u5FEB\u7167": "KPI snapshot",
7385
+ "\u8D26\u6237\u5065\u5EB7\u5EA6\u4E0E\u7ED3\u6784\u5206\u6790": "Account health and structure",
7386
+ "\u6295\u653E\u9884\u7B97\u4E0E\u7ADE\u4E89\u529B\u5206\u6790": "Budget and competitiveness",
7387
+ "\u76EE\u6807\u53D7\u4F17\u4E0E\u6295\u653E\u7B56\u7565": "Audience and targeting",
7388
+ "\u7740\u9646\u9875\u5206\u6790": "Landing pages",
7389
+ "\u5173\u952E\u8BCD\u4E0E\u641C\u7D22\u8BCD\u6D1E\u5BDF": "Keywords and search terms",
7390
+ "\u9884\u7B97\u4E0E\u51FA\u4EF7\u7B56\u7565": "Budget and bidding",
7391
+ "\u5E7F\u544A\u521B\u610F\u4E0E\u7D20\u6750\u4F18\u5316": "Ads and assets",
7392
+ "\u65B0\u4EA7\u54C1\u5E94\u7528": "New products",
7393
+ "\u8BCA\u65AD\u603B\u7ED3": "Diagnosis summary",
7394
+ "\u8BCA\u65AD\u9879": "Diagnosis sections",
7395
+ "\u8BCA\u65AD\u5468\u671F": "Diagnosis period",
7396
+ "\u6267\u884C\u6458\u8981": "Executive summary",
7397
+ "\u6267\u884C\u6458\u8981\uFF08\u603B\u89C8\uFF09": "Executive summary",
7398
+ "\u6BCF\u65E5\u6295\u653E\u8D8B\u52BF": "Daily trend",
7399
+ "\u5468\u671F\u6C47\u603B": "Period totals",
7400
+ "\u5E7F\u544A\u7CFB\u5217\u8868\u73B0": "Campaign performance",
7401
+ "\u8BBE\u5907\u5206\u5E03": "Devices",
7402
+ "\u5730\u57DF\u5206\u5E03\uFF08Top 20\uFF09": "Locations (top 20)",
7403
+ "\u5730\u57DF\u5206\u5E03": "Locations",
7404
+ "\u53D7\u4F17\u5206\u5E03\uFF08Top 20\uFF09": "Audiences (top 20)",
7405
+ "\u53D7\u4F17\u5206\u5E03": "Audiences",
7406
+ "\u5173\u952E\u8BCD\u8868\u73B0\uFF08Top 50\uFF09": "Keywords (top 50)",
7407
+ "\u4F18\u5316\u5EFA\u8BAE": "Recommendations",
7408
+ "\u672C\u671F\u6D88\u8017/\u5C55\u793A/\u70B9\u51FB/\u8F6C\u5316/CTR/CPC/CPA \u6982\u89C8": "Spend, impressions, clicks, conversions, CTR, CPC, and CPA",
7409
+ "\u6309\u65E5\u6D88\u8017 / \u8F6C\u5316 / CPA \u66F2\u7EBF": "Daily spend, conversions, and CPA",
7410
+ "\u5168\u5468\u671F\u7D2F\u8BA1\u6838\u5FC3\u6307\u6807\u4E0E\u641C\u7D22\u66DD\u5149\u4EFD\u989D": "Period totals and search impression share",
7411
+ "\u6309\u6D88\u8017\u6392\u5E8F\uFF0C\u542B\u72B6\u6001\u4E0E\u9884\u7B97/\u51FA\u4EF7\u7C7B\u578B": "Sorted by spend, with status and budget or bid type",
7412
+ "PC / \u79FB\u52A8 / \u5E73\u677F \u6D88\u8017\u4E0E\u6548\u679C\u5360\u6BD4": "Spend and results by computer, mobile, and tablet",
7413
+ "\u6309\u6D88\u8017\u6392\u5E8F\u7684\u56FD\u5BB6/\u5730\u533A\u8868\u73B0": "Countries and regions by spend",
7414
+ "\u8DE8\u7CFB\u5217\u5408\u5E76\u5C55\u793A\uFF0C\u6309\u6D88\u8017\u6392\u5E8F": "Combined across campaigns, sorted by spend",
7415
+ "\u7EFC\u5408\u4EE5\u4E0A\u6570\u636E\u7684\u53EF\u6267\u884C\u6539\u8FDB\u5EFA\u8BAE": "Actions based on the data above",
7416
+ "\u5F53\u524D\u8D26\u6237\u5DF2\u6709\u5426\u5B9A\u5173\u952E\u8BCD\uFF08\u7CFB\u5217 / \u5E7F\u544A\u7EC4 / \u8D26\u6237\uFF09\uFF0C\u4E0D\u662F\u672C\u5468\u671F\u6D88\u8017": "Negative keywords already on the account (campaign, ad group, or account), not this period's spend",
7417
+ "AdGroupAudienceData\uFF1A\u610F\u5411/\u5174\u8DA3/\u518D\u8425\u9500/\u5BA2\u6237\u5339\u914D\u7B49\u5217\u8868\u8868\u73B0\uFF0C\u4E0D\u662F\u5E74\u9F84\xD7\u6027\u522B": "Ad group audiences (in-market, affinity, remarketing, customer match), not age or gender",
7418
+ "\u641C\u7D22\u5E7F\u544A\u66DD\u5149\u4EFD\u989D": "Search impression share",
7419
+ "\u5C55\u793A\u6B21\u6570": "Impressions",
7420
+ "\u70B9\u51FB\u6B21\u6570": "Clicks",
7421
+ "\u8F6C\u5316\u6B21\u6570": "Conversions",
7422
+ "\u70B9\u51FB\u7387 CTR": "CTR",
7423
+ "\u5E73\u5747\u70B9\u51FB\u8D39\u7528": "Avg. CPC",
7424
+ "\u6BCF\u6B21\u8F6C\u5316\u8D39\u7528 CPA": "CPA",
7425
+ "\u6BCF\u6B21\u8F6C\u5316\u8D39\u7528": "Cost / conv.",
7426
+ "\u6D3B\u8DC3\u5929\u6570": "Active days",
7427
+ "\u65E5\u5747\u6D88\u8017": "Avg. daily spend",
7428
+ "\u5E7F\u544A\u7CFB\u5217": "Campaign",
7429
+ "\u5E7F\u544A\u7EC4": "Ad group",
7430
+ "\u5173\u952E\u8BCD": "Keyword",
7431
+ "\u5339\u914D\u65B9\u5F0F": "Match type",
7432
+ "\u56FD\u5BB6/\u5730\u533A": "Country / region",
7433
+ "\u641C\u7D22\u8BCD": "Search term",
7434
+ "\u5426\u8BCD": "Negative keywords",
7435
+ "\u5426\u5B9A\u8BCD": "Negative keyword",
7436
+ "\u7EA7\u522B": "Level",
7437
+ "\u53D7\u4F17": "Audience",
7438
+ "\u8BBE\u5907": "Device",
7439
+ "\u72B6\u6001": "Status",
7440
+ "\u7C7B\u578B": "Type",
7441
+ "\u5206\u6790": "Analysis",
7442
+ "\u5EFA\u8BAE": "Recommendation",
7443
+ "\u73AF\u6BD4": "vs prior",
7444
+ "\u8D26\u6237": "Account",
7445
+ "\u8D27\u5E01": "Currency",
7446
+ "\u6D88\u8017": "Spend",
7447
+ "\u5C55\u793A": "Impr.",
7448
+ "\u70B9\u51FB": "Clicks",
7449
+ "\u8F6C\u5316": "Conv.",
7450
+ "\u82B1\u8D39": "Spend",
7451
+ "\u6D88\u8D39": "Spend",
7452
+ "\u9884\u7B97": "Budget",
7453
+ "\u4F59\u989D": "Balance",
7454
+ "\u5B63\u5EA6": "Quarter",
7455
+ "\u6708\u5EA6": "Month",
7456
+ "\u5468\u671F": "Period",
7457
+ "\u672C\u671F": "This period",
7458
+ "\u4E0A\u671F": "Prior period",
7459
+ "\u4E0B\u671F": "Next period",
7460
+ "\u672C\u8282\u6682\u65E0\u6570\u636E": "No data in this section",
7461
+ "\u6682\u65E0\u6570\u636E": "No data",
7462
+ "\u672A\u63D0\u4F9B": "Not provided",
7463
+ "\u672A\u586B\u5199": "Not filled in",
7464
+ "\u672A\u547D\u540D": "Unnamed",
7465
+ "\u672A\u8BBE\u7F6E": "Not set",
7466
+ "\u672A\u542F\u7528": "Off",
7467
+ "\u5DF2\u542F\u7528": "On",
7468
+ "\u5DF2\u6682\u505C": "Paused",
7469
+ "\u5DF2\u79FB\u9664": "Removed",
7470
+ "\u6295\u653E\u4E2D": "Active",
7471
+ "\u672A\u77E5": "Unknown",
7472
+ "\u826F\u597D": "Good",
7473
+ "\u4F18\u79C0": "Excellent",
7474
+ "\u8FBE\u6807": "Met",
7475
+ "\u4E0D\u8FBE\u6807": "Not met",
7476
+ "\u5F85\u4F18\u5316": "Needs work",
7477
+ "\u6570\u636E\u6765\u6E90": "Source",
7478
+ "\u8D26\u6237\u540D\u79F0": "Account name",
7479
+ "\u8D26\u6237\u62A5\u544A": "Account report",
7480
+ "\u8D26\u6237\u603B\u89C8": "Account overview",
7481
+ "\u8D26\u6237\u7ED3\u6784": "Account structure",
7482
+ "\u5E7F\u544A\u7CFB\u5217\u62A5\u544A": "Campaign report",
7483
+ "\u641C\u7D22\u5173\u952E\u5B57\u62A5\u544A": "Keyword report",
7484
+ "\u641C\u7D22\u5B57\u8BCD\u62A5\u544A": "Search terms report",
7485
+ "\u8BBE\u5907\u62A5\u544A": "Device report",
7486
+ "\u5730\u7406\u4F4D\u7F6E\u62A5\u544A": "Geographic report",
7487
+ "\u5E73\u5747\u6BCF\u6B21\u70B9\u51FB\u8D39\u7528": "Avg. CPC",
7488
+ "\u6240\u6709\u8F6C\u5316\u6B21\u6570": "All conversions",
7489
+ "\u8F6C\u5316\u7387": "Conv. rate",
7490
+ "\u5339\u914D\u7C7B\u578B": "Match type",
7491
+ "\u5DF2\u6DFB\u52A0/\u5DF2\u6392\u9664": "Added / excluded",
7492
+ "\u51FA\u4EF7\u8C03\u6574": "Bid adjustment",
7493
+ "\u4E92\u52A8\u6B21\u6570": "Interactions",
7494
+ "\u4E92\u52A8\u7387": "Interaction rate",
7495
+ "\u5730\u7406\u4F4D\u7F6E": "Location",
7496
+ "\u641C\u7D22\u5B57\u8BCD": "Search term",
7497
+ "\u5173\u952E\u5B57": "Keyword",
7498
+ "\u603B\u8BA1": "Total",
7499
+ "\u8986\u76D6\u4EBA\u6570": "Reach",
7500
+ "\u9891\u6B21": "Frequency",
7501
+ "\u5F52\u56E0\u8BBE\u7F6E": "Attribution",
7502
+ "\u6210\u6548\u7C7B\u578B": "Result type",
7503
+ "\u5355\u6B21\u6210\u6548\u8D39\u7528": "Cost per result",
7504
+ "\u5DF2\u82B1\u8D39\u91D1\u989D": "Amount spent",
7505
+ "\u6C47\u603B\u6570\u636E": "Summary",
7506
+ "\u65E5\u8D8B\u52BF": "Daily trend",
7507
+ "\u56FD\u5BB6": "Country",
7508
+ "\u65B9\u6848\u603B\u89C8": "Plan overview",
7509
+ "1-\u5BA2\u6237\u753B\u50CF": "1-Customer profile",
7510
+ "2-\u8D26\u6237\u7ED3\u6784": "2-Account structure",
7511
+ "3-\u8868\u5355\u8BE2\u76D8C1": "3-Lead form C1",
7512
+ "\u5BA2\u6237\u753B\u50CF": "Customer profile",
7513
+ "\u5BA2\u6237\u540D\u79F0": "Customer",
7514
+ "Facebook \u4E3B\u9875": "Facebook page",
7515
+ "\u5B98\u65B9\u7F51\u7AD9": "Website",
7516
+ "\u4E3B\u8425\u4E1A\u52A1": "Business",
7517
+ "\u6838\u5FC3\u4EA7\u54C1": "Products",
7518
+ "\u6838\u5FC3\u5356\u70B9": "Selling points",
7519
+ "\u76EE\u6807\u5E02\u573A": "Target market",
7520
+ "\u8425\u9500\u76EE\u6807": "Goal",
7521
+ "\u65E5\u9884\u7B97": "Daily budget",
7522
+ "\u5E7F\u544A\u7ED3\u6784": "Ad structure",
7523
+ "\u6295\u653E\u7B56\u7565": "Strategy",
7524
+ "\u5E7F\u544A\u8D26\u6237\u7ED3\u6784": "Ad account structure",
7525
+ "\u5F85 pages \u540E\u8865": "Fill in after pages load",
7526
+ "\u8868\u5355\u8BE2\u76D8\uFF08Lead Form\uFF09": "Lead form",
7527
+ "\u8868\u5355\u8BE2\u76D8": "Lead form",
7528
+ "\u8BCA\u65AD\u65F6\u95F4": "Diagnosed at",
7529
+ "\u7F51\u7AD9\u5185\u5BB9\u53CA\u7ED3\u6784": "Site content and structure",
7530
+ "\u8425\u9500\u57FA\u7840\u4E0E\u5E7F\u544A\u843D\u5730\u9875\u8BCA\u65AD": "Marketing basics and landing pages",
7531
+ "\u7528\u6237\u4F53\u9A8C\u4E0E\u8F6C\u5316\u5206\u6790\u8BCA\u65AD": "UX and conversion",
7532
+ "\u7F51\u7AD9\u6027\u80FD\u8BCA\u65AD": "Site performance",
7533
+ "\u4F18\u52BF": "Strengths",
7534
+ "\u4E0D\u8DB3": "Gaps",
7535
+ "\u4F18\u5148\u7EA7": "Priority",
7536
+ "\u5173\u952E\u884C\u52A8": "Actions",
7537
+ "\u9884\u671F\u6548\u679C": "Expected outcome",
7538
+ "\u4F18\u5316\u91CD\u70B9": "Focus",
7539
+ "\u67D0\u67D0\u67D0\u6709\u9650\u516C\u53F8": "Sample Company Ltd.",
7540
+ "\u8BE2\u76D8/\u8F6C\u5316": "Leads / conversions",
7541
+ "Google Ads \u8D26\u6237": "Google Ads account",
7542
+ "TikTok \u5E7F\u544A\u4E3B\u8D26\u6237": "TikTok advertiser",
7543
+ "Bing \u5E7F\u544A\u8D26\u6237": "Bing Ads account",
7544
+ "Meta \u5E7F\u544A\u8D26\u6237": "Meta ad account",
7545
+ "Yandex \u5E7F\u544A\u8D26\u6237": "Yandex Ads account",
7546
+ "Facebook \u5E7F\u544A\u8D26\u6237": "Facebook ad account",
7547
+ "\u5206\u6790\u62A5\u544A": " report",
7548
+ "\u6BCF\u65E5\u8D8B\u52BF": "Daily trend",
7549
+ "\u7CFB\u5217\u8868\u73B0": "Campaigns",
7550
+ "\u4E00\u7EA7\u884C\u653F\u533A": "Admin area",
7551
+ "\u4E0B\u94BB": "drill-down",
7552
+ "\u6682\u65E0\u5EFA\u8BAE": "No recommendation",
7553
+ "\u6682\u65E0\u4F18\u5316\u5EFA\u8BAE": "No recommendation",
7554
+ "\u6682\u65E0\u5206\u6790\u7ED3\u8BBA": "No analysis",
7555
+ "\u6682\u65E0\u6838\u5FC3\u6307\u6807\u6570\u636E": "No KPI data",
7556
+ "\u6682\u65E0\u8D26\u6237\u7ED3\u6784\u6570\u636E": "No structure data",
7557
+ "CLI \u7B80\u6613\u8BCA\u65AD\uFF08Lighthouse API \u5931\u8D25\uFF09": "Simplified diagnosis (Lighthouse API failed)",
7558
+ "\u57FA\u7840\u8FBE\u6807\u3002(Basic compliance.)": "Basic compliance.",
7559
+ "\u826F\u597D\u3002(Good.)": "Good.",
7560
+ "\u6240\u6709\u6307\u6807\u5747\u672A\u8FBE\u6807\uFF0C\u9700\u8981\u5168\u9762\u4F18\u5316\u3002(All metrics failed, comprehensive optimization needed.)": "All metrics failed. A full optimization pass is needed.",
7561
+ "\u6570\u636E\u83B7\u53D6\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7AD9\u914D\u7F6E\u3002(Data fetch failed, please check website configuration.)": "Data fetch failed. Check the site configuration.",
7562
+ "\u4E0D\u5EFA\u8BAE\u6295\u653E\u5E7F\u544A\uFF0C\u9700\u8981\u5168\u9762\u6574\u6539": "Not ready for ads. Fix the site first.",
7563
+ "\u5C0F\u5E45\u4F18\u5316\u540E\u53EF\u76F4\u63A5\u6295\u653E\u5E7F\u544A": "Ready for ads after a few fixes",
7564
+ "\u53EF\u76F4\u63A5\u6295\u653E\u5E7F\u544A": "Ready for ads",
7565
+ "\u6025\u9700\u4F18\u5316\uFF01": "Needs urgent work",
7566
+ "\u9700\u8981\u91CD\u70B9\u4F18\u5316": "Needs focused work",
7567
+ "\u7F51\u7AD9\u8868\u73B0\u4F18\u79C0\uFF0C\u8FDC\u9AD8\u4E8E\u884C\u4E1A\u5E73\u5747\u503C\uFF0C\u53EF\u653E\u5FC3\u5F00\u542F\u5E7F\u544A\u6295\u653E": "The site is well above the industry average and is ready for ads",
7568
+ "\u7F51\u7AD9\u52A0\u8F7D\u901F\u5EA6\u5FEB\uFF0C\u9875\u9762\u54CD\u5E94\u53CA\u65F6\uFF0C\u8FBE\u5230\u884C\u4E1A\u9886\u5148\u6C34\u5E73": "The site loads quickly and is ahead of the industry",
7569
+ "\u7F51\u7AD9\u52A0\u8F7D\u901F\u5EA6\u8F83\u6162\uFF0C\u5EFA\u8BAE\u8FDB\u884C\u5168\u9762\u4F18\u5316\uFF0C\u63D0\u5347\u9875\u9762\u6027\u80FD": "The site is slow. Improve performance before scaling ads",
7570
+ "\u62A5\u544A\u6D4F\u89C8\u903B\u8F91": "How to read this report",
7571
+ "\u6295\u653E\u6548\u7387\u603B\u89C8": "Efficiency overview",
7572
+ "\u9884\u7B97\u82B1\u5728\u54EA\uFF08\u5E7F\u544A\u7C7B\u578B\uFF09": "Where the budget went (ad types)",
7573
+ "\u8BE2\u76D8\u4ECE\u54EA\u6765\uFF08\u533A\u57DF\u7ED3\u6784\uFF09": "Where leads came from (regions)",
7574
+ "\u56FD\u5BB6\u660E\u7EC6\uFF1A\u6D88\u8D39 \xB7 \u8BE2\u76D8 \xB7 \u6210\u672C": "Country detail: spend \xB7 leads \xB7 cost",
7575
+ "\u533A\u57DF\u4E0E\u56FD\u5BB6\uFF1A\u52A0 / \u5B88 / \u780D": "Regions and countries: add / hold / cut",
7576
+ "\u641C\u7D22\u4F18\u5316\u6253\u6CD5\uFF08\u57FA\u672C\u76D8\uFF09": "Search optimization (core)",
7577
+ "\u975E\u641C\u7D22\uFF1A\u6309\u7C7B\u578B\u6267\u884C\u6E05\u5355": "Non-search: action list by type",
7578
+ "\u5468\u8282\u594F\u4E0E\u6267\u884C\u6E05\u5355": "Weekly cadence and action list",
7579
+ "\u6708\u5EA6\u76EE\u6807\u9636\u68AF\uFF08\u5EFA\u8BAE\u6863\u82B1\u8D39\uFF0C\u4E0D\u662F\u5DF2\u6279\u9884\u7B97\uFF09": "Monthly target ladder (suggested spend, not approved budget)",
7580
+ "\u6838\u5FC3 7 \u7EF4\uFF08\u5BF9\u6807\u6295\u653E\u62A5\u8868\uFF09": "7 core metrics (vs the ads report)",
7581
+ "\u6708\u5EA6\u5168\u7EF4\u5EA6\u660E\u7EC6": "Monthly full-dimension detail",
7582
+ "\u5E7F\u544A\u63A2\u7D22\uFF08\u975E\u641C\u7D22\uFF09": "Ad exploration (non-search)",
7583
+ "\u5185\u5BB9\u6C47\u603B\uFF1A\u4EAE\u70B9\u4E0E\u6CE8\u610F": "Highlights and watch-outs",
7584
+ "\u8D26\u6237\u4FA7\u89C2\u5BDF": "Account observations",
7585
+ "\u6570\u636E\u89C4\u5212": "Planning",
7586
+ "\u6570\u636E\u8868\u73B0": "Results",
7587
+ "\u89C4\u5212\u6838\u5FC3": "Plan focus",
7588
+ "\u9884\u7B97\u600E\u4E48\u5206": "How to split the budget",
7589
+ "\u8BE2\u76D8\u6570\u91CF + \u8D28\u91CF": "Lead volume and quality",
7590
+ "\u54C1\u724C\u58F0\u91CF": "Brand reach",
7591
+ "\u914D\u6BD4\u7EAA\u5F8B": "Mix rules",
7592
+ "\u843D\u5730\u9875\u4E0E\u7EBF\u7D22\u8D28\u91CF": "Landing pages and lead quality",
7593
+ "\u53D7\u4F17\u4E0E\u8FD0\u8425\u52A8\u4F5C": "Audiences and ops",
7594
+ "\u89C6\u9891\u53E3\u5F84\uFF08\u4E0D\u662F\u672C\u6237\u89C4\u5212\uFF09": "Video notes (not this account's plan)",
7595
+ "\u4E09\u6708\u8DEF\u7EBF\u56FE": "Three-month roadmap",
7596
+ "\u5EFA\u8BAE\u6863\u6708\u5EA6\u62C6\u5206": "Suggested monthly split",
7597
+ "\u76F8\u5173\u641C\u7D22\u8BCD": "Related search terms",
7598
+ "\u6838\u5FC3\u7ED3\u8BBA": "Key takeaways",
7599
+ "\u9605\u8BFB\u8DEF\u5F84": "Reading path",
7600
+ "\u9884\u7B97\u7ED3\u6784": "Budget mix",
7601
+ "\u533A\u57DF\u7ED3\u6784": "Region mix",
7602
+ "\u56FD\u5BB6\u660E\u7EC6": "Country detail",
7603
+ "\u8D26\u6237\u89C2\u5BDF": "Account notes",
7604
+ "\u7ED3\u6784\u4F18\u5316": "Structure",
7605
+ "\u63A2\u7D22\u843D\u5730": "Exploration",
7606
+ "\u901A\u7528\u5DE1\u68C0": "Routine checks",
7607
+ "\u9884\u7B97\u914D\u6BD4": "Budget mix",
7608
+ "\u5E7F\u544A\u63A2\u7D22": "Ad exploration",
7609
+ "\u8BE2\u76D8\u4FA7\u8865\u5145": "Lead-side extras",
7610
+ "\u76EE\u6807\u5206\u5C42": "Goal tiers",
7611
+ "\u8C22\u8C22": "Thank you"
7612
+ };
7613
+ }
7614
+ });
7615
+
7616
+ // src/report-i18n/html.ts
7617
+ function localizeReportHtml(html, lang) {
7618
+ const withLang = setHtmlLang(html, lang);
7619
+ const phrased = lang === "en" ? applyEnglishPhrases(withLang) : withLang;
7620
+ return injectReportLabels(phrased, lang);
7621
+ }
7622
+ function setHtmlLang(html, lang) {
7623
+ const attr = htmlLangAttr(lang);
7624
+ if (/<html\b[^>]*\blang\s*=/i.test(html)) {
7625
+ return html.replace(/(<html\b[^>]*\blang\s*=\s*)(["'])[^"']*\2/i, `$1"${attr}"`);
7626
+ }
7627
+ return html.replace(/<html\b/i, `<html lang="${attr}"`);
7628
+ }
7629
+ function applyEnglishPhrases(html) {
7630
+ const keys = Object.keys(ENGLISH_PHRASES).filter((key) => key.length >= 2).sort((a, b) => b.length - a.length);
7631
+ let out = html;
7632
+ for (const zh of keys) {
7633
+ const en = ENGLISH_PHRASES[zh];
7634
+ if (!en || en === zh || !out.includes(zh)) continue;
7635
+ out = out.split(zh).join(en);
7636
+ }
7637
+ return out;
7638
+ }
7639
+ function injectReportLabels(html, lang) {
7640
+ const json = JSON.stringify(kpiLabels(lang)).replace(/</g, "\\u003c");
7641
+ const assignment = `window.__REPORT_LABELS__ = ${json};`;
7642
+ if (html.includes(LABELS_MARKER)) return html.replace(LABELS_MARKER, assignment);
7643
+ const tag = `<script>${assignment}</script>`;
7644
+ if (html.includes("</head>")) return html.replace("</head>", `${tag}
7645
+ </head>`);
7646
+ return `${tag}
7647
+ ${html}`;
7648
+ }
7649
+ var LABELS_MARKER;
7650
+ var init_html = __esm({
7651
+ "src/report-i18n/html.ts"() {
7652
+ "use strict";
7653
+ init_lang();
7654
+ init_labels();
7655
+ init_phrases_en();
7656
+ LABELS_MARKER = "window.__REPORT_LABELS__ = window.__REPORT_LABELS__ || null;";
7657
+ }
7658
+ });
7659
+
7232
7660
  // src/utils/google-analysis-snapshot.ts
7233
7661
  var init_google_analysis_snapshot = __esm({
7234
7662
  "src/utils/google-analysis-snapshot.ts"() {
@@ -7579,19 +8007,20 @@ var init_geo_en2zh = __esm({
7579
8007
  });
7580
8008
 
7581
8009
  // src/commands/report-period.ts
7582
- function classifyReportPeriod(current, previous) {
8010
+ function classifyReportPeriod(current, previous, lang = "zh") {
7583
8011
  const start = tryParse(current?.start);
7584
8012
  const end = tryParse(current?.end);
7585
- if (!start || !end) return customPeriod();
8013
+ if (!start || !end) return customPeriod(lang);
7586
8014
  if (isFullCalendarQuarter(start, end)) {
7587
8015
  const tag = quarterTagFromMonth(start.m);
7588
8016
  const prev = tryParse(previous?.start);
7589
8017
  const prevEnd = tryParse(previous?.end);
7590
8018
  return {
7591
8019
  current: tag,
7592
- previous: prev && prevEnd && isFullCalendarQuarter(prev, prevEnd) ? quarterTagFromMonth(prev.m) : "\u4E0A\u671F",
8020
+ previous: prev && prevEnd && isFullCalendarQuarter(prev, prevEnd) ? quarterTagFromMonth(prev.m) : priorWord(lang),
7593
8021
  next: tag === "Q4" ? "Q1" : `Q${Number(tag.slice(1)) + 1}`,
7594
- unit: "\u5B63\u5EA6"
8022
+ unit: "\u5B63\u5EA6",
8023
+ unitLabel: lang === "en" ? "Quarterly" : "\u5B63\u5EA6"
7595
8024
  };
7596
8025
  }
7597
8026
  if (start.d === 1 && start.y === end.y && start.m === end.m) {
@@ -7600,13 +8029,14 @@ function classifyReportPeriod(current, previous) {
7600
8029
  const prevSameMonth = prev && prevEnd && prev.y === prevEnd.y && prev.m === prevEnd.m;
7601
8030
  const nextMonth = start.m === 12 ? 1 : start.m + 1;
7602
8031
  return {
7603
- current: `${start.m}\u6708`,
7604
- previous: prevSameMonth ? `${prev.m}\u6708` : "\u4E0A\u671F",
7605
- next: `${nextMonth}\u6708`,
7606
- unit: "\u6708\u5EA6"
8032
+ current: monthWord(start.m, lang),
8033
+ previous: prevSameMonth ? monthWord(prev.m, lang) : priorWord(lang),
8034
+ next: monthWord(nextMonth, lang),
8035
+ unit: "\u6708\u5EA6",
8036
+ unitLabel: lang === "en" ? "Monthly" : "\u6708\u5EA6"
7607
8037
  };
7608
8038
  }
7609
- return customPeriod();
8039
+ return customPeriod(lang);
7610
8040
  }
7611
8041
  function rangeFromMeta(meta) {
7612
8042
  if (!meta || typeof meta !== "object") return null;
@@ -7614,14 +8044,24 @@ function rangeFromMeta(meta) {
7614
8044
  if (typeof record2.startDate !== "string" || typeof record2.endDate !== "string") return null;
7615
8045
  return { start: record2.startDate, end: record2.endDate };
7616
8046
  }
7617
- function attachPeriodChrome(payload) {
7618
- return { ...payload, periodChrome: classifyReportPeriod(rangeFromMeta(payload.meta)) };
8047
+ function attachPeriodChrome(payload, lang = "zh") {
8048
+ return { ...payload, periodChrome: classifyReportPeriod(rangeFromMeta(payload.meta), void 0, lang) };
7619
8049
  }
7620
8050
  function periodWord(unit, neutral) {
7621
8051
  return unit || neutral;
7622
8052
  }
7623
- function customPeriod() {
7624
- return { current: "\u672C\u671F", previous: "\u4E0A\u671F", next: "\u4E0B\u671F", unit: "" };
8053
+ function customPeriod(lang) {
8054
+ if (lang === "en") {
8055
+ return { current: "This period", previous: "Prior period", next: "Next period", unit: "", unitLabel: "" };
8056
+ }
8057
+ return { current: "\u672C\u671F", previous: "\u4E0A\u671F", next: "\u4E0B\u671F", unit: "", unitLabel: "" };
8058
+ }
8059
+ function priorWord(lang) {
8060
+ return lang === "en" ? "Prior period" : "\u4E0A\u671F";
8061
+ }
8062
+ function monthWord(month, lang) {
8063
+ if (lang === "en") return MONTHS_EN[month - 1] ?? String(month);
8064
+ return `${month}\u6708`;
7625
8065
  }
7626
8066
  function tryParse(iso) {
7627
8067
  if (!iso) return null;
@@ -7644,10 +8084,11 @@ function isFullCalendarQuarter(start, end) {
7644
8084
  function quarterTagFromMonth(month) {
7645
8085
  return `Q${Math.ceil(month / 3)}`;
7646
8086
  }
7647
- var DATE_RE2;
8087
+ var MONTHS_EN, DATE_RE2;
7648
8088
  var init_report_period = __esm({
7649
8089
  "src/commands/report-period.ts"() {
7650
8090
  "use strict";
8091
+ MONTHS_EN = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
7651
8092
  DATE_RE2 = /^(\d{4})-(\d{2})-(\d{2})/;
7652
8093
  }
7653
8094
  });
@@ -103235,19 +103676,20 @@ var init_city_to_country = __esm({
103235
103676
  });
103236
103677
 
103237
103678
  // src/commands/google-analysis/format-campaign-status.ts
103238
- function formatGoogleCampaignStatusDisplay(status) {
103679
+ function formatGoogleCampaignStatusDisplay(status, lang = "zh") {
103239
103680
  if (status == null) return "\u2014";
103240
103681
  const raw = String(status).trim();
103241
103682
  if (raw === "") return "\u2014";
103683
+ const en = lang === "en";
103242
103684
  switch (raw.toUpperCase()) {
103243
103685
  case "ENABLED":
103244
- return "\u6295\u653E\u4E2D";
103686
+ return en ? "Active" : "\u6295\u653E\u4E2D";
103245
103687
  case "PAUSED":
103246
- return "\u5DF2\u6682\u505C";
103688
+ return en ? "Paused" : "\u5DF2\u6682\u505C";
103247
103689
  case "REMOVED":
103248
- return "\u5DF2\u79FB\u9664";
103690
+ return en ? "Removed" : "\u5DF2\u79FB\u9664";
103249
103691
  default:
103250
- return "\u672A\u77E5";
103692
+ return en ? "Unknown" : "\u672A\u77E5";
103251
103693
  }
103252
103694
  }
103253
103695
  function readGoogleCampaignStatusRaw(row) {
@@ -104558,17 +105000,31 @@ function formatOkkiZhDateRange(start, end) {
104558
105000
  if (!a || !b) return [start, end].filter(Boolean).join(" - ") || "\u2014";
104559
105001
  return `${a.y}\u5E74${a.m}\u6708${a.d}\u65E5 - ${b.y}\u5E74${b.m}\u6708${b.d}\u65E5`;
104560
105002
  }
104561
- function formatOkkiReviewTitle(endDate) {
105003
+ function formatOkkiReviewTitle(endDate, lang = "zh") {
104562
105004
  const parsed = parseYmd(endDate);
104563
105005
  const month = parsed?.m ?? Number(String(endDate).split("-")[1]);
104564
- if (!Number.isFinite(month) || month < 1) return "\u6570\u636E\u590D\u76D8\uFF1A";
105006
+ if (!Number.isFinite(month) || month < 1) return lang === "en" ? "Performance review:" : "\u6570\u636E\u590D\u76D8\uFF1A";
105007
+ if (lang === "en") return `Performance review for month ${month}:`;
104565
105008
  return `${month}\u6708\u4EFD\u6570\u636E\u590D\u76D8\uFF1A`;
104566
105009
  }
104567
- function deviceLabel(value) {
105010
+ function formatOkkiDateRange(start, end, lang) {
105011
+ if (lang !== "en") return formatOkkiZhDateRange(start, end);
105012
+ const a = parseYmd(start);
105013
+ const b = parseYmd(end);
105014
+ if (!a || !b) return [start, end].filter(Boolean).join(" - ") || "\u2014";
105015
+ const iso = (p) => `${p.y}-${String(p.m).padStart(2, "0")}-${String(p.d).padStart(2, "0")}`;
105016
+ return `${iso(a)} \u2013 ${iso(b)}`;
105017
+ }
105018
+ function deviceLabel(value, lang) {
104568
105019
  const raw = str(value);
105020
+ if (lang === "en") return raw || "\u2014";
104569
105021
  return DEVICE_TYPE_ZH[raw] ?? raw;
104570
105022
  }
104571
- function targetingStatus(row) {
105023
+ function targetingStatus(row, lang = "zh") {
105024
+ if (lang === "en") {
105025
+ const raw2 = row.queryTargetingStatus ?? row.matchType;
105026
+ if (textLen(raw2)) return String(raw2).trim();
105027
+ }
104572
105028
  if (textLen(row.queryTargetingStatusZh)) return String(row.queryTargetingStatusZh).trim();
104573
105029
  const raw = str(row.queryTargetingStatus, "");
104574
105030
  return TARGETING_STATUS_ZH[raw] ?? (raw || "\u2014");
@@ -104657,10 +105113,11 @@ function campaignRows(data) {
104657
105113
  ]
104658
105114
  };
104659
105115
  }
104660
- function keywordRows(data) {
105116
+ function keywordRows(data, lang = "zh") {
105117
+ const matchOf = (row) => lang === "en" ? row.keywordMatchType || row.keywordMatchTypeZh : row.keywordMatchTypeZh || row.keywordMatchType;
104661
105118
  return sortBySpendDesc(data.tables?.keywords ?? []).map((row) => [
104662
- formatOkkiKeywordDisplay(row.keyword, row.keywordMatchTypeZh || row.keywordMatchType),
104663
- str(row.keywordMatchTypeZh || row.keywordMatchType),
105119
+ formatOkkiKeywordDisplay(row.keyword, matchOf(row)),
105120
+ str(matchOf(row)),
104664
105121
  str(row.campaignName),
104665
105122
  str(row.adGroupName),
104666
105123
  money(row.spend),
@@ -104673,16 +105130,16 @@ function keywordRows(data) {
104673
105130
  rate(row.conversionRate)
104674
105131
  ]);
104675
105132
  }
104676
- function searchTermRows(data) {
105133
+ function searchTermRows(data, lang = "zh") {
104677
105134
  return sortBySpendDesc(data.tables?.searchTerms ?? []).map((row) => [
104678
105135
  str(row.searchTermText),
104679
- str(row.matchTypeZh || row.matchType),
104680
- targetingStatus(row),
105136
+ str(lang === "en" ? row.matchType || row.matchTypeZh : row.matchTypeZh || row.matchType),
105137
+ targetingStatus(row, lang),
104681
105138
  str(row.campaignName),
104682
105139
  str(row.adGroupName),
104683
105140
  formatOkkiKeywordDisplay(
104684
105141
  row.keywordText || row.keyword,
104685
- row.keywordMatchTypeZh || row.keywordMatchType || row.matchTypeZh || row.matchType
105142
+ lang === "en" ? row.keywordMatchType || row.keywordMatchTypeZh || row.matchType || row.matchTypeZh : row.keywordMatchTypeZh || row.keywordMatchType || row.matchTypeZh || row.matchType
104686
105143
  ),
104687
105144
  count(row.impressions),
104688
105145
  count(row.clicks),
@@ -104694,11 +105151,11 @@ function searchTermRows(data) {
104694
105151
  rate(row.conversionRate)
104695
105152
  ]);
104696
105153
  }
104697
- function deviceRows(data) {
105154
+ function deviceRows(data, lang = "zh") {
104698
105155
  return sortBySpendDesc(data.tables?.devices ?? []).map((row) => {
104699
105156
  const pair = conversionsPair(row);
104700
105157
  return [
104701
- deviceLabel(row.deviceType),
105158
+ deviceLabel(row.deviceType, lang),
104702
105159
  "\u5E7F\u544A\u7CFB\u5217",
104703
105160
  str(row.campaignName),
104704
105161
  str(row.bidModifierDisplay, "\u2014"),
@@ -104714,13 +105171,15 @@ function deviceRows(data) {
104714
105171
  ];
104715
105172
  });
104716
105173
  }
104717
- function geoRows(data) {
105174
+ function geoRows(data, lang = "zh") {
104718
105175
  return sortBySpendDesc(data.tables?.geo ?? []).map((row) => {
104719
105176
  const pair = conversionsPair(row);
104720
105177
  const impressions = num6(row.impressions) ?? 0;
104721
105178
  const interactions = okkiGeoInteractionCount(row);
104722
105179
  return [
104723
- str(row.countryOrRegionZh || row.countryNameZh || row.countryOrRegion),
105180
+ str(
105181
+ lang === "en" ? row.countryOrRegion || row.countryNameZh : row.countryOrRegionZh || row.countryNameZh || row.countryOrRegion
105182
+ ),
104724
105183
  str(row.campaignName),
104725
105184
  count(row.impressions),
104726
105185
  interactions > 0 ? count(interactions) : 0,
@@ -104736,9 +105195,9 @@ function geoRows(data) {
104736
105195
  ];
104737
105196
  });
104738
105197
  }
104739
- function buildOkkiWeeklySheetDrafts(data) {
105198
+ function buildOkkiWeeklySheetDrafts(data, lang = "zh") {
104740
105199
  const meta = asRecord21(data.meta) ?? {};
104741
- const period = formatOkkiZhDateRange(str(meta.startDate, ""), str(meta.endDate, ""));
105200
+ const period = formatOkkiDateRange(str(meta.startDate, ""), str(meta.endDate, ""), lang);
104742
105201
  const campaigns = campaignRows(data);
104743
105202
  const drafts = {
104744
105203
  campaigns: {
@@ -104762,7 +105221,7 @@ function buildOkkiWeeklySheetDrafts(data) {
104762
105221
  dataRows: campaigns.body,
104763
105222
  totalRow: campaigns.total,
104764
105223
  analysisLines: analysisLines(data, "campaigns"),
104765
- reviewTitle: formatOkkiReviewTitle(str(meta.endDate, "")),
105224
+ reviewTitle: formatOkkiReviewTitle(str(meta.endDate, ""), lang),
104766
105225
  reviewLines: reviewLines(data)
104767
105226
  },
104768
105227
  keywords: {
@@ -104784,7 +105243,7 @@ function buildOkkiWeeklySheetDrafts(data) {
104784
105243
  "\u8F6C\u5316\u7387"
104785
105244
  ],
104786
105245
  colWidths: [28, 12, 24, 20, 11, 11, 10, 9, 16, 10, 14, 9],
104787
- dataRows: keywordRows(data),
105246
+ dataRows: keywordRows(data, lang),
104788
105247
  analysisLines: analysisLines(data, "keywords")
104789
105248
  },
104790
105249
  searchTerms: {
@@ -104808,7 +105267,7 @@ function buildOkkiWeeklySheetDrafts(data) {
104808
105267
  "\u8F6C\u5316\u7387"
104809
105268
  ],
104810
105269
  colWidths: [26, 12, 14, 22, 18, 22, 11, 10, 9, 16, 11, 10, 14, 9],
104811
- dataRows: searchTermRows(data),
105270
+ dataRows: searchTermRows(data, lang),
104812
105271
  analysisLines: analysisLines(data, "searchTerms")
104813
105272
  },
104814
105273
  devices: {
@@ -104831,7 +105290,7 @@ function buildOkkiWeeklySheetDrafts(data) {
104831
105290
  "\u8F6C\u5316\u7387"
104832
105291
  ],
104833
105292
  colWidths: [12, 10, 26, 12, 11, 10, 9, 16, 11, 14, 10, 14, 9],
104834
- dataRows: deviceRows(data),
105293
+ dataRows: deviceRows(data, lang),
104835
105294
  analysisLines: analysisLines(data, "devices")
104836
105295
  },
104837
105296
  geo: {
@@ -104854,15 +105313,26 @@ function buildOkkiWeeklySheetDrafts(data) {
104854
105313
  "\u8F6C\u5316\u7387"
104855
105314
  ],
104856
105315
  colWidths: [16, 26, 11, 11, 9, 11, 11, 10, 9, 14, 10, 14, 9],
104857
- dataRows: geoRows(data),
105316
+ dataRows: geoRows(data, lang),
104858
105317
  analysisLines: analysisLines(data, "geo")
104859
105318
  }
104860
105319
  };
104861
- return OKKI_SHEET_KEYS.map((key) => drafts[key]);
105320
+ return OKKI_SHEET_KEYS.map((key) => localizeOkkiDraft(drafts[key], lang));
105321
+ }
105322
+ function localizeOkkiDraft(draft, lang) {
105323
+ if (lang !== "en") return draft;
105324
+ const text3 = (value) => applyEnglishPhrases(value);
105325
+ return {
105326
+ ...draft,
105327
+ name: text3(draft.name),
105328
+ title: text3(draft.title),
105329
+ headers: draft.headers.map(text3),
105330
+ reviewTitle: draft.reviewTitle ? text3(draft.reviewTitle) : draft.reviewTitle
105331
+ };
104862
105332
  }
104863
- function writeOkkiWeeklyExcel(outPath, data) {
105333
+ function writeOkkiWeeklyExcel(outPath, data, lang = "zh") {
104864
105334
  const wb = createExcelWorkbook({ accent: "google" });
104865
- for (const draft of buildOkkiWeeklySheetDrafts(data)) {
105335
+ for (const draft of buildOkkiWeeklySheetDrafts(data, lang)) {
104866
105336
  const sheet = wb.addSheet(draft.name);
104867
105337
  const span = draft.headers.length;
104868
105338
  sheet.setColWidths(draft.colWidths);
@@ -104876,7 +105346,7 @@ function writeOkkiWeeklyExcel(outPath, data) {
104876
105346
  sheet.dataRow(draft.totalRow, { labelColumns: [0] });
104877
105347
  }
104878
105348
  sheet.blankRow();
104879
- sheet.sectionBar("\u5206\u6790", { span });
105349
+ sheet.sectionBar(lang === "en" ? "Analysis" : "\u5206\u6790", { span });
104880
105350
  for (const line of draft.analysisLines) {
104881
105351
  sheet.noteRow(line, span);
104882
105352
  }
@@ -104894,6 +105364,7 @@ var REVIEW_LABELS2, DEVICE_TYPE_ZH, TARGETING_STATUS_ZH;
104894
105364
  var init_okki_render_excel = __esm({
104895
105365
  "src/commands/google-analysis/okki-render-excel.ts"() {
104896
105366
  "use strict";
105367
+ init_html();
104897
105368
  init_xlsx_style_engine();
104898
105369
  init_okki_display();
104899
105370
  init_okki_report_content();
@@ -104960,6 +105431,7 @@ async function runGoogleAnalysisOkkiRender(opts) {
104960
105431
  `);
104961
105432
  process.exit(1);
104962
105433
  }
105434
+ const lang = readReportLangOrExit({ flag: opts.lang, meta: data.meta });
104963
105435
  const contentCheck = validateOkkiWeeklyReportContent(data);
104964
105436
  if (!contentCheck.ok) {
104965
105437
  console.error(`
@@ -104976,7 +105448,7 @@ async function runGoogleAnalysisOkkiRender(opts) {
104976
105448
  opts.out ?? path35.join(path35.dirname(dataPath), "okki-weekly-report.xlsx")
104977
105449
  );
104978
105450
  await fs30.mkdir(path35.dirname(outPath), { recursive: true });
104979
- writeOkkiWeeklyExcel(outPath, data);
105451
+ writeOkkiWeeklyExcel(outPath, data, lang);
104980
105452
  console.log(`
104981
105453
  \u2705 OKKI \u5468\u62A5\u5DF2\u751F\u6210\uFF1A${outPath}
104982
105454
  `);
@@ -104988,6 +105460,7 @@ var init_okki_render_report = __esm({
104988
105460
  init_okki_merge_snapshot();
104989
105461
  init_okki_report_content();
104990
105462
  init_okki_render_excel();
105463
+ init_lang();
104991
105464
  }
104992
105465
  });
104993
105466
 
@@ -105180,12 +105653,13 @@ function asRecord23(value) {
105180
105653
  }
105181
105654
  throw new Error("\u62A5\u544A\u6570\u636E\u987B\u4E3A JSON \u5BF9\u8C61");
105182
105655
  }
105183
- function injectReportData2(html, payload) {
105184
- const json = JSON.stringify(attachPeriodChrome(payload)).replace(/</g, "\\u003c");
105185
- if (!html.includes(INJECT_MARKER)) {
105656
+ function injectReportData2(html, payload, lang) {
105657
+ const localized = localizeReportHtml(html, lang);
105658
+ const json = JSON.stringify(attachPeriodChrome(stampMetaLang(payload, lang), lang)).replace(/</g, "\\u003c");
105659
+ if (!localized.includes(INJECT_MARKER)) {
105186
105660
  throw new Error("\u62A5\u544A\u6A21\u677F\u7F3A\u5C11\u6570\u636E\u6CE8\u5165\u951A\u70B9\uFF0C\u8BF7\u66F4\u65B0 google-period-report.html");
105187
105661
  }
105188
- return html.replace(INJECT_MARKER, `window.__GOOGLE_PERIOD_REPORT__ = ${json};`);
105662
+ return localized.replace(INJECT_MARKER, `window.__GOOGLE_PERIOD_REPORT__ = ${json};`);
105189
105663
  }
105190
105664
  async function runGoogleAnalysisRender(opts) {
105191
105665
  const dataPath = path36.resolve(opts.dataFile);
@@ -105193,6 +105667,7 @@ async function runGoogleAnalysisRender(opts) {
105193
105667
  if (opts.snapshotDir?.trim()) {
105194
105668
  data = await mergeGoogleAnalysisSnapshotIntoReport(data, path36.resolve(opts.snapshotDir));
105195
105669
  }
105670
+ const lang = readReportLangOrExit({ flag: opts.lang, meta: data.meta });
105196
105671
  const contentCheck = validateGooglePeriodReportContent(data);
105197
105672
  if (!contentCheck.ok) {
105198
105673
  console.error(`
@@ -105222,7 +105697,7 @@ async function runGoogleAnalysisRender(opts) {
105222
105697
  );
105223
105698
  const outDir = path36.dirname(outPath);
105224
105699
  await fsPromises2.mkdir(outDir, { recursive: true });
105225
- await fsPromises2.writeFile(outPath, injectReportData2(html, data), "utf8");
105700
+ await fsPromises2.writeFile(outPath, injectReportData2(html, data, lang), "utf8");
105226
105701
  const { stats } = contentCheck;
105227
105702
  const cover = periodWord(attachPeriodChrome(data).periodChrome.unit, "\u5468\u671F");
105228
105703
  console.log(`
@@ -105243,6 +105718,8 @@ var init_render_report = __esm({
105243
105718
  init_merge_snapshot();
105244
105719
  init_report_content();
105245
105720
  init_report_period();
105721
+ init_lang();
105722
+ init_html();
105246
105723
  TEMPLATE_BASENAME = "google-period-report.html";
105247
105724
  INJECT_MARKER = "window.__GOOGLE_PERIOD_REPORT__ = window.__GOOGLE_PERIOD_REPORT__ || null;";
105248
105725
  }
@@ -106829,11 +107306,12 @@ function registerGoogleAnalysisCommands(program2) {
106829
107306
  ).requiredOption("--data <file>", "Agent \u4EA7\u51FA\u7684 google-period-report.json").option(
106830
107307
  "--snapshot-dir <dir>",
106831
107308
  "\u53EF\u9009\uFF1Agoogle-analysis --json-out \u76EE\u5F55\uFF0C\u81EA\u52A8\u5408\u5E76 KPI / \u56FE\u8868 / \u8868\u683C"
106832
- ).option("--out <file>", "\u8F93\u51FA HTML \u8DEF\u5F84\uFF08\u9ED8\u8BA4\u540C --data \u76EE\u5F55\uFF09").action(async (opts) => {
107309
+ ).option("--out <file>", "\u8F93\u51FA HTML \u8DEF\u5F84\uFF08\u9ED8\u8BA4\u540C --data \u76EE\u5F55\uFF09").option("--lang <lang>", "\u7EC8\u7A3F\u8BED\u8A00\uFF1Azh \u6216 en\u3002\u8986\u76D6 JSON meta.lang\uFF1B\u90FD\u6CA1\u6709\u5219\u4E2D\u6587").action(async (opts) => {
106833
107310
  await runGoogleAnalysisRender({
106834
107311
  dataFile: opts.data,
106835
107312
  snapshotDir: opts.snapshotDir,
106836
- out: opts.out
107313
+ out: opts.out,
107314
+ lang: opts.lang
106837
107315
  });
106838
107316
  });
106839
107317
  root.command("okki-render").description(
@@ -106841,11 +107319,12 @@ function registerGoogleAnalysisCommands(program2) {
106841
107319
  ).requiredOption("--data <file>", "Agent \u4EA7\u51FA\u7684 okki-weekly-report.json").requiredOption(
106842
107320
  "--snapshot-dir <dir>",
106843
107321
  "google-analysis --json-out \u76EE\u5F55\uFF08\u987B\u542B campaigns / keywords / search-terms / campaign-device / geo-targeted\uFF09"
106844
- ).option("--out <file>", "\u8F93\u51FA xlsx \u8DEF\u5F84\uFF08\u9ED8\u8BA4\u540C --data \u76EE\u5F55\u7684 okki-weekly-report.xlsx\uFF09").action(async (opts) => {
107322
+ ).option("--out <file>", "\u8F93\u51FA xlsx \u8DEF\u5F84\uFF08\u9ED8\u8BA4\u540C --data \u76EE\u5F55\u7684 okki-weekly-report.xlsx\uFF09").option("--lang <lang>", "\u7EC8\u7A3F\u8BED\u8A00\uFF1Azh \u6216 en\u3002\u8986\u76D6 JSON meta.lang\uFF1B\u90FD\u6CA1\u6709\u5219\u4E2D\u6587").action(async (opts) => {
106845
107323
  await runGoogleAnalysisOkkiRender({
106846
107324
  dataFile: opts.data,
106847
107325
  snapshotDir: opts.snapshotDir,
106848
- out: opts.out
107326
+ out: opts.out,
107327
+ lang: opts.lang
106849
107328
  });
106850
107329
  });
106851
107330
  }
@@ -109378,15 +109857,20 @@ function cell(value) {
109378
109857
  function label(hit) {
109379
109858
  return hit.advertiserName || hit.name || "-";
109380
109859
  }
109381
- function buildBalanceScanDelivery(items3, dataIssueMessage) {
109860
+ function buildBalanceScanDelivery(items3, dataIssueMessage, lang = "zh") {
109861
+ const en = lang === "en";
109382
109862
  if (dataIssueMessage) {
109383
109863
  return { allClear: false, userDelivery: dataIssueMessage, deliveryRule: DELIVERY_RULE };
109384
109864
  }
109385
109865
  const hits = items3.filter((row) => row.hitReason !== "none");
109386
109866
  if (hits.length === 0) {
109387
- return { allClear: true, userDelivery: PATROL_ALL_CLEAR, deliveryRule: DELIVERY_RULE };
109867
+ return {
109868
+ allClear: true,
109869
+ userDelivery: en ? "No hits today" : PATROL_ALL_CLEAR,
109870
+ deliveryRule: DELIVERY_RULE
109871
+ };
109388
109872
  }
109389
- const header = "| \u8D26\u6237\u540D\u79F0 | \u8D26\u6237ID | \u4F59\u989D | \u5E01\u79CD |\n| --- | --- | ---: | --- |";
109873
+ const header = en ? "| Account | Account ID | Balance | Currency |\n| --- | --- | ---: | --- |" : "| \u8D26\u6237\u540D\u79F0 | \u8D26\u6237ID | \u4F59\u989D | \u5E01\u79CD |\n| --- | --- | ---: | --- |";
109390
109874
  const lines = hits.map((h) => {
109391
109875
  const balance = h.balance == null ? "\u2014" : String(h.balance);
109392
109876
  return `| ${cell(label(h))} | ${h.mediaCustomerId} | ${balance} | ${h.currencyCode ?? "\u2014"} |`;
@@ -109636,6 +110120,7 @@ function evaluateBalanceScanAccounts(params) {
109636
110120
  }
109637
110121
 
109638
110122
  // src/commands/balance-scan/run.ts
110123
+ init_lang();
109639
110124
  async function runBalanceScan(opts) {
109640
110125
  let config = loadConfig(opts.token);
109641
110126
  if (opts.refreshDp) {
@@ -109800,7 +110285,8 @@ async function runBalanceScan(opts) {
109800
110285
  totalReported: total ?? null,
109801
110286
  generatedAt: (/* @__PURE__ */ new Date()).toISOString()
109802
110287
  };
109803
- const agentBrief = buildBalanceScanDelivery(evaluated, dataIssue?.message ?? null);
110288
+ const lang = readReportLangOrExit({ flag: opts.lang });
110289
+ const agentBrief = buildBalanceScanDelivery(evaluated, dataIssue?.message ?? null, lang);
109804
110290
  const scanPayload = { ok: !dataIssue, data: { items: evaluated }, meta, agentBrief };
109805
110291
  const wroteJson = await emitCliJsonOrSnapshot(opts, {
109806
110292
  section: `balance-scan-${media}`,
@@ -109912,7 +110398,7 @@ function register9(program2) {
109912
110398
  "--json-out <path>",
109913
110399
  "\u843D\u76D8\uFF08\u76EE\u5F55\u6216 *.json \u6587\u4EF6\u8DEF\u5F84\uFF09\u5E76\u66F4\u65B0 cli-manifest[-<\u67E5\u8BE2id>].json\uFF1B\u76EE\u5F55\u6A21\u5F0F\u6587\u4EF6\u540D\u4E3A `<section>[-<\u67E5\u8BE2id>].json`\uFF1Bstdout \u4E00\u884C\u6458\u8981 JSON\uFF0C\u542B outlineFile\uFF08TS \u5F0F\u7C7B\u578B\u5728 `*.outline.txt`\uFF09",
109914
110400
  void 0
109915
- ).option("--refresh-dp", "\u5F3A\u5236\u91CD\u62C9 Datapermission \u540E\u518D\u626B\u63CF\uFF08\u6392\u67E5\u4F1A\u8BDD\u5F02\u5E38\uFF09", false).option("--verbose", "\u663E\u793A\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
110401
+ ).option("--refresh-dp", "\u5F3A\u5236\u91CD\u62C9 Datapermission \u540E\u518D\u626B\u63CF\uFF08\u6392\u67E5\u4F1A\u8BDD\u5F02\u5E38\uFF09", false).option("--verbose", "\u663E\u793A\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).option("--lang <lang>", "\u4EA4\u7ED9\u7528\u6237\u7684\u53E5\u5B50\u8BED\u8A00\uFF1Azh \u6216 en\uFF0C\u9ED8\u8BA4 zh").action(
109916
110402
  async (opts) => {
109917
110403
  await runBalanceScan({
109918
110404
  token: opts.token,
@@ -109928,7 +110414,8 @@ function register9(program2) {
109928
110414
  jsonOut: opts.jsonOut,
109929
110415
  refreshDp: opts.refreshDp,
109930
110416
  verbose: opts.verbose,
109931
- loginCustomerId: opts.loginCustomerId
110417
+ loginCustomerId: opts.loginCustomerId,
110418
+ lang: opts.lang
109932
110419
  });
109933
110420
  }
109934
110421
  );
@@ -110084,11 +110571,15 @@ var PATROL_ALL_CLEAR2 = "\u4ECA\u65E5\u65E0\u547D\u4E2D";
110084
110571
  function cell2(value) {
110085
110572
  return value.replace(/\|/g, "\uFF5C").replace(/\r?\n/g, " ");
110086
110573
  }
110087
- function buildBudgetCircuitDelivery(hits) {
110574
+ function buildBudgetCircuitDelivery(hits, lang = "zh") {
110088
110575
  if (hits.length === 0) {
110089
- return { allClear: true, userDelivery: PATROL_ALL_CLEAR2, deliveryRule: DELIVERY_RULE2 };
110576
+ return {
110577
+ allClear: true,
110578
+ userDelivery: lang === "en" ? "No hits today" : PATROL_ALL_CLEAR2,
110579
+ deliveryRule: DELIVERY_RULE2
110580
+ };
110090
110581
  }
110091
- const header = "| \u7CFB\u5217\u540D\u79F0 | \u7CFB\u5217ID | \u8D26\u6237ID | \u5F53\u65E5\u82B1\u8D39 | \u65E5\u9884\u7B97 |\n| --- | --- | --- | ---: | ---: |";
110582
+ const header = lang === "en" ? "| Campaign | Campaign ID | Account ID | Spend today | Daily budget |\n| --- | --- | --- | ---: | ---: |" : "| \u7CFB\u5217\u540D\u79F0 | \u7CFB\u5217ID | \u8D26\u6237ID | \u5F53\u65E5\u82B1\u8D39 | \u65E5\u9884\u7B97 |\n| --- | --- | --- | ---: | ---: |";
110092
110583
  const lines = hits.map(
110093
110584
  (h) => `| ${cell2(h.campaignName)} | ${h.campaignId} | ${h.mediaCustomerId} | ${h.spend} | ${h.budget} |`
110094
110585
  );
@@ -110098,11 +110589,15 @@ function buildBudgetCircuitDelivery(hits) {
110098
110589
  deliveryRule: DELIVERY_RULE2
110099
110590
  };
110100
110591
  }
110101
- function buildZeroConvDelivery(hits) {
110592
+ function buildZeroConvDelivery(hits, lang = "zh") {
110102
110593
  if (hits.length === 0) {
110103
- return { allClear: true, userDelivery: PATROL_ALL_CLEAR2, deliveryRule: DELIVERY_RULE2 };
110594
+ return {
110595
+ allClear: true,
110596
+ userDelivery: lang === "en" ? "No hits today" : PATROL_ALL_CLEAR2,
110597
+ deliveryRule: DELIVERY_RULE2
110598
+ };
110104
110599
  }
110105
- const header = "| \u5BF9\u8C61\u540D\u79F0 | \u5BF9\u8C61ID | \u8D26\u6237ID | \u5F53\u65E5\u82B1\u8D39 |\n| --- | --- | --- | ---: |";
110600
+ const header = lang === "en" ? "| Name | ID | Account ID | Spend today |\n| --- | --- | --- | ---: |" : "| \u5BF9\u8C61\u540D\u79F0 | \u5BF9\u8C61ID | \u8D26\u6237ID | \u5F53\u65E5\u82B1\u8D39 |\n| --- | --- | --- | ---: |";
110106
110601
  const lines = hits.map(
110107
110602
  (h) => `| ${cell2(h.objectName)} | ${h.objectId} | ${h.mediaCustomerId} | ${h.spend} |`
110108
110603
  );
@@ -110113,6 +110608,9 @@ function buildZeroConvDelivery(hits) {
110113
110608
  };
110114
110609
  }
110115
110610
 
110611
+ // src/commands/guard/budget-circuit.ts
110612
+ init_lang();
110613
+
110116
110614
  // src/commands/guard/rules.ts
110117
110615
  function moneyGte(leftYuan, rightYuan) {
110118
110616
  return Math.round(leftYuan * 100) >= Math.round(rightYuan * 100);
@@ -110245,7 +110743,7 @@ async function runBudgetCircuit(opts) {
110245
110743
  },
110246
110744
  hits,
110247
110745
  errors,
110248
- agentBrief: buildBudgetCircuitDelivery(hits)
110746
+ agentBrief: buildBudgetCircuitDelivery(hits, readReportLangOrExit({ flag: opts.lang }))
110249
110747
  };
110250
110748
  const agentBrief = payload.agentBrief;
110251
110749
  if (await emitCliJsonOrSnapshot(opts, {
@@ -110290,6 +110788,7 @@ async function runBudgetCircuit(opts) {
110290
110788
 
110291
110789
  // src/commands/guard/zero-conv.ts
110292
110790
  init_cli_json_snapshot();
110791
+ init_lang();
110293
110792
  function parseLevel(raw) {
110294
110793
  const v = (raw ?? "adgroup").toLowerCase();
110295
110794
  if (v === "adgroup" || v === "ad" || v === "both") return v;
@@ -110443,7 +110942,7 @@ async function runZeroConv(opts) {
110443
110942
  hits,
110444
110943
  skipped,
110445
110944
  errors,
110446
- agentBrief: buildZeroConvDelivery(hits)
110945
+ agentBrief: buildZeroConvDelivery(hits, readReportLangOrExit({ flag: opts.lang }))
110447
110946
  };
110448
110947
  await emitCliJsonOrSnapshot(opts, {
110449
110948
  section: "guard-zero-conv",
@@ -110498,7 +110997,7 @@ function register11(program2) {
110498
110997
  "--json-out <path>",
110499
110998
  "\u843D\u76D8\u76EE\u5F55\u6216 *.json\uFF1Bstdout \u4E00\u884C\u6458\u8981\uFF1B\u4EBA\u7C7B\u53EF\u8BFB\u8868\u59CB\u7EC8\u6253\u5370",
110500
110999
  void 0
110501
- ).option("--refresh-dp", "\u5F3A\u5236\u91CD\u62C9 Datapermission", false).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF", false).action(
111000
+ ).option("--refresh-dp", "\u5F3A\u5236\u91CD\u62C9 Datapermission", false).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF", false).option("--lang <lang>", "\u4EA4\u7ED9\u7528\u6237\u7684\u53E5\u5B50\u8BED\u8A00\uFF1Azh \u6216 en\uFF0C\u9ED8\u8BA4 zh").action(
110502
111001
  async (opts) => {
110503
111002
  await runBudgetCircuit({
110504
111003
  media: opts.media,
@@ -110512,7 +111011,8 @@ function register11(program2) {
110512
111011
  jsonOut: opts.jsonOut,
110513
111012
  refreshDp: opts.refreshDp,
110514
111013
  verbose: opts.verbose,
110515
- loginCustomerId: opts.loginCustomerId
111014
+ loginCustomerId: opts.loginCustomerId,
111015
+ lang: opts.lang
110516
111016
  });
110517
111017
  }
110518
111018
  );
@@ -110525,7 +111025,7 @@ function register11(program2) {
110525
111025
  "--fallback-target-cpa <yuan>",
110526
111026
  "\u7EC4\u65E0 targetCpa\uFF08\u6216\u521B\u610F\u7EF4\uFF09\u65F6\u4F7F\u7528\u7684\u76EE\u6807 CPA\uFF08\u5143\uFF09",
110527
111027
  (v) => parseFloat(v)
110528
- ).option("--page-size <n>", "\u8D26\u6237\u6E05\u5355\u5206\u9875\u5927\u5C0F\uFF08\u9ED8\u8BA4 200\uFF09", (v) => parseInt(v, 10)).option("--login-customer-id <id>", LOGIN_CUSTOMER_ID_OPTION_HELP).option("--max-pages <n>", "\u6700\u591A\u626B\u63CF\u9875\u6570\uFF08\u9ED8\u8BA4 20\uFF09", (v) => parseInt(v, 10)).option("-t, --token <token>", "Token\uFF08\u53EF\u9009\uFF09").option("--json-out <path>", "\u843D\u76D8\u76EE\u5F55\u6216 *.json", void 0).option("--refresh-dp", "\u5F3A\u5236\u91CD\u62C9 Datapermission", false).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF", false).action(
111028
+ ).option("--page-size <n>", "\u8D26\u6237\u6E05\u5355\u5206\u9875\u5927\u5C0F\uFF08\u9ED8\u8BA4 200\uFF09", (v) => parseInt(v, 10)).option("--login-customer-id <id>", LOGIN_CUSTOMER_ID_OPTION_HELP).option("--max-pages <n>", "\u6700\u591A\u626B\u63CF\u9875\u6570\uFF08\u9ED8\u8BA4 20\uFF09", (v) => parseInt(v, 10)).option("-t, --token <token>", "Token\uFF08\u53EF\u9009\uFF09").option("--json-out <path>", "\u843D\u76D8\u76EE\u5F55\u6216 *.json", void 0).option("--refresh-dp", "\u5F3A\u5236\u91CD\u62C9 Datapermission", false).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF", false).option("--lang <lang>", "\u4EA4\u7ED9\u7528\u6237\u7684\u53E5\u5B50\u8BED\u8A00\uFF1Azh \u6216 en\uFF0C\u9ED8\u8BA4 zh").action(
110529
111029
  async (opts) => {
110530
111030
  await runZeroConv({
110531
111031
  media: opts.media,
@@ -110540,7 +111040,8 @@ function register11(program2) {
110540
111040
  jsonOut: opts.jsonOut,
110541
111041
  refreshDp: opts.refreshDp,
110542
111042
  verbose: opts.verbose,
110543
- loginCustomerId: opts.loginCustomerId
111043
+ loginCustomerId: opts.loginCustomerId,
111044
+ lang: opts.lang
110544
111045
  });
110545
111046
  }
110546
111047
  );
@@ -120853,6 +121354,19 @@ var LAYER_TITLE = {
120853
121354
  negative_keyword: "\u5426\u5B9A\u8BCD",
120854
121355
  extension: "\u9644\u52A0\u4FE1\u606F"
120855
121356
  };
121357
+ var LAYER_TITLE_EN = {
121358
+ campaign: "Campaign",
121359
+ location: "Locations",
121360
+ excluded_location: "Excluded locations",
121361
+ adgroup: "Ad group",
121362
+ keyword: "Keywords",
121363
+ ad: "RSA copy",
121364
+ negative_keyword: "Negative keywords",
121365
+ extension: "Extensions"
121366
+ };
121367
+ function layerTitle(layer, lang) {
121368
+ return lang === "en" ? LAYER_TITLE_EN[layer] : LAYER_TITLE[layer];
121369
+ }
120856
121370
  var STATUS_MARK = {
120857
121371
  ok: "\u2705",
120858
121372
  missing: "\u274C",
@@ -120893,53 +121407,59 @@ function printPlanItemsByAdGroup(configFile, layer, layerItems) {
120893
121407
  function mdCell(text3) {
120894
121408
  return text3.replace(/\r?\n/g, " ").replace(/\|/g, "\uFF5C").trim();
120895
121409
  }
120896
- function statusLabel(status) {
121410
+ function statusLabel(status, lang = "zh") {
121411
+ if (lang === "en") {
121412
+ if (status === "ok") return "\u2705 Created";
121413
+ if (status === "missing") return "\u274C Not created";
121414
+ return "\u23ED Not compared";
121415
+ }
120897
121416
  if (status === "ok") return "\u2705 \u5DF2\u521B\u5EFA";
120898
121417
  if (status === "missing") return "\u274C \u672A\u521B\u5EFA";
120899
121418
  return "\u23ED \u672A\u6BD4\u5BF9";
120900
121419
  }
120901
- function buildCampaignCreateStatusMarkdown(result) {
121420
+ function buildCampaignCreateStatusMarkdown(result, lang = "zh") {
121421
+ const en = lang === "en";
120902
121422
  const sc = result.statusCounts;
120903
121423
  const c = result.counts;
120904
- const conclusion = result.ok ? "\u2705 **\u5168\u90E8\u6210\u529F**\uFF1A\u8BA1\u5212\u5185\u5404\u9879\u5747\u5DF2\u5728\u8D26\u6237\u751F\u6548" : `\u274C **\u90E8\u5206\u5931\u8D25**\uFF1A\u4ECD\u6709 **${sc.missing}** \u9879\u672A\u521B\u5EFA\uFF08\u53E6\u6709 ${sc.skipped} \u9879\u56E0\u7236\u7EA7\u7F3A\u5931\u672A\u6BD4\u5BF9\uFF09`;
121424
+ const conclusion = result.ok ? en ? "\u2705 **All created**: every planned item is live on the account" : "\u2705 **\u5168\u90E8\u6210\u529F**\uFF1A\u8BA1\u5212\u5185\u5404\u9879\u5747\u5DF2\u5728\u8D26\u6237\u751F\u6548" : en ? `\u274C **Partly failed**: **${sc.missing}** items were not created (${sc.skipped} skipped because a parent is missing)` : `\u274C **\u90E8\u5206\u5931\u8D25**\uFF1A\u4ECD\u6709 **${sc.missing}** \u9879\u672A\u521B\u5EFA\uFF08\u53E6\u6709 ${sc.skipped} \u9879\u56E0\u7236\u7EA7\u7F3A\u5931\u672A\u6BD4\u5BF9\uFF09`;
120905
121425
  const lines = [
120906
- `# Google \u641C\u7D22\u5E7F\u544A\u521B\u5EFA\u8BE6\u60C5`,
121426
+ en ? `# Google Search campaign creation` : `# Google \u641C\u7D22\u5E7F\u544A\u521B\u5EFA\u8BE6\u60C5`,
120907
121427
  ``,
120908
- `> \u672C\u6587\u6863\u7531 \`ad batch diff\` \u6839\u636E\u5E7F\u544A\u8BA1\u5212 JSON \u4E0E\u8D26\u6237\u5B9E\u51B5\u81EA\u52A8\u751F\u6210\uFF0C\u8BF7\u6309\u8868\u683C\u6838\u5BF9\u6BCF\u6761\u6587\u6848/\u5173\u952E\u8BCD\u7684\u521B\u5EFA\u72B6\u6001\u3002`,
121428
+ en ? `> Generated by \`ad batch diff\` from the plan JSON and the live account. Check each keyword and RSA line in the tables.` : `> \u672C\u6587\u6863\u7531 \`ad batch diff\` \u6839\u636E\u5E7F\u544A\u8BA1\u5212 JSON \u4E0E\u8D26\u6237\u5B9E\u51B5\u81EA\u52A8\u751F\u6210\uFF0C\u8BF7\u6309\u8868\u683C\u6838\u5BF9\u6BCF\u6761\u6587\u6848/\u5173\u952E\u8BCD\u7684\u521B\u5EFA\u72B6\u6001\u3002`,
120909
121429
  ``,
120910
- `## \u6982\u89C8`,
121430
+ en ? `## Overview` : `## \u6982\u89C8`,
120911
121431
  ``,
120912
- `| \u9879\u76EE | \u5185\u5BB9 |`,
121432
+ en ? `| Item | Value |` : `| \u9879\u76EE | \u5185\u5BB9 |`,
120913
121433
  `| --- | --- |`,
120914
- `| \u8D26\u6237 ID | \`${mdCell(result.accountId)}\` |`,
120915
- `| \u7CFB\u5217\u540D\u79F0 | ${mdCell(result.campaignName || "\u2014")} |`,
120916
- `| \u7CFB\u5217 ID (campaignId) | \`${mdCell(result.campaignId)}\` |`,
120917
- `| \u6279\u91CF\u4EFB\u52A1 | ${mdCell(result.batchId || "\u2014")} / ${mdCell(result.batchStatus || "\u2014")} |`,
120918
- `| \u8BA1\u5212\u914D\u7F6E | \`${mdCell(result.configFile || "\u2014")}\` |`,
120919
- `| \u7ED3\u8BBA | ${conclusion} |`,
121434
+ en ? `| Account ID | \`${mdCell(result.accountId)}\` |` : `| \u8D26\u6237 ID | \`${mdCell(result.accountId)}\` |`,
121435
+ en ? `| Campaign | ${mdCell(result.campaignName || "\u2014")} |` : `| \u7CFB\u5217\u540D\u79F0 | ${mdCell(result.campaignName || "\u2014")} |`,
121436
+ en ? `| Campaign ID | \`${mdCell(result.campaignId)}\` |` : `| \u7CFB\u5217 ID (campaignId) | \`${mdCell(result.campaignId)}\` |`,
121437
+ en ? `| Batch | ${mdCell(result.batchId || "\u2014")} / ${mdCell(result.batchStatus || "\u2014")} |` : `| \u6279\u91CF\u4EFB\u52A1 | ${mdCell(result.batchId || "\u2014")} / ${mdCell(result.batchStatus || "\u2014")} |`,
121438
+ en ? `| Plan file | \`${mdCell(result.configFile || "\u2014")}\` |` : `| \u8BA1\u5212\u914D\u7F6E | \`${mdCell(result.configFile || "\u2014")}\` |`,
121439
+ en ? `| Result | ${conclusion} |` : `| \u7ED3\u8BBA | ${conclusion} |`,
120920
121440
  ``,
120921
- `## \u6570\u91CF\u6C47\u603B`,
121441
+ en ? `## Counts` : `## \u6570\u91CF\u6C47\u603B`,
120922
121442
  ``,
120923
- `| \u5C42\u7EA7 | \u8D26\u6237\u5B9E\u51B5 | \u8BA1\u5212 |`,
121443
+ en ? `| Layer | Live | Planned |` : `| \u5C42\u7EA7 | \u8D26\u6237\u5B9E\u51B5 | \u8BA1\u5212 |`,
120924
121444
  `| --- | ---: | ---: |`,
120925
- `| \u5E7F\u544A\u7EC4 | ${c.liveAdGroups} | ${c.plannedAdGroups} |`,
120926
- `| \u5173\u952E\u8BCD | ${c.liveKeywords} | ${c.plannedKeywords} |`,
120927
- `| \u521B\u610F (RSA) | ${c.liveAds} | ${c.plannedAds} |`,
120928
- `| \u5426\u5B9A\u8BCD | ${c.liveNegativeKeywords} | ${c.plannedNegativeKeywords} |`,
120929
- `| \u9644\u52A0\u4FE1\u606F | ${c.liveExtensions} | ${c.plannedExtensions} |`,
120930
- `| \u6295\u653E\u5730\u57DF | ${c.liveLocations} | ${c.plannedLocations} |`,
120931
- `| \u6392\u9664\u5730\u57DF | ${c.liveExcludedLocations} | ${c.plannedExcludedLocations} |`,
121445
+ `| ${layerTitle("adgroup", lang)} | ${c.liveAdGroups} | ${c.plannedAdGroups} |`,
121446
+ `| ${layerTitle("keyword", lang)} | ${c.liveKeywords} | ${c.plannedKeywords} |`,
121447
+ `| ${layerTitle("ad", lang)} | ${c.liveAds} | ${c.plannedAds} |`,
121448
+ `| ${layerTitle("negative_keyword", lang)} | ${c.liveNegativeKeywords} | ${c.plannedNegativeKeywords} |`,
121449
+ `| ${layerTitle("extension", lang)} | ${c.liveExtensions} | ${c.plannedExtensions} |`,
121450
+ `| ${layerTitle("location", lang)} | ${c.liveLocations} | ${c.plannedLocations} |`,
121451
+ `| ${layerTitle("excluded_location", lang)} | ${c.liveExcludedLocations} | ${c.plannedExcludedLocations} |`,
120932
121452
  ``,
120933
- `**\u9010\u9879\u72B6\u6001\u8BA1\u6570**\uFF1A\u2705 ${sc.ok}\u3000\u274C ${sc.missing}\u3000\u23ED ${sc.skipped}\u3000\u5408\u8BA1 ${result.items.length}`,
121453
+ en ? `**Status counts**: \u2705 ${sc.ok}\u3000\u274C ${sc.missing}\u3000\u23ED ${sc.skipped}\u3000total ${result.items.length}` : `**\u9010\u9879\u72B6\u6001\u8BA1\u6570**\uFF1A\u2705 ${sc.ok}\u3000\u274C ${sc.missing}\u3000\u23ED ${sc.skipped}\u3000\u5408\u8BA1 ${result.items.length}`,
120934
121454
  ``
120935
121455
  ];
120936
121456
  const emptyInPlan = [
120937
- c.plannedExcludedLocations === 0 ? "\u6392\u9664\u5730\u57DF" : "",
120938
- c.plannedNegativeKeywords === 0 ? "\u5426\u5B9A\u8BCD" : ""
121457
+ c.plannedExcludedLocations === 0 ? layerTitle("excluded_location", lang) : "",
121458
+ c.plannedNegativeKeywords === 0 ? layerTitle("negative_keyword", lang) : ""
120939
121459
  ].filter(Boolean);
120940
121460
  if (emptyInPlan.length > 0) {
120941
121461
  lines.push(
120942
- `> \u26A0\uFE0F \u672C\u6B21\u63D0\u4EA4\u7684\u8BA1\u5212 JSON \u4E2D **${emptyInPlan.join("\u3001")}\u4E3A 0 \u6761**\uFF0C\u8D26\u6237\u5185\u4E5F\u5C31\u6CA1\u6709\u8BBE\u7F6E\u3002\u82E5\u6295\u653E\u65B9\u6848\u539F\u672C\u6709\u8FD9\u4E9B\u5185\u5BB9\uFF0C\u8BF4\u660E\u521B\u5EFA\u8FC7\u7A0B\u4E2D\u88AB\u79FB\u9664\uFF0C\u8BF7\u4E0E\u52A9\u624B\u6838\u5BF9\u662F\u5426\u9700\u8981\u8865\u4E0A\u3002`,
121462
+ en ? `> \u26A0\uFE0F The submitted plan JSON has **0 ${emptyInPlan.join(" and ")}**. The account has none either. If the brief included them, they were dropped during creation \u2014 ask the assistant to add them back.` : `> \u26A0\uFE0F \u672C\u6B21\u63D0\u4EA4\u7684\u8BA1\u5212 JSON \u4E2D **${emptyInPlan.join("\u3001")}\u4E3A 0 \u6761**\uFF0C\u8D26\u6237\u5185\u4E5F\u5C31\u6CA1\u6709\u8BBE\u7F6E\u3002\u82E5\u6295\u653E\u65B9\u6848\u539F\u672C\u6709\u8FD9\u4E9B\u5185\u5BB9\uFF0C\u8BF4\u660E\u521B\u5EFA\u8FC7\u7A0B\u4E2D\u88AB\u79FB\u9664\uFF0C\u8BF7\u4E0E\u52A9\u624B\u6838\u5BF9\u662F\u5426\u9700\u8981\u8865\u4E0A\u3002`,
120943
121463
  ``
120944
121464
  );
120945
121465
  }
@@ -120957,15 +121477,18 @@ function buildCampaignCreateStatusMarkdown(result) {
120957
121477
  }
120958
121478
  for (const [groupName, groupItems] of byGroup) {
120959
121479
  if (layer === "keyword") {
120960
- lines.push(`## \u5E7F\u544A\u7EC4\u300C${groupName}\u300D\xB7 \u5173\u952E\u8BCD`, ``);
120961
- lines.push(`| \u72B6\u6001 | \u8BA1\u5212\u5185\u5BB9 |`, `| --- | --- |`);
121480
+ lines.push(
121481
+ en ? `## Ad group \u201C${groupName}\u201D \xB7 Keywords` : `## \u5E7F\u544A\u7EC4\u300C${groupName}\u300D\xB7 \u5173\u952E\u8BCD`,
121482
+ ``
121483
+ );
121484
+ lines.push(en ? `| Status | Planned |` : `| \u72B6\u6001 | \u8BA1\u5212\u5185\u5BB9 |`, `| --- | --- |`);
120962
121485
  for (const item of groupItems) {
120963
- lines.push(`| ${statusLabel(item.status)} | ${mdCell(item.plannedContent)} |`);
121486
+ lines.push(`| ${statusLabel(item.status, lang)} | ${mdCell(item.plannedContent)} |`);
120964
121487
  }
120965
121488
  lines.push(``);
120966
121489
  continue;
120967
121490
  }
120968
- lines.push(`## \u5E7F\u544A\u7EC4\u300C${groupName}\u300D\xB7 RSA \u6587\u6848`, ``);
121491
+ lines.push(en ? `## Ad group \u201C${groupName}\u201D \xB7 RSA` : `## \u5E7F\u544A\u7EC4\u300C${groupName}\u300D\xB7 RSA \u6587\u6848`, ``);
120969
121492
  const byAd = /* @__PURE__ */ new Map();
120970
121493
  for (const item of groupItems) {
120971
121494
  const key = item.adLabel ?? "\u2014";
@@ -120974,39 +121497,47 @@ function buildCampaignCreateStatusMarkdown(result) {
120974
121497
  byAd.set(key, list);
120975
121498
  }
120976
121499
  for (const [adLabel, adItems] of byAd) {
120977
- lines.push(`### \u5E7F\u544A\u300C${adLabel}\u300D`, ``);
120978
- lines.push(`| \u72B6\u6001 | \u8BA1\u5212\u5185\u5BB9 |`, `| --- | --- |`);
121500
+ lines.push(en ? `### Ad \u201C${adLabel}\u201D` : `### \u5E7F\u544A\u300C${adLabel}\u300D`, ``);
121501
+ lines.push(en ? `| Status | Planned |` : `| \u72B6\u6001 | \u8BA1\u5212\u5185\u5BB9 |`, `| --- | --- |`);
120979
121502
  for (const item of adItems) {
120980
- lines.push(`| ${statusLabel(item.status)} | ${mdCell(item.plannedContent)} |`);
121503
+ lines.push(`| ${statusLabel(item.status, lang)} | ${mdCell(item.plannedContent)} |`);
120981
121504
  }
120982
121505
  lines.push(``);
120983
121506
  }
120984
121507
  }
120985
121508
  continue;
120986
121509
  }
120987
- lines.push(`## ${LAYER_TITLE[layer]}`, ``);
120988
- lines.push(`| \u72B6\u6001 | \u8BA1\u5212\u5185\u5BB9 |`, `| --- | --- |`);
121510
+ lines.push(`## ${layerTitle(layer, lang)}`, ``);
121511
+ lines.push(en ? `| Status | Planned |` : `| \u72B6\u6001 | \u8BA1\u5212\u5185\u5BB9 |`, `| --- | --- |`);
120989
121512
  for (const item of layerItems) {
120990
- lines.push(`| ${statusLabel(item.status)} | ${mdCell(item.plannedContent)} |`);
121513
+ lines.push(`| ${statusLabel(item.status, lang)} | ${mdCell(item.plannedContent)} |`);
120991
121514
  }
120992
121515
  lines.push(``);
120993
121516
  }
120994
121517
  if (result.missing.length > 0) {
120995
- lines.push(`## \u4ECD\u672A\u521B\u5EFA\u9879\uFF08\u987B\u5173\u6CE8\uFF09`, ``);
120996
- lines.push(`| \u5C42\u7EA7 | \u8BA1\u5212\u5185\u5BB9 | \u8BF4\u660E |`, `| --- | --- | --- |`);
121518
+ lines.push(en ? `## Still missing (needs attention)` : `## \u4ECD\u672A\u521B\u5EFA\u9879\uFF08\u987B\u5173\u6CE8\uFF09`, ``);
121519
+ lines.push(
121520
+ en ? `| Layer | Planned | Note |` : `| \u5C42\u7EA7 | \u8BA1\u5212\u5185\u5BB9 | \u8BF4\u660E |`,
121521
+ `| --- | --- | --- |`
121522
+ );
120997
121523
  for (const m of result.missing) {
120998
121524
  lines.push(
120999
- `| ${LAYER_TITLE[m.layer]} | ${mdCell(m.plannedContent)} | ${mdCell(m.summary)} |`
121525
+ `| ${layerTitle(m.layer, lang)} | ${mdCell(m.plannedContent)} | ${mdCell(m.summary)} |`
121000
121526
  );
121001
121527
  }
121002
121528
  lines.push(``);
121003
121529
  } else {
121004
- lines.push(`## \u4ECD\u672A\u521B\u5EFA\u9879`, ``, `\u65E0\u3002\u8BA1\u5212\u4E0E\u8D26\u6237\u5B9E\u51B5\u4E00\u81F4\u3002`, ``);
121530
+ lines.push(
121531
+ en ? `## Still missing` : `## \u4ECD\u672A\u521B\u5EFA\u9879`,
121532
+ ``,
121533
+ en ? `None. The plan matches the live account.` : `\u65E0\u3002\u8BA1\u5212\u4E0E\u8D26\u6237\u5B9E\u51B5\u4E00\u81F4\u3002`,
121534
+ ``
121535
+ );
121005
121536
  }
121006
121537
  lines.push(
121007
- `## \u8BF4\u660E`,
121538
+ en ? `## Notes` : `## \u8BF4\u660E`,
121008
121539
  ``,
121009
- result.ok ? `\u672C\u7CFB\u5217\u5DF2\u6309\u5E7F\u544A\u8BA1\u5212\u521B\u5EFA\u5B8C\u6210\u3002\u4E0A\u65B9\u8868\u683C\u5217\u51FA\u4E86\u6BCF\u4E00\u6761\u5173\u952E\u8BCD\u4E0E RSA \u6807\u9898/\u63CF\u8FF0\u7684\u751F\u6548\u72B6\u6001\uFF0C\u4FBF\u4E8E\u6838\u5BF9\u3002` : `\u672C\u7CFB\u5217\u5DF2\u90E8\u5206\u521B\u5EFA\u3002\u8BF7\u91CD\u70B9\u67E5\u770B\u300C\u4ECD\u672A\u521B\u5EFA\u9879\u300D\u4E0E\u8868\u683C\u4E2D\u7684 \u274C \u884C\uFF1B\u5982\u9700\u8865\u9F50\u7F3A\u5931\u5185\u5BB9\uFF0C\u53EF\u544A\u77E5\u52A9\u624B\u7EE7\u7EED\u5904\u7406\u3002`,
121540
+ result.ok ? en ? `This campaign matches the plan. The tables above show each keyword and RSA headline or description.` : `\u672C\u7CFB\u5217\u5DF2\u6309\u5E7F\u544A\u8BA1\u5212\u521B\u5EFA\u5B8C\u6210\u3002\u4E0A\u65B9\u8868\u683C\u5217\u51FA\u4E86\u6BCF\u4E00\u6761\u5173\u952E\u8BCD\u4E0E RSA \u6807\u9898/\u63CF\u8FF0\u7684\u751F\u6548\u72B6\u6001\uFF0C\u4FBF\u4E8E\u6838\u5BF9\u3002` : en ? `This campaign is only partly created. Check \u201CStill missing\u201D and the \u274C rows. Ask the assistant to fill the gaps.` : `\u672C\u7CFB\u5217\u5DF2\u90E8\u5206\u521B\u5EFA\u3002\u8BF7\u91CD\u70B9\u67E5\u770B\u300C\u4ECD\u672A\u521B\u5EFA\u9879\u300D\u4E0E\u8868\u683C\u4E2D\u7684 \u274C \u884C\uFF1B\u5982\u9700\u8865\u9F50\u7F3A\u5931\u5185\u5BB9\uFF0C\u53EF\u544A\u77E5\u52A9\u624B\u7EE7\u7EED\u5904\u7406\u3002`,
121010
121541
  ``
121011
121542
  );
121012
121543
  return lines.join("\n");
@@ -121025,8 +121556,8 @@ function resolveBatchDiffMdOutPath(opts) {
121025
121556
  }
121026
121557
  return path15.resolve(fileName);
121027
121558
  }
121028
- function writeCampaignCreateStatusMarkdown(result, mdPath) {
121029
- const reportMarkdown = buildCampaignCreateStatusMarkdown(result);
121559
+ function writeCampaignCreateStatusMarkdown(result, mdPath, lang = "zh") {
121560
+ const reportMarkdown = buildCampaignCreateStatusMarkdown(result, lang);
121030
121561
  fs10.mkdirSync(path15.dirname(mdPath), { recursive: true });
121031
121562
  fs10.writeFileSync(mdPath, reportMarkdown, "utf8");
121032
121563
  return { reportMarkdown, reportMarkdownFile: mdPath };
@@ -121194,7 +121725,7 @@ async function runCampaignBatchDiff(opts) {
121194
121725
  jsonOut: opts.jsonOut,
121195
121726
  campaignId
121196
121727
  });
121197
- const report = writeCampaignCreateStatusMarkdown(result, mdPath);
121728
+ const report = writeCampaignCreateStatusMarkdown(result, mdPath, opts.lang === "en" ? "en" : "zh");
121198
121729
  const payload = {
121199
121730
  ...result,
121200
121731
  ...report
@@ -122189,6 +122720,16 @@ function pmaxAssetGroupReportUrl(googleApiUrl, accountId, query) {
122189
122720
  const qs = query.toString();
122190
122721
  return `${base}/reporting/media-account/${accountId}/pmax/asset-groups/reports${qs ? `?${qs}` : ""}`;
122191
122722
  }
122723
+ function pmaxAssetReportUrl(googleApiUrl, accountId, query) {
122724
+ const base = googleApiUrl.replace(/\/$/, "");
122725
+ const qs = query.toString();
122726
+ return `${base}/reporting/media-account/${accountId}/pmax/asset-groups/reports/assets${qs ? `?${qs}` : ""}`;
122727
+ }
122728
+ function pmaxChannelReportUrl(googleApiUrl, accountId, query) {
122729
+ const base = googleApiUrl.replace(/\/$/, "");
122730
+ const qs = query.toString();
122731
+ return `${base}/reporting/media-account/${accountId}/pmax/campaigns/reports/channels${qs ? `?${qs}` : ""}`;
122732
+ }
122192
122733
  function pmaxGeoReportUrl(googleApiUrl, accountId, query) {
122193
122734
  const base = googleApiUrl.replace(/\/$/, "");
122194
122735
  const qs = query.toString();
@@ -124845,6 +125386,80 @@ PMax \u8D44\u4EA7\u7EC4\u6548\u679C\u62A5\u8868\uFF08${query.get("startDate")} ~
124845
125386
  console.log(" \u5B8C\u6574\u6570\u636E\u8BF7\u4F7F\u7528 --json-out\n");
124846
125387
  });
124847
125388
  }
125389
+ async function runAdPmaxReportAssets(opts) {
125390
+ const accountId = requireAccountId(opts.account);
125391
+ const query = buildReportQuery(opts);
125392
+ await withGoogleApi(opts, async (config, googleApiUrl) => {
125393
+ const url = pmaxAssetReportUrl(googleApiUrl, accountId, query);
125394
+ let data;
125395
+ try {
125396
+ const raw = await pmaxApiFetch(url, config, {}, opts.verbose);
125397
+ data = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : { data: raw };
125398
+ } catch (err) {
125399
+ console.error(
125400
+ `
125401
+ \u274C \u83B7\u53D6 PMax \u7D20\u6750\u62A5\u8868\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
125402
+ `
125403
+ );
125404
+ process.exit(1);
125405
+ }
125406
+ if (await emitPmaxResult(
125407
+ { ...opts, account: accountId },
125408
+ `ad-pmax-report-assets-${accountId}`,
125409
+ "ad pmax-report-assets",
125410
+ data,
125411
+ accountId
125412
+ )) {
125413
+ return;
125414
+ }
125415
+ const assets = data["assets"];
125416
+ const n = Array.isArray(assets) ? assets.length : 0;
125417
+ console.log(
125418
+ `
125419
+ PMax \u7D20\u6750\u62A5\u8868\uFF08${query.get("startDate")} ~ ${query.get("endDate")}\uFF0C${n} \u6761\u7D20\u6750\uFF09
125420
+ `
125421
+ );
125422
+ console.log(" \u6BD4\u7387\u6309\u672C\u8868\u5408\u8BA1\u3002\u4E0E\u7D20\u6750\u7EC4\u82B1\u8D39/\u70B9\u51FB\u4E0D\u5FC5\u76F8\u7B49\u3002\u7D20\u6750\u70B9\u51FB\u53EF\u5927\u4E8E\u7CFB\u5217\u70B9\u51FB\u3002");
125423
+ console.log(" \u5B8C\u6574\u6570\u636E\u8BF7\u4F7F\u7528 --json-out\n");
125424
+ });
125425
+ }
125426
+ async function runAdPmaxReportChannels(opts) {
125427
+ const accountId = requireAccountId(opts.account);
125428
+ const query = buildReportQuery(opts);
125429
+ await withGoogleApi(opts, async (config, googleApiUrl) => {
125430
+ const url = pmaxChannelReportUrl(googleApiUrl, accountId, query);
125431
+ let data;
125432
+ try {
125433
+ const raw = await pmaxApiFetch(url, config, {}, opts.verbose);
125434
+ data = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : { data: raw };
125435
+ } catch (err) {
125436
+ console.error(
125437
+ `
125438
+ \u274C \u83B7\u53D6 PMax \u6E20\u9053\u62A5\u8868\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}
125439
+ `
125440
+ );
125441
+ process.exit(1);
125442
+ }
125443
+ if (await emitPmaxResult(
125444
+ { ...opts, account: accountId },
125445
+ `ad-pmax-report-channels-${accountId}`,
125446
+ "ad pmax-report-channels",
125447
+ data,
125448
+ accountId
125449
+ )) {
125450
+ return;
125451
+ }
125452
+ const campaigns = data["campaigns"];
125453
+ const n = Array.isArray(campaigns) ? campaigns.length : 0;
125454
+ console.log(
125455
+ `
125456
+ PMax \u6E20\u9053\u62A5\u8868\uFF08${query.get("startDate")} ~ ${query.get("endDate")}\uFF0C${n} \u4E2A\u7CFB\u5217\uFF09
125457
+ `
125458
+ );
125459
+ console.log(" \u5408\u8BA1\u53EA\u542B\u5404\u7F51\u7EDC\u4E4B\u548C\u3002\u89C6\u9891\u3001\u5546\u54C1\u662F\u5BF9\u7167\uFF0C\u4E0D\u52A0\u5165\u5408\u8BA1\u3002");
125460
+ console.log(" \u5B8C\u6574\u6570\u636E\u8BF7\u4F7F\u7528 --json-out\n");
125461
+ });
125462
+ }
124848
125463
  async function runAdPmaxReportGeo(opts) {
124849
125464
  const accountId = requireAccountId(opts.account);
124850
125465
  const query = buildReportQuery(opts);
@@ -124880,6 +125495,9 @@ PMax \u5730\u7406\u62A5\u8868\uFF08\u6D3B\u52A8\xD7\u56FD\u5BB6\uFF0C${query.get
124880
125495
  });
124881
125496
  }
124882
125497
 
125498
+ // src/commands/ad/_register.ts
125499
+ init_lang();
125500
+
124883
125501
  // src/commands/ad/campaign-validate.ts
124884
125502
  import { writeFileSync as writeFileSync4 } from "fs";
124885
125503
  init_cli_json_snapshot();
@@ -125248,7 +125866,7 @@ function register21(program2) {
125248
125866
  ).option("--batch-id <id>", "\u6279\u91CF\u4EFB\u52A1 ID\uFF08\u6821\u9A8C\u975E Failed\uFF1BHasFailed \u65F6\u53EF\u7701\u7565 campaign-id\uFF09").option("-a, --account <id>", "\u5A92\u4F53\u8D26\u6237 ID\uFF08\u9ED8\u8BA4\u8BFB JSON \u7684 account\uFF09").option("--json-out <path>", "\u6BD4\u5BF9\u7ED3\u679C\u843D\u76D8\u8DEF\u5F84\uFF08\u542B reportMarkdown\uFF09").option(
125249
125867
  "--md-out <path>",
125250
125868
  "\u521B\u5EFA\u8BE6\u60C5 Markdown \u843D\u76D8\u8DEF\u5F84\uFF08\u9ED8\u8BA4\uFF1A\u4E0E --json-out \u540C\u76EE\u5F55\u7684 campaign-create-status-<campaignId>.md\uFF09"
125251
- ).option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
125869
+ ).option("-t, --token <token>", "Auth Token").option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).option("--lang <lang>", "\u4EA4\u7ED9\u7528\u6237\u7684\u521B\u5EFA\u8BE6\u60C5\u8BED\u8A00\uFF1Azh \u6216 en\uFF0C\u9ED8\u8BA4 zh").action(
125252
125870
  async (opts) => {
125253
125871
  await runCampaignBatchDiff({
125254
125872
  token: opts.token,
@@ -125258,7 +125876,8 @@ function register21(program2) {
125258
125876
  account: opts.account,
125259
125877
  jsonOut: opts.jsonOut,
125260
125878
  mdOut: opts.mdOut,
125261
- verbose: opts.verbose
125879
+ verbose: opts.verbose,
125880
+ lang: readReportLangOrExit({ flag: opts.lang })
125262
125881
  });
125263
125882
  }
125264
125883
  );
@@ -125704,7 +126323,7 @@ function register21(program2) {
125704
126323
  );
125705
126324
  addPmaxJsonOptions(
125706
126325
  adCmd.command("pmax-edit").description(
125707
- "\u66F4\u65B0 PMax \u6D3B\u52A8\uFF08PATCH\uFF1B\u52FF\u7528 ad campaign-edit\uFF09\n --patch-file \u89C1 assets/pmax-patch-campaign-template.json"
126326
+ "\u66F4\u65B0 PMax \u6D3B\u52A8\uFF08PATCH\uFF1B\u52FF\u7528 ad campaign-edit\uFF09\n --patch-file \u89C1 assets/pmax-patch-campaign-template.json\uFF1BassetAutomationSettings \u53EA\u6539\u63D0\u4EA4\u7684\u7C7B\u578B"
125708
126327
  ).requiredOption("-a, --account <id>", "Google \u5A92\u4F53\u5BA2\u6237 ID").requiredOption("--campaign-id <id>", "\u6D3B\u52A8 ID").option("--patch-file <path>", "PATCH JSON\uFF08budget/targetCpa \u586B\u5143\uFF09").option(
125709
126328
  "--status <status>",
125710
126329
  "2|3|4 \u6216 Enabled|Paused|Removed\uFF08Removed \u4E0D\u53EF\u6062\u590D\uFF0C\u987B\u7528\u6237\u540C\u610F\u540E\u52A0 --i-confirm\uFF09"
@@ -125790,7 +126409,7 @@ function register21(program2) {
125790
126409
  }
125791
126410
  );
125792
126411
  addPmaxJsonOptions(
125793
- adCmd.command("pmax-signals-set").description("\u5168\u91CF\u540C\u6B65\u8D44\u4EA7\u7EC4\u4FE1\u53F7\uFF08PUT .../signals\uFF1B\u6A21\u677F pmax-signals-template.json\uFF09").requiredOption("--config-file <path>", "JSON \u914D\u7F6E\u8DEF\u5F84").option("-t, --token <token>", "Auth Token")
126412
+ adCmd.command("pmax-signals-set").description("\u5168\u91CF\u540C\u6B65\u8D44\u4EA7\u7EC4\u4FE1\u53F7\uFF08PUT .../signals\uFF1B\u4E24\u7C7B\u90FD\u7A7A\u4E14\u7EC4\u91CC\u8FD8\u6709\u4FE1\u53F7\u4F1A 400\uFF09").requiredOption("--config-file <path>", "JSON \u914D\u7F6E\u8DEF\u5F84").option("-t, --token <token>", "Auth Token")
125794
126413
  ).action(
125795
126414
  async (opts) => {
125796
126415
  await runAdPmaxSignalsSet(opts);
@@ -125854,6 +126473,36 @@ function register21(program2) {
125854
126473
  });
125855
126474
  }
125856
126475
  );
126476
+ pmaxReportOpts(
126477
+ adCmd.command("pmax-report-assets").description("PMax \u5355\u7D20\u6750\u62A5\u8868\uFF08GET .../pmax/asset-groups/reports/assets\uFF09")
126478
+ ).action(
126479
+ async (opts) => {
126480
+ await runAdPmaxReportAssets({
126481
+ account: opts.account,
126482
+ startDate: opts.start,
126483
+ endDate: opts.end,
126484
+ campaignId: opts.campaignId,
126485
+ token: opts.token,
126486
+ jsonOut: opts.jsonOut,
126487
+ verbose: opts.verbose
126488
+ });
126489
+ }
126490
+ );
126491
+ pmaxReportOpts(
126492
+ adCmd.command("pmax-report-channels").description("PMax \u6E20\u9053\u62A5\u8868\uFF08GET .../pmax/campaigns/reports/channels\uFF09")
126493
+ ).action(
126494
+ async (opts) => {
126495
+ await runAdPmaxReportChannels({
126496
+ account: opts.account,
126497
+ startDate: opts.start,
126498
+ endDate: opts.end,
126499
+ campaignId: opts.campaignId,
126500
+ token: opts.token,
126501
+ jsonOut: opts.jsonOut,
126502
+ verbose: opts.verbose
126503
+ });
126504
+ }
126505
+ );
125857
126506
  pmaxReportOpts(
125858
126507
  adCmd.command("pmax-report-geo").description("PMax \u5730\u7406\u62A5\u8868 \u6D3B\u52A8\xD7\u56FD\u5BB6\uFF08GET .../pmax/campaigns/reports/geo\uFF09").option("--cost-greater <n>", "\u82B1\u8D39\u8FC7\u6EE4", (v) => parseFloat(v)).option("--click-greater <n>", "\u70B9\u51FB\u8FC7\u6EE4", (v) => parseFloat(v)).option("--conversions-greater <n>", "\u8F6C\u5316\u8FC7\u6EE4", (v) => parseFloat(v))
125859
126508
  ).action(
@@ -128015,6 +128664,8 @@ async function runMetaAdFormCreate(opts) {
128015
128664
  init_xlsx_style_engine();
128016
128665
  import fs12 from "fs";
128017
128666
  import path17 from "path";
128667
+ init_html();
128668
+ init_lang();
128018
128669
  var PENDING = "[\u5F85\u8865]";
128019
128670
  var SHEET_NAMES = ["\u65B9\u6848\u603B\u89C8", "1-\u5BA2\u6237\u753B\u50CF", "2-\u8D26\u6237\u7ED3\u6784", "3-\u8868\u5355\u8BE2\u76D8C1"];
128020
128671
  var OPS = {
@@ -128035,6 +128686,9 @@ var QUESTION_TYPE_LABEL = {
128035
128686
  JOB_TITLE: "Job Title",
128036
128687
  CUSTOM: "Custom"
128037
128688
  };
128689
+ function chrome(text3, lang) {
128690
+ return lang === "en" ? applyEnglishPhrases(text3) : text3;
128691
+ }
128038
128692
  function text2(value) {
128039
128693
  if (value == null) return PENDING;
128040
128694
  const s = String(value).trim();
@@ -128149,12 +128803,13 @@ function resolveCopyBlocks(cfg) {
128149
128803
  }
128150
128804
  return blocks;
128151
128805
  }
128152
- function writeOpsExcel(outPath, cfg) {
128806
+ function writeOpsExcel(outPath, cfg, lang = "zh") {
128153
128807
  const plan = cfg.plan ?? {};
128154
128808
  const wb = createExcelWorkbook({ accent: "1F4E79" });
128155
128809
  const adSets = resolveAdSets(cfg);
128156
128810
  const subtitle = defaultSubtitle(cfg);
128157
- const overview = wb.addSheet(SHEET_NAMES[0]);
128811
+ const sheet = (index) => chrome(SHEET_NAMES[index], lang);
128812
+ const overview = wb.addSheet(sheet(0));
128158
128813
  overview.setColWidths([20, 18, 18, 18, 18, 42]);
128159
128814
  overview.titleBar(overviewTitle(plan), {
128160
128815
  fill: null,
@@ -128163,22 +128818,22 @@ function writeOpsExcel(outPath, cfg) {
128163
128818
  hAlign: "center"
128164
128819
  });
128165
128820
  overview.blankRow();
128166
- overview.kvRow("\u5BA2\u6237\u540D\u79F0", text2(plan.customerName));
128167
- overview.kvRow("Facebook \u4E3B\u9875", text2(plan.pageUrl || "\u5F85 pages \u540E\u8865"), {
128821
+ overview.kvRow(chrome("\u5BA2\u6237\u540D\u79F0", lang), text2(plan.customerName));
128822
+ overview.kvRow(chrome("Facebook \u4E3B\u9875", lang), text2(plan.pageUrl || chrome("\u5F85 pages \u540E\u8865", lang)), {
128168
128823
  hyperlink: plan.pageUrl?.startsWith("http") ? plan.pageUrl : void 0
128169
128824
  });
128170
- overview.kvRow("\u5B98\u65B9\u7F51\u7AD9", text2(plan.siteUrl), {
128825
+ overview.kvRow(chrome("\u5B98\u65B9\u7F51\u7AD9", lang), text2(plan.siteUrl), {
128171
128826
  hyperlink: plan.siteUrl?.startsWith("http") ? plan.siteUrl : void 0
128172
128827
  });
128173
- overview.kvRow("\u4E3B\u8425\u4E1A\u52A1", text2(plan.businessOneLiner));
128174
- overview.kvRow("\u6838\u5FC3\u4EA7\u54C1", text2(plan.products));
128175
- overview.kvRow("\u6838\u5FC3\u5356\u70B9", text2(plan.sellingPoints));
128176
- overview.kvRow("\u76EE\u6807\u5E02\u573A", text2(plan.targetMarkets || joinList(cfg.adset?.countries, ", ")));
128177
- overview.kvRow("\u8425\u9500\u76EE\u6807", text2(plan.mainResult || "\u8868\u5355\u8BE2\u76D8\uFF08Lead Form\uFF09"));
128178
- overview.kvRow("\u65E5\u9884\u7B97", dailyBudgetText(cfg));
128179
- overview.kvRow("\u5E7F\u544A\u7ED3\u6784", structureSummary(cfg));
128180
- overview.kvRow("\u6295\u653E\u7B56\u7565", text2(plan.strategy || plan.landingType));
128181
- const profile = wb.addSheet(SHEET_NAMES[1]);
128828
+ overview.kvRow(chrome("\u4E3B\u8425\u4E1A\u52A1", lang), text2(plan.businessOneLiner));
128829
+ overview.kvRow(chrome("\u6838\u5FC3\u4EA7\u54C1", lang), text2(plan.products));
128830
+ overview.kvRow(chrome("\u6838\u5FC3\u5356\u70B9", lang), text2(plan.sellingPoints));
128831
+ overview.kvRow(chrome("\u76EE\u6807\u5E02\u573A", lang), text2(plan.targetMarkets || joinList(cfg.adset?.countries, ", ")));
128832
+ overview.kvRow(chrome("\u8425\u9500\u76EE\u6807", lang), text2(plan.mainResult || chrome("\u8868\u5355\u8BE2\u76D8\uFF08Lead Form\uFF09", lang)));
128833
+ overview.kvRow(chrome("\u65E5\u9884\u7B97", lang), dailyBudgetText(cfg));
128834
+ overview.kvRow(chrome("\u5E7F\u544A\u7ED3\u6784", lang), structureSummary(cfg));
128835
+ overview.kvRow(chrome("\u6295\u653E\u7B56\u7565", lang), text2(plan.strategy || plan.landingType));
128836
+ const profile = wb.addSheet(sheet(1));
128182
128837
  profile.setColWidths([26, 48, 26, 26]);
128183
128838
  profile.titleBar(profileTitle(plan), { fill: OPS.title });
128184
128839
  profile.subtitleBar(subtitle);
@@ -128220,7 +128875,7 @@ function writeOpsExcel(outPath, cfg) {
128220
128875
  profile.noteRow(
128221
128876
  plan.targeting?.disclaimer?.trim() || "\u672C\u6279\u7EC4\u5B9A\u5411\u53EA\u8BA4\u56FD\u5BB6/\u5E74\u9F84/\u6027\u522B\uFF0C\u5174\u8DA3\u8BCD\u4EC5\u4F5C\u7D20\u6750\u4E0E\u83B7\u5BA2\u8BF4\u660E"
128222
128877
  );
128223
- const structure = wb.addSheet(SHEET_NAMES[2]);
128878
+ const structure = wb.addSheet(sheet(2));
128224
128879
  structure.setColWidths([18, 44, 26, 22]);
128225
128880
  structure.titleBar(text2(plan.brand ? `\u5E7F\u544A\u8D26\u6237\u7ED3\u6784\uFF1A${plan.brand}` : "\u5E7F\u544A\u8D26\u6237\u7ED3\u6784"), {
128226
128881
  fill: OPS.title
@@ -128259,7 +128914,7 @@ function writeOpsExcel(outPath, cfg) {
128259
128914
  structure.noteRow(
128260
128915
  plan.structureNote?.trim() || `\u8BF4\u660E\uFF1AC1 \u8868\u5355\u8BE2\u76D8\u8BA1\u5212 ${adSets.length} \u7EC4\uFF1B\u672C\u6B21 create \u53EA\u843D\u5730 1 \u7CFB\u5217 \xD7 1 \u7EC4 \xD7 1 \u521B\u610F \xD7 1 \u5E7F\u544A\uFF0C\u5176\u4F59\u786E\u8BA4\u540E\u7528\u539F\u8BED\u7EED\u5EFA\u3002`
128261
128916
  );
128262
- const lead = wb.addSheet(SHEET_NAMES[3]);
128917
+ const lead = wb.addSheet(sheet(3));
128263
128918
  lead.setColWidths([16, 22, 36, 40, 16, 18]);
128264
128919
  lead.titleBar(`Campaign 1\uFF1A\u8868\u5355\u8BE2\u76D8\uFF08Leads\uFF09\uFF5C ${dailyBudgetText(cfg)}`, { fill: OPS.title });
128265
128920
  lead.subtitleBar(subtitle);
@@ -128313,7 +128968,7 @@ function writeOpsExcel(outPath, cfg) {
128313
128968
  }
128314
128969
  return wb.writeFile(outPath);
128315
128970
  }
128316
- function renderMarkdown(cfg) {
128971
+ function renderMarkdown(cfg, lang = "zh") {
128317
128972
  const plan = cfg.plan ?? {};
128318
128973
  const adSets = resolveAdSets(cfg);
128319
128974
  const questions = (cfg.form?.questions ?? []).map(
@@ -128325,13 +128980,14 @@ function renderMarkdown(cfg) {
128325
128980
 
128326
128981
  ${text2(block.text)}
128327
128982
  `).join("\n");
128983
+ const h = (label2) => chrome(label2, lang);
128328
128984
  return `# ${overviewTitle(plan)}
128329
128985
 
128330
- - \u5BA2\u6237/\u7AD9\u70B9\uFF1A${text2(plan.customerName)} + ${text2(plan.siteUrl)} + ${text2(plan.businessOneLiner)}
128331
- - \u4E3B\u7ED3\u679C\uFF1A${text2(plan.mainResult)}
128986
+ - ${h("\u5BA2\u6237\u540D\u79F0")}/${h("\u5B98\u65B9\u7F51\u7AD9")}\uFF1A${text2(plan.customerName)} + ${text2(plan.siteUrl)} + ${text2(plan.businessOneLiner)}
128987
+ - ${h("\u8425\u9500\u76EE\u6807")}\uFF1A${text2(plan.mainResult)}
128332
128988
  - \u843D\u5730\u5EFA\u8BAE\uFF1A${text2(plan.landingType)}
128333
- - \u65E5\u9884\u7B97\uFF1A${dailyBudgetText(cfg)}\u3000\u56FD\u5BB6\uFF1A${joinList(cfg.adset?.countries, ", ")}
128334
- - \u5E7F\u544A\u7ED3\u6784\uFF1A${structureSummary(cfg)}
128989
+ - ${h("\u65E5\u9884\u7B97")}\uFF1A${dailyBudgetText(cfg)}\u3000${h("\u56FD\u5BB6")}\uFF1A${joinList(cfg.adset?.countries, ", ")}
128990
+ - ${h("\u5E7F\u544A\u7ED3\u6784")}\uFF1A${structureSummary(cfg)}
128335
128991
  - \u8BA1\u5212\u72B6\u6001\uFF1A**\u5F85\u786E\u8BA4**\u3000\u672C\u6279\u53EF\u6267\u884C\uFF1AInstant Form \u7EBF\u7D22\u5E7F\u544A\uFF08\u975E Advantage+\uFF09
128336
128992
 
128337
128993
  > \u8D26\u6237\uFF1A${text2(cfg.account)}\u3000\u4E3B\u9875\uFF1A${text2(cfg.pageId)}
@@ -128391,24 +129047,25 @@ ${copies}
128391
129047
  \u8349\u6848\u4F9D\u636E\u4E3A\u516C\u5F00\u9875\u9762/\u5BA2\u6237\u6750\u6599\uFF0C\u975E\u5E7F\u544A\u540E\u53F0\u6570\u636E\u3002
128392
129048
  `;
128393
129049
  }
128394
- function writeMetaLeadPlanExcel(outPath, cfg) {
128395
- return writeOpsExcel(outPath, cfg);
129050
+ function writeMetaLeadPlanExcel(outPath, cfg, lang = "zh") {
129051
+ return writeOpsExcel(outPath, cfg, lang);
128396
129052
  }
128397
- function writeMetaLeadPlanMarkdown(outPath, cfg) {
129053
+ function writeMetaLeadPlanMarkdown(outPath, cfg, lang = "zh") {
128398
129054
  const abs = path17.resolve(outPath);
128399
129055
  fs12.mkdirSync(path17.dirname(abs), { recursive: true });
128400
- fs12.writeFileSync(abs, renderMarkdown(cfg), "utf8");
129056
+ fs12.writeFileSync(abs, renderMarkdown(cfg, lang), "utf8");
128401
129057
  return abs;
128402
129058
  }
128403
129059
  async function runMetaAdPlanRender(opts) {
128404
129060
  const cfg = loadMetaLeadCreateConfig(opts.configFile);
129061
+ const lang = readReportLangOrExit({ flag: opts.lang });
128405
129062
  const configDir = path17.dirname(path17.resolve(opts.configFile));
128406
129063
  const xlsxOut = path17.resolve(opts.out || path17.join(configDir, "meta-lead-plan.xlsx"));
128407
129064
  const mdOut = path17.resolve(
128408
129065
  opts.mdOut || path17.join(path17.dirname(xlsxOut), `${path17.parse(xlsxOut).name}.md`)
128409
129066
  );
128410
- const xlsxPath = writeMetaLeadPlanExcel(xlsxOut, cfg);
128411
- const mdPath = writeMetaLeadPlanMarkdown(mdOut, cfg);
129067
+ const xlsxPath = writeMetaLeadPlanExcel(xlsxOut, cfg, lang);
129068
+ const mdPath = writeMetaLeadPlanMarkdown(mdOut, cfg, lang);
128412
129069
  console.log(
128413
129070
  JSON.stringify({
128414
129071
  ok: true,
@@ -129196,7 +129853,7 @@ function register22(program2) {
129196
129853
  await runMetaAdCreate(opts);
129197
129854
  }
129198
129855
  );
129199
- meta.command("plan-render").description("\u6309\u8FD0\u8425\u56FA\u5B9A 4 Sheet \u6295\u5F71 Facebook \u7EBF\u7D22\u65B9\u6848 Excel + Markdown\uFF08\u4E0D\u8C03\u7F51\u5173\uFF1B\u7248\u5F0F\u9501\u6B7B\uFF09").requiredOption("--config-file <path>", "meta-lead-create JSON").option("--out <file>", "xlsx \u8F93\u51FA\u8DEF\u5F84\uFF08\u9ED8\u8BA4\u4E0E JSON \u540C\u76EE\u5F55 meta-lead-plan.xlsx\uFF09").option("--md-out <file>", "Markdown \u5BA1\u67E5\u7A3F\u8DEF\u5F84\uFF08\u9ED8\u8BA4\u4E0E xlsx \u540C\u540D .md\uFF09").action(async (opts) => {
129856
+ meta.command("plan-render").description("\u6309\u8FD0\u8425\u56FA\u5B9A 4 Sheet \u6295\u5F71 Facebook \u7EBF\u7D22\u65B9\u6848 Excel + Markdown\uFF08\u4E0D\u8C03\u7F51\u5173\uFF1B\u7248\u5F0F\u9501\u6B7B\uFF09").requiredOption("--config-file <path>", "meta-lead-create JSON").option("--out <file>", "xlsx \u8F93\u51FA\u8DEF\u5F84\uFF08\u9ED8\u8BA4\u4E0E JSON \u540C\u76EE\u5F55 meta-lead-plan.xlsx\uFF09").option("--md-out <file>", "Markdown \u5BA1\u67E5\u7A3F\u8DEF\u5F84\uFF08\u9ED8\u8BA4\u4E0E xlsx \u540C\u540D .md\uFF09").option("--lang <lang>", "\u7EC8\u7A3F\u8BED\u8A00\uFF1Azh \u6216 en\u3002\u9ED8\u8BA4\u4E2D\u6587").action(async (opts) => {
129200
129857
  await runMetaAdPlanRender(opts);
129201
129858
  });
129202
129859
  addCommon(addAccount(meta.command("account").description("\u8BFB\u5E7F\u544A\u8D26\u6237\uFF08\u4F59\u989D/\u82B1\u8D39/\u5E01\u79CD\uFF09"))).action(
@@ -134470,6 +135127,8 @@ async function runWebsiteDiagnosisPrepare(opts) {
134470
135127
  }
134471
135128
 
134472
135129
  // src/commands/website-diagnosis/render-report.ts
135130
+ init_lang();
135131
+ init_html();
134473
135132
  import fs16 from "fs";
134474
135133
  import fsPromises from "fs/promises";
134475
135134
  import path21 from "path";
@@ -134521,13 +135180,14 @@ function mergeCollectLighthouse(data, collect) {
134521
135180
  ...collect.lighthouseWarning && !data.lighthouseWarning ? { lighthouseWarning: collect.lighthouseWarning } : {}
134522
135181
  };
134523
135182
  }
134524
- function injectReportData(html, payload) {
134525
- const json = JSON.stringify(payload).replace(/</g, "\\u003c");
135183
+ function injectReportData(html, payload, lang) {
135184
+ const localized = localizeReportHtml(html, lang);
135185
+ const json = JSON.stringify(stampMetaLang(payload, lang)).replace(/</g, "\\u003c");
134526
135186
  const marker = "window.__WEBSITE_DIAGNOSIS__ = window.__WEBSITE_DIAGNOSIS__ || null;";
134527
- if (!html.includes(marker)) {
135187
+ if (!localized.includes(marker)) {
134528
135188
  throw new Error("\u62A5\u544A\u6A21\u677F\u7F3A\u5C11\u6570\u636E\u6CE8\u5165\u951A\u70B9\uFF0C\u8BF7\u66F4\u65B0 website-diagnosis-report.html");
134529
135189
  }
134530
- return html.replace(marker, `window.__WEBSITE_DIAGNOSIS__ = ${json};`);
135190
+ return localized.replace(marker, `window.__WEBSITE_DIAGNOSIS__ = ${json};`);
134531
135191
  }
134532
135192
  async function runWebsiteDiagnosisRender(opts) {
134533
135193
  const dataPath = path21.resolve(opts.dataFile);
@@ -134537,6 +135197,7 @@ async function runWebsiteDiagnosisRender(opts) {
134537
135197
  const collectRaw = await readJsonFile(path21.resolve(opts.collectFile));
134538
135198
  data = mergeCollectLighthouse(data, asRecord5(collectRaw));
134539
135199
  }
135200
+ const lang = readReportLangOrExit({ flag: opts.lang, meta: data.meta });
134540
135201
  const templatePath = websiteDiagnosisReportTemplatePath();
134541
135202
  let html;
134542
135203
  try {
@@ -134553,7 +135214,7 @@ async function runWebsiteDiagnosisRender(opts) {
134553
135214
  );
134554
135215
  const outDir = path21.dirname(outPath);
134555
135216
  await fsPromises.mkdir(outDir, { recursive: true });
134556
- await fsPromises.writeFile(outPath, injectReportData(html, data), "utf8");
135217
+ await fsPromises.writeFile(outPath, injectReportData(html, data, lang), "utf8");
134557
135218
  const runtimeSrc = websiteDiagnosisRuntimePath();
134558
135219
  const runtimeOut = path21.join(outDir, TEMPLATE_BASENAMES.runtime);
134559
135220
  let runtimeCopied = false;
@@ -134620,11 +135281,12 @@ function registerWebsiteDiagnosisCommands(program2) {
134620
135281
  jsonOut: opts.jsonOut
134621
135282
  });
134622
135283
  });
134623
- root.command("render").description("\u6839\u636E\u8BCA\u65AD JSON \u751F\u6210 HTML \u7EC8\u7A3F\uFF08\u9ED8\u8BA4\u4EA4\u4ED8\u683C\u5F0F\uFF0Cwebsite-diagnosis-report.html\uFF09").requiredOption("--data <file>", "\u8BCA\u65AD\u7ED3\u679C JSON\uFF08getWebsiteDiagnosisData \u540C\u7ED3\u6784\uFF09").option("--collect <file>", "\u53EF\u9009\uFF1Acollect \u843D\u76D8 JSON\uFF0C\u7528\u4E8E\u5408\u5E76 lighthouse").option("--out <file>", "\u8F93\u51FA HTML \u8DEF\u5F84\uFF08\u9ED8\u8BA4\u540C --data \u76EE\u5F55\uFF09").action(async (opts) => {
135284
+ root.command("render").description("\u6839\u636E\u8BCA\u65AD JSON \u751F\u6210 HTML \u7EC8\u7A3F\uFF08\u9ED8\u8BA4\u4EA4\u4ED8\u683C\u5F0F\uFF0Cwebsite-diagnosis-report.html\uFF09").requiredOption("--data <file>", "\u8BCA\u65AD\u7ED3\u679C JSON\uFF08getWebsiteDiagnosisData \u540C\u7ED3\u6784\uFF09").option("--collect <file>", "\u53EF\u9009\uFF1Acollect \u843D\u76D8 JSON\uFF0C\u7528\u4E8E\u5408\u5E76 lighthouse").option("--out <file>", "\u8F93\u51FA HTML \u8DEF\u5F84\uFF08\u9ED8\u8BA4\u540C --data \u76EE\u5F55\uFF09").option("--lang <lang>", "\u7EC8\u7A3F\u8BED\u8A00\uFF1Azh \u6216 en\u3002\u8986\u76D6 JSON meta.lang\uFF1B\u90FD\u6CA1\u6709\u5219\u4E2D\u6587").action(async (opts) => {
134624
135285
  await runWebsiteDiagnosisRender({
134625
135286
  dataFile: opts.data,
134626
135287
  collectFile: opts.collect,
134627
- out: opts.out
135288
+ out: opts.out,
135289
+ lang: opts.lang
134628
135290
  });
134629
135291
  });
134630
135292
  }
@@ -134980,12 +135642,12 @@ function countTableColumns(tableHtml) {
134980
135642
  return Math.max(th, td);
134981
135643
  }
134982
135644
  function isCompareTable(tableHtml, html, tableIndex) {
134983
- const inTable = /对比|竞品|功能|性能|维度|指标|参数/.test(tableHtml);
135645
+ const inTable = /对比|竞品|功能|性能|维度|指标|参数|competitor|compare|feature/i.test(tableHtml);
134984
135646
  if (inTable) return true;
134985
135647
  const windowStart = Math.max(0, tableIndex - 600);
134986
135648
  const windowEnd = Math.min(html.length, tableIndex + tableHtml.length + 400);
134987
135649
  const nearby = html.slice(windowStart, windowEnd);
134988
- return /对比|竞品|竞争|功能|性能|对标/.test(nearby);
135650
+ return /对比|竞品|竞争|功能|性能|对标|competitor|compare|benchmark/i.test(nearby);
134989
135651
  }
134990
135652
  function findFeatureCompareTableRows(html) {
134991
135653
  const tables = html.match(/<table[\s\S]*?<\/table>/gi) ?? [];
@@ -135002,9 +135664,9 @@ function findFeatureCompareTableRows(html) {
135002
135664
  return best;
135003
135665
  }
135004
135666
  function countPainPoints(html) {
135005
- const labeled = (html.match(/痛点\s*\d/gi) ?? []).length;
135667
+ const labeled = (html.match(/(?:痛点|pain\s*point)\s*\d/gi) ?? []).length;
135006
135668
  if (labeled >= 4) return labeled;
135007
- const section = html.match(/痛点[\s\S]{0,3000}/i)?.[0] ?? html;
135669
+ const section = html.match(/(?:痛点|pain points?)[\s\S]{0,3000}/i)?.[0] ?? html;
135008
135670
  const listItems = (section.match(/<li[^>]*>/gi) ?? []).length;
135009
135671
  return Math.max(labeled, listItems >= 4 ? 4 : listItems);
135010
135672
  }
@@ -135020,7 +135682,7 @@ function hasEchartsInlineResize(html) {
135020
135682
  return /addEventListener\s*\(\s*['"]resize['"]/.test(html) && /\.resize\s*\(/.test(html);
135021
135683
  }
135022
135684
  function countCompetitorMentions(html) {
135023
- const competitorSection = html.match(/(?:主要)?竞品[\s\S]{0,5000}/i)?.[0] ?? html.match(/竞争对手[\s\S]{0,5000}/i)?.[0] ?? html.match(/对标(?:企业|品牌|公司)[\s\S]{0,5000}/i)?.[0] ?? "";
135685
+ const competitorSection = html.match(/(?:主要)?竞品[\s\S]{0,5000}/i)?.[0] ?? html.match(/竞争对手[\s\S]{0,5000}/i)?.[0] ?? html.match(/对标(?:企业|品牌|公司)[\s\S]{0,5000}/i)?.[0] ?? html.match(/competitors?[\s\S]{0,5000}/i)?.[0] ?? "";
135024
135686
  if (competitorSection) {
135025
135687
  const listItems = (competitorSection.match(/<li[^>]*>[\s\S]*?<\/li>/gi) ?? []).filter(
135026
135688
  (li) => stripHtmlTags(li).trim().length >= 2
@@ -135061,8 +135723,10 @@ function splitMarketTokens(value) {
135061
135723
  return value.split(/[、,,/|]/).map((t) => t.trim()).filter((t) => t.length >= 2);
135062
135724
  }
135063
135725
  function hasTargetGeoBreakdown(html, ctx) {
135064
- const hasMetrics = /规模|增长|优先级|潜力|份额|CAGR|市场容量/.test(html);
135065
- const hasGeoFrame = /国家|区域|地区|市场/.test(html);
135726
+ const hasMetrics = /规模|增长|优先级|潜力|份额|CAGR|市场容量|market size|growth|priority|share/i.test(
135727
+ html
135728
+ );
135729
+ const hasGeoFrame = /国家|区域|地区|市场|country|region|market/i.test(html);
135066
135730
  if (!hasMetrics || !hasGeoFrame) return false;
135067
135731
  const targetMarket = ctx.targetMarket?.trim() ?? "";
135068
135732
  if (targetMarket) {
@@ -135079,56 +135743,58 @@ function hasTargetGeoBreakdown(html, ctx) {
135079
135743
  return regionHits >= 2;
135080
135744
  }
135081
135745
  function hasReportTitle(html, ctx) {
135082
- const hasTitle = /战略市场分析|KA客户|行业分析报告|市场分析报告|行业调查|市场调查/.test(html);
135746
+ const hasTitle = /战略市场分析|KA客户|行业分析报告|市场分析报告|行业调查|市场调查|industry analysis|market analysis|strategic market/i.test(
135747
+ html
135748
+ );
135083
135749
  if (!hasTitle) return false;
135084
135750
  return /目标市场|全球市场|行业|细分|market/i.test(html) || !!ctx.targetMarket?.trim() || !!ctx.industry?.trim() || !!ctx.customerName?.trim();
135085
135751
  }
135086
135752
  var CONTENT_RULES = [
135087
135753
  { id: "cover", test: (h, ctx) => hasReportTitle(h, ctx) },
135088
135754
  { id: "cover-kpi", test: (h) => /核心结论|metric|kpi|结论|摘要|executive summary/i.test(h) },
135089
- { id: "ch1", test: (h) => /市场与趋势诊断/.test(h) },
135755
+ { id: "ch1", test: (h) => /市场与趋势诊断|market and trend/i.test(h) },
135090
135756
  {
135091
135757
  id: "ch1-tam",
135092
- test: (h) => (/TAM|SAM|SOM/.test(h) || /市场规模/.test(h)) && /CAGR|增长率|增长趋势/.test(h)
135758
+ test: (h) => (/TAM|SAM|SOM/.test(h) || /市场规模|market size/i.test(h)) && /CAGR|增长率|增长趋势|growth/i.test(h)
135093
135759
  },
135094
- { id: "ch1-value-chain", test: (h) => /价值链|生态定位|产业链/.test(h) },
135760
+ { id: "ch1-value-chain", test: (h) => /价值链|生态定位|产业链|value chain|ecosystem/i.test(h) },
135095
135761
  { id: "ch1-target-geo", test: (h, ctx) => hasTargetGeoBreakdown(h, ctx) },
135096
- { id: "ch1-pestel", test: (h) => /PESTEL|政策|法规环境|技术趋势|经济|社会|环境/.test(h) },
135097
- { id: "ch1-forecast", test: (h) => /预测|3.?5年|未来\d/.test(h) },
135098
- { id: "ch2", test: (h) => /行业深度洞察|目标行业/.test(h) },
135762
+ { id: "ch1-pestel", test: (h) => /PESTEL|政策|法规环境|技术趋势|经济|社会|环境|policy|regulatory/i.test(h) },
135763
+ { id: "ch1-forecast", test: (h) => /预测|3.?5年|未来\d|forecast|next \d/i.test(h) },
135764
+ { id: "ch2", test: (h) => /行业深度洞察|目标行业|industry insight|target industry/i.test(h) },
135099
135765
  { id: "ch2-pain", test: (h, ctx) => ctx.painPointCount >= 4 },
135100
- { id: "ch2-compliance", test: (h) => /法规|合规/.test(h) && /<table/.test(h) },
135766
+ { id: "ch2-compliance", test: (h) => /法规|合规|regulation|compliance/i.test(h) && /<table/.test(h) },
135101
135767
  {
135102
135768
  id: "ch2-bpmn",
135103
- test: (h) => /原流程|BPMN|介入后|新流程/.test(h) && /效率|成本|提升|下降|节约/.test(h)
135769
+ test: (h) => /原流程|BPMN|介入后|新流程|as-is|to-be|current process/i.test(h) && /效率|成本|提升|下降|节约|efficiency|cost|improve/i.test(h)
135104
135770
  },
135105
- { id: "ch3", test: (h) => /目标受众|受众与场景/.test(h) },
135106
- { id: "ch3-audience", test: (h) => /用户|受众|画像|群体/.test(h) && /<table/.test(h) },
135107
- { id: "ch3-scenario", test: (h) => /应用场景|场景|差异化|用户价值|企业价值/.test(h) },
135108
- { id: "ch4", test: (h) => /竞争策略|竞争.{0,6}定位/.test(h) },
135771
+ { id: "ch3", test: (h) => /目标受众|受众与场景|target audience|audience and scenario/i.test(h) },
135772
+ { id: "ch3-audience", test: (h) => /用户|受众|画像|群体|audience|persona|segment/i.test(h) && /<table/.test(h) },
135773
+ { id: "ch3-scenario", test: (h) => /应用场景|场景|差异化|用户价值|企业价值|use case|scenario/i.test(h) },
135774
+ { id: "ch4", test: (h) => /竞争策略|竞争.{0,6}定位|competitive (strategy|positioning)/i.test(h) },
135109
135775
  { id: "ch4-competitors", test: (h, ctx) => ctx.competitorCount >= 3 },
135110
135776
  { id: "ch4-compare-table", test: (h, ctx) => ctx.competitorTableRows >= 11 },
135111
135777
  {
135112
135778
  id: "ch4-gaps",
135113
135779
  test: (h) => [
135114
- /功能空白|功能.*空白/.test(h),
135115
- /场景/.test(h),
135116
- /用户群|用户/.test(h),
135117
- /地域|地区|区域/.test(h)
135780
+ /功能空白|功能.*空白|feature gap/i.test(h),
135781
+ /场景|scenario/i.test(h),
135782
+ /用户群|用户|audience|segment/i.test(h),
135783
+ /地域|地区|区域|geo|region/i.test(h)
135118
135784
  ].filter(Boolean).length >= 3
135119
135785
  },
135120
- { id: "ch4-positioning", test: (h) => /定位陈述|定位:|核心定位|差异化/.test(h) },
135121
- { id: "ch5", test: (h) => /增长与品牌|GTM/.test(h) },
135122
- { id: "ch5-gtm", test: (h) => /试点|规模复制|生态扩展|三阶段/.test(h) && /KPI|ROI|指标/.test(h) },
135123
- { id: "ch5-brand", test: (h) => /品牌|传播|主张/.test(h) },
135124
- { id: "ch5-tactics", test: (h) => /内容营销|渠道|KOL|销售模式|营销/.test(h) },
135125
- { id: "ch6", test: (h) => /实施/.test(h) && /风险|资源/.test(h) },
135126
- { id: "ch6-milestone", test: (h) => /里程碑|阶段/.test(h) && /任务|负责人|成果|指标/.test(h) },
135127
- { id: "ch6-budget", test: (h) => /预算|资源需求|人力|分配/.test(h) },
135128
- { id: "ch6-risk", test: (h) => /风险/.test(h) && /应对|措施|矩阵/.test(h) },
135129
- { id: "appendix", test: (h) => /附录|数据来源/.test(h) },
135130
- { id: "appendix-sources", test: (h) => /数据来源|Source:|报告名称|发布日期|http/.test(h) },
135131
- { id: "appendix-methods", test: (h) => /调研方法|桌面研究|访谈|问卷/.test(h) },
135786
+ { id: "ch4-positioning", test: (h) => /定位陈述|定位:|核心定位|差异化|positioning|differentiat/i.test(h) },
135787
+ { id: "ch5", test: (h) => /增长与品牌|GTM|growth and brand/i.test(h) },
135788
+ { id: "ch5-gtm", test: (h) => /试点|规模复制|生态扩展|三阶段|pilot|scale|three phases/i.test(h) && /KPI|ROI|指标|metric/i.test(h) },
135789
+ { id: "ch5-brand", test: (h) => /品牌|传播|主张|brand|message/i.test(h) },
135790
+ { id: "ch5-tactics", test: (h) => /内容营销|渠道|KOL|销售模式|营销|content marketing|channel/i.test(h) },
135791
+ { id: "ch6", test: (h) => /实施|implementation/i.test(h) && /风险|资源|risk|resource/i.test(h) },
135792
+ { id: "ch6-milestone", test: (h) => /里程碑|阶段|milestone|phase/i.test(h) && /任务|负责人|成果|指标|owner|deliverable/i.test(h) },
135793
+ { id: "ch6-budget", test: (h) => /预算|资源需求|人力|分配|budget|headcount/i.test(h) },
135794
+ { id: "ch6-risk", test: (h) => /风险|risk/i.test(h) && /应对|措施|矩阵|mitigation|matrix/i.test(h) },
135795
+ { id: "appendix", test: (h) => /附录|数据来源|appendix|sources/i.test(h) },
135796
+ { id: "appendix-sources", test: (h) => /数据来源|Source:|报告名称|发布日期|http/i.test(h) },
135797
+ { id: "appendix-methods", test: (h) => /调研方法|桌面研究|访谈|问卷|desk research|interview|survey/i.test(h) },
135132
135798
  {
135133
135799
  id: "appendix-glossary",
135134
135800
  test: (h) => [/ROI/.test(h), /CAGR/.test(h), /GTM/.test(h), /BPMN/.test(h)].filter(Boolean).length >= 3
@@ -135193,6 +135859,7 @@ function formatMarketReportContentErrors(result) {
135193
135859
  }
135194
135860
 
135195
135861
  // src/commands/market-analysis/render-report.ts
135862
+ init_lang();
135196
135863
  function asRecord6(value) {
135197
135864
  if (value && typeof value === "object" && !Array.isArray(value)) {
135198
135865
  return value;
@@ -135236,6 +135903,16 @@ async function runMarketAnalysisRender(opts) {
135236
135903
  process.exit(1);
135237
135904
  }
135238
135905
  const reportData = asRecord6(dataRaw);
135906
+ const lang = readReportLangOrExit({ flag: opts.lang, meta: reportData.meta });
135907
+ try {
135908
+ assertHtmlLangMatches(html, lang);
135909
+ } catch (err) {
135910
+ const message = err instanceof Error ? err.message : String(err);
135911
+ console.error(`
135912
+ \u274C ${message}
135913
+ `);
135914
+ process.exit(1);
135915
+ }
135239
135916
  const contentCheck = validateMarketReportContent(html, validationContextFromReport(reportData));
135240
135917
  if (!contentCheck.ok) {
135241
135918
  console.error(`
@@ -135287,10 +135964,11 @@ function registerMarketAnalysisCommands(program2) {
135287
135964
  });
135288
135965
  }
135289
135966
  );
135290
- root.command("render").description("\u6839\u636E Agent \u751F\u6210\u7684 market-report.json\uFF08\u542B htmlContent\uFF09\u5199\u51FA HTML \u7EC8\u7A3F").requiredOption("--data <file>", "Agent \u4EA7\u51FA\u7684 market-report.json").option("--out <file>", "\u8F93\u51FA HTML \u8DEF\u5F84\uFF08\u9ED8\u8BA4\u540C --data \u76EE\u5F55\uFF09").action(async (opts) => {
135967
+ root.command("render").description("\u6839\u636E Agent \u751F\u6210\u7684 market-report.json\uFF08\u542B htmlContent\uFF09\u5199\u51FA HTML \u7EC8\u7A3F").requiredOption("--data <file>", "Agent \u4EA7\u51FA\u7684 market-report.json").option("--out <file>", "\u8F93\u51FA HTML \u8DEF\u5F84\uFF08\u9ED8\u8BA4\u540C --data \u76EE\u5F55\uFF09").option("--lang <lang>", "\u7EC8\u7A3F\u8BED\u8A00\uFF1Azh \u6216 en\u3002\u8986\u76D6 JSON meta.lang\uFF1B\u987B\u4E0E html lang \u4E00\u81F4").action(async (opts) => {
135291
135968
  await runMarketAnalysisRender({
135292
135969
  dataFile: opts.data,
135293
- out: opts.out
135970
+ out: opts.out,
135971
+ lang: opts.lang
135294
135972
  });
135295
135973
  });
135296
135974
  }
@@ -137467,6 +138145,8 @@ function applyDataRenderedCopy(data) {
137467
138145
  }
137468
138146
 
137469
138147
  // src/commands/google-ads-diagnosis/render-report.ts
138148
+ init_lang();
138149
+ init_html();
137470
138150
  function asRecord16(value) {
137471
138151
  if (value && typeof value === "object" && !Array.isArray(value)) {
137472
138152
  return value;
@@ -137590,9 +138270,10 @@ async function runGoogleAdsDiagnosisRender(opts) {
137590
138270
  `);
137591
138271
  process.exit(1);
137592
138272
  }
138273
+ const lang = readReportLangOrExit({ flag: opts.lang, meta: data.meta });
137593
138274
  let rendered;
137594
138275
  try {
137595
- rendered = injectGoogleAdsDiagnosisData(html, data);
138276
+ rendered = injectGoogleAdsDiagnosisData(localizeReportHtml(html, lang), stampMetaLang(data, lang));
137596
138277
  } catch (err) {
137597
138278
  const message = err instanceof Error ? err.message : String(err);
137598
138279
  console.error(`
@@ -137656,14 +138337,15 @@ function registerGoogleAdsDiagnosisCommands(program2) {
137656
138337
  "--no-merge-collect",
137657
138338
  "\u4E0D\u4ECE collect \u81EA\u52A8\u6062\u590D campaigns/geographic/keywords.items",
137658
138339
  false
137659
- ).option("--lenient", "\u7ED3\u6784\u8B66\u544A\uFF08\u5982\u7F3A analysis\uFF09\u65F6\u4E0D\u963B\u65AD\u6E32\u67D3", false).action(
138340
+ ).option("--lenient", "\u7ED3\u6784\u8B66\u544A\uFF08\u5982\u7F3A analysis\uFF09\u65F6\u4E0D\u963B\u65AD\u6E32\u67D3", false).option("--lang <lang>", "\u7EC8\u7A3F\u8BED\u8A00\uFF1Azh \u6216 en\u3002\u8986\u76D6 JSON meta.lang\uFF1B\u90FD\u6CA1\u6709\u5219\u4E2D\u6587").action(
137660
138341
  async (opts) => {
137661
138342
  await runGoogleAdsDiagnosisRender({
137662
138343
  dataFile: opts.data,
137663
138344
  out: opts.out,
137664
138345
  collectFile: opts.collect,
137665
138346
  noMergeCollect: opts.noMergeCollect,
137666
- lenient: opts.lenient
138347
+ lenient: opts.lenient,
138348
+ lang: opts.lang
137667
138349
  });
137668
138350
  }
137669
138351
  );
@@ -138044,15 +138726,27 @@ function utcToIso(ms) {
138044
138726
  const d = new Date(ms);
138045
138727
  return isoDate(d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate());
138046
138728
  }
138047
- function reportPeriodChrome(current, previous) {
138048
- const kind = classifyReportPeriod(current, previous);
138729
+ function reportPeriodChrome(current, previous, lang = "zh") {
138730
+ const kind = classifyReportPeriod(current, previous, lang);
138049
138731
  if (kind.unit === "\u5B63\u5EA6") {
138050
- return { ...kind, reportTitle: "Google \u5B63\u5EA6\u7ECF\u8425\u62A5\u544A", coverEn: "QUARTERLY REPORT" };
138732
+ return {
138733
+ ...kind,
138734
+ reportTitle: lang === "en" ? "Google quarterly business report" : "Google \u5B63\u5EA6\u7ECF\u8425\u62A5\u544A",
138735
+ coverEn: "QUARTERLY REPORT"
138736
+ };
138051
138737
  }
138052
138738
  if (kind.unit === "\u6708\u5EA6") {
138053
- return { ...kind, reportTitle: "Google \u6708\u5EA6\u7ECF\u8425\u62A5\u544A", coverEn: "MONTHLY REPORT" };
138739
+ return {
138740
+ ...kind,
138741
+ reportTitle: lang === "en" ? "Google monthly business report" : "Google \u6708\u5EA6\u7ECF\u8425\u62A5\u544A",
138742
+ coverEn: "MONTHLY REPORT"
138743
+ };
138054
138744
  }
138055
- return { ...kind, reportTitle: "Google \u7ECF\u8425\u62A5\u544A", coverEn: "PERFORMANCE REPORT" };
138745
+ return {
138746
+ ...kind,
138747
+ reportTitle: lang === "en" ? "Google business report" : "Google \u7ECF\u8425\u62A5\u544A",
138748
+ coverEn: "PERFORMANCE REPORT"
138749
+ };
138056
138750
  }
138057
138751
  function lastCompleteQuarter(now = /* @__PURE__ */ new Date()) {
138058
138752
  const parts = new Intl.DateTimeFormat("en-CA", {
@@ -138892,6 +139586,8 @@ import { fileURLToPath as fileURLToPath8 } from "url";
138892
139586
  var TSO_CLI_ROOT = path31.join(path31.dirname(fileURLToPath8(import.meta.url)), "../..");
138893
139587
 
138894
139588
  // src/commands/google-quarterly/render-report.ts
139589
+ init_lang();
139590
+ init_html();
138895
139591
  function asRecord18(value) {
138896
139592
  if (value && typeof value === "object" && !Array.isArray(value)) {
138897
139593
  return value;
@@ -138941,8 +139637,16 @@ async function runGoogleQuarterlyRender(opts) {
138941
139637
  }
138942
139638
  const outPath = path32.resolve(opts.out ?? path32.join(path32.dirname(dataPath), GOOGLE_QUARTERLY_HTML_BASENAME));
138943
139639
  await fs27.mkdir(path32.dirname(outPath), { recursive: true });
138944
- const periodChrome = reportPeriodChrome(readMetaRange(merged, "current"), readMetaRange(merged, "previous"));
138945
- const outHtml = injectQuarterlyReport(html, { ...merged, periodChrome });
139640
+ const lang = readReportLangOrExit({ flag: opts.lang, meta: merged.meta });
139641
+ const periodChrome = reportPeriodChrome(
139642
+ readMetaRange(merged, "current"),
139643
+ readMetaRange(merged, "previous"),
139644
+ lang
139645
+ );
139646
+ const outHtml = injectQuarterlyReport(
139647
+ localizeReportHtml(html, lang),
139648
+ stampMetaLang({ ...merged, periodChrome }, lang)
139649
+ );
138946
139650
  if (!outHtml.includes("homeCDN/echarts.min.js") || /new Chart\s*\(/.test(outHtml)) {
138947
139651
  console.error("\n\u274C \u6A21\u677F\u5FC5\u987B\u4F7F\u7528 ECharts\uFF0C\u7981\u6B62 Chart.js\u3002\n");
138948
139652
  process.exit(1);
@@ -138994,14 +139698,15 @@ function registerGoogleQuarterlyCommands(program2) {
138994
139698
  });
138995
139699
  }
138996
139700
  );
138997
- const render = root.command("render").description("\u5C06 Agent narrative + collect \u4E8B\u5B9E\u6CE8\u5165\u5B63\u5EA6 HTML").requiredOption("--data <file>", "Agent \u64B0\u5199\u7684 google-quarterly.json").option("--collect <file>", "collect JSON\uFF08\u9ED8\u8BA4\u540C\u76EE\u5F55 google-quarterly-collect.json\uFF09").option("--out <file>", "\u8F93\u51FA HTML").option("--lenient", "narrative \u7F3A\u9879\u65F6\u4ECD\u51FA HTML", false);
139701
+ const render = root.command("render").description("\u5C06 Agent narrative + collect \u4E8B\u5B9E\u6CE8\u5165\u5B63\u5EA6 HTML").requiredOption("--data <file>", "Agent \u64B0\u5199\u7684 google-quarterly.json").option("--collect <file>", "collect JSON\uFF08\u9ED8\u8BA4\u540C\u76EE\u5F55 google-quarterly-collect.json\uFF09").option("--out <file>", "\u8F93\u51FA HTML").option("--lenient", "narrative \u7F3A\u9879\u65F6\u4ECD\u51FA HTML", false).option("--lang <lang>", "\u7EC8\u7A3F\u8BED\u8A00\uFF1Azh \u6216 en\u3002\u8986\u76D6 JSON meta.lang\uFF1B\u90FD\u6CA1\u6709\u5219\u4E2D\u6587");
138998
139702
  render.action(
138999
139703
  async (opts) => {
139000
139704
  await runGoogleQuarterlyRender({
139001
139705
  dataFile: opts.data,
139002
139706
  collectFile: opts.collect,
139003
139707
  out: opts.out,
139004
- lenient: opts.lenient
139708
+ lenient: opts.lenient,
139709
+ lang: opts.lang
139005
139710
  });
139006
139711
  }
139007
139712
  );
@@ -139412,6 +140117,9 @@ async function mergeFacebookSnapshotIntoReport(payload, snapshotDir) {
139412
140117
  };
139413
140118
  }
139414
140119
 
140120
+ // src/commands/facebook-analysis/render-excel.ts
140121
+ init_html();
140122
+
139415
140123
  // src/commands/facebook-analysis/write-xlsx.ts
139416
140124
  init_xlsx_style_engine();
139417
140125
  var MIN_COL_WIDTH = 10;
@@ -139620,8 +140328,18 @@ function buildMetaPeriodExcelSheets(data) {
139620
140328
  { name: "\u53D7\u4F17", rows: audienceRows }
139621
140329
  ];
139622
140330
  }
139623
- function writeMetaPeriodExcel(outPath, data) {
139624
- writeSimpleXlsx(outPath, buildMetaPeriodExcelSheets(data));
140331
+ function chrome2(text3, lang) {
140332
+ return lang === "en" ? applyEnglishPhrases(text3) : text3;
140333
+ }
140334
+ function writeMetaPeriodExcel(outPath, data, lang = "zh") {
140335
+ const sheets = buildMetaPeriodExcelSheets(data).map((sheet) => ({
140336
+ ...sheet,
140337
+ name: chrome2(sheet.name, lang),
140338
+ rows: sheet.rows.map(
140339
+ (row, index) => index === 0 ? row.map((cell5) => typeof cell5 === "string" ? chrome2(cell5, lang) : cell5) : row
140340
+ )
140341
+ }));
140342
+ writeSimpleXlsx(outPath, sheets);
139625
140343
  }
139626
140344
 
139627
140345
  // src/commands/facebook-analysis/report-content.ts
@@ -139911,6 +140629,8 @@ function formatMetaPeriodReportErrors(result) {
139911
140629
 
139912
140630
  // src/commands/facebook-analysis/render-report.ts
139913
140631
  init_report_period();
140632
+ init_lang();
140633
+ init_html();
139914
140634
  var TEMPLATE_BASENAMES2 = {
139915
140635
  html: "meta-period-report.html"
139916
140636
  };
@@ -139959,12 +140679,13 @@ function asRecord27(value) {
139959
140679
  }
139960
140680
  throw new Error("\u62A5\u544A\u6570\u636E\u987B\u4E3A JSON \u5BF9\u8C61");
139961
140681
  }
139962
- function injectReportData3(html, payload) {
139963
- const json = JSON.stringify(attachPeriodChrome(payload)).replace(/</g, "\\u003c");
139964
- if (!html.includes(INJECT_MARKER2)) {
140682
+ function injectReportData3(html, payload, lang) {
140683
+ const localized = localizeReportHtml(html, lang);
140684
+ const json = JSON.stringify(attachPeriodChrome(stampMetaLang(payload, lang), lang)).replace(/</g, "\\u003c");
140685
+ if (!localized.includes(INJECT_MARKER2)) {
139965
140686
  throw new Error("\u62A5\u544A\u6A21\u677F\u7F3A\u5C11\u6570\u636E\u6CE8\u5165\u951A\u70B9\uFF0C\u8BF7\u66F4\u65B0 meta-period-report.html");
139966
140687
  }
139967
- return html.replace(INJECT_MARKER2, `window.__META_PERIOD_REPORT__ = ${json};`);
140688
+ return localized.replace(INJECT_MARKER2, `window.__META_PERIOD_REPORT__ = ${json};`);
139968
140689
  }
139969
140690
  function defaultOutPath(dataPath, format, out) {
139970
140691
  if (out?.trim()) {
@@ -139987,6 +140708,7 @@ async function runFacebookAnalysisRender(opts) {
139987
140708
  data = await mergeFacebookSnapshotIntoReport(data, path42.resolve(opts.snapshotDir));
139988
140709
  }
139989
140710
  localizeFacebookReportLabels(data);
140711
+ const lang = readReportLangOrExit({ flag: opts.lang, meta: data.meta });
139990
140712
  const contentCheck = validateMetaPeriodReportContent(data);
139991
140713
  if (!contentCheck.ok) {
139992
140714
  console.error(`
@@ -140005,7 +140727,7 @@ async function runFacebookAnalysisRender(opts) {
140005
140727
  const outPath = defaultOutPath(dataPath, format, opts.out);
140006
140728
  await fsPromises3.mkdir(path42.dirname(outPath), { recursive: true });
140007
140729
  if (format === "xlsx") {
140008
- writeMetaPeriodExcel(outPath, data);
140730
+ writeMetaPeriodExcel(outPath, data, lang);
140009
140731
  } else {
140010
140732
  const templatePath = metaPeriodReportTemplatePath();
140011
140733
  let html;
@@ -140018,7 +140740,7 @@ async function runFacebookAnalysisRender(opts) {
140018
140740
  `);
140019
140741
  process.exit(1);
140020
140742
  }
140021
- await fsPromises3.writeFile(outPath, injectReportData3(html, data), "utf8");
140743
+ await fsPromises3.writeFile(outPath, injectReportData3(html, data, lang), "utf8");
140022
140744
  }
140023
140745
  written.push(outPath);
140024
140746
  }
@@ -140389,6 +141111,8 @@ function formatMetaDiagnosisReportErrors(result) {
140389
141111
  }
140390
141112
 
140391
141113
  // src/commands/facebook-analysis/diagnosis-render-report.ts
141114
+ init_lang();
141115
+ init_html();
140392
141116
  var TEMPLATE_BASENAME2 = "meta-account-diagnosis-report.html";
140393
141117
  var INJECT_MARKER3 = "window.__META_DIAGNOSIS_REPORT__ = window.__META_DIAGNOSIS_REPORT__ || null;";
140394
141118
  function resolveSkillTemplatePath5(basename13) {
@@ -140420,12 +141144,13 @@ function asRecord30(value) {
140420
141144
  }
140421
141145
  throw new Error("\u62A5\u544A\u6570\u636E\u987B\u4E3A JSON \u5BF9\u8C61");
140422
141146
  }
140423
- function injectReportData4(html, payload) {
140424
- const json = JSON.stringify(payload).replace(/</g, "\\u003c");
140425
- if (!html.includes(INJECT_MARKER3)) {
141147
+ function injectReportData4(html, payload, lang) {
141148
+ const localized = localizeReportHtml(html, lang);
141149
+ const json = JSON.stringify(stampMetaLang(payload, lang)).replace(/</g, "\\u003c");
141150
+ if (!localized.includes(INJECT_MARKER3)) {
140426
141151
  throw new Error("\u62A5\u544A\u6A21\u677F\u7F3A\u5C11\u6570\u636E\u6CE8\u5165\u951A\u70B9\uFF0C\u8BF7\u66F4\u65B0 meta-account-diagnosis-report.html");
140427
141152
  }
140428
- return html.replace(INJECT_MARKER3, `window.__META_DIAGNOSIS_REPORT__ = ${json};`);
141153
+ return localized.replace(INJECT_MARKER3, `window.__META_DIAGNOSIS_REPORT__ = ${json};`);
140429
141154
  }
140430
141155
  async function runFacebookAnalysisDiagnosisRender(opts) {
140431
141156
  const dataPath = path44.resolve(opts.dataFile);
@@ -140433,6 +141158,7 @@ async function runFacebookAnalysisDiagnosisRender(opts) {
140433
141158
  if (opts.snapshotDir?.trim()) {
140434
141159
  data = await mergeMetaDiagnosisSnapshotIntoReport(data, path44.resolve(opts.snapshotDir));
140435
141160
  }
141161
+ const lang = readReportLangOrExit({ flag: opts.lang, meta: data.meta });
140436
141162
  const contentCheck = validateMetaDiagnosisReportContent(data);
140437
141163
  if (!contentCheck.ok) {
140438
141164
  console.error(`
@@ -140462,7 +141188,7 @@ async function runFacebookAnalysisDiagnosisRender(opts) {
140462
141188
  );
140463
141189
  const outDir = path44.dirname(outPath);
140464
141190
  await fsPromises4.mkdir(outDir, { recursive: true });
140465
- await fsPromises4.writeFile(outPath, injectReportData4(html, data), "utf8");
141191
+ await fsPromises4.writeFile(outPath, injectReportData4(html, data, lang), "utf8");
140466
141192
  const { stats } = contentCheck;
140467
141193
  console.log(`
140468
141194
  \u2705 Meta\uFF08Facebook\uFF09\u8D26\u6237\u8BCA\u65AD HTML \u62A5\u544A\u5DF2\u751F\u6210\uFF1A${outPath}
@@ -141152,12 +141878,13 @@ function registerFacebookAnalysisCommands(program2) {
141152
141878
  "--format <html|xlsx>",
141153
141879
  "\u9ED8\u8BA4 html\uFF08\u8FD0\u8425\u6D45\u8272\u6A21\u677F\uFF09\uFF1B\u7528\u6237\u8981\u8868\u683C / Excel \u65F6\u4F20 xlsx\uFF1B\u53EF\u9017\u53F7\u540C\u65F6\u8F93\u51FA html,xlsx",
141154
141880
  "html"
141155
- ).action(async (opts) => {
141881
+ ).option("--lang <lang>", "\u7EC8\u7A3F\u8BED\u8A00\uFF1Azh \u6216 en\u3002\u8986\u76D6 JSON meta.lang\uFF1B\u90FD\u6CA1\u6709\u5219\u4E2D\u6587").action(async (opts) => {
141156
141882
  await runFacebookAnalysisRender({
141157
141883
  dataFile: opts.data,
141158
141884
  snapshotDir: opts.snapshotDir,
141159
141885
  out: opts.out,
141160
- format: opts.format
141886
+ format: opts.format,
141887
+ lang: opts.lang
141161
141888
  });
141162
141889
  });
141163
141890
  root.command("diagnosis-render").description(
@@ -141165,11 +141892,12 @@ function registerFacebookAnalysisCommands(program2) {
141165
141892
  ).requiredOption("--data <file>", "Agent \u4EA7\u51FA\u7684 meta-account-diagnosis-report.json").option(
141166
141893
  "--snapshot-dir <dir>",
141167
141894
  "\u53EF\u9009\uFF1Afacebook-analysis --json-out \u76EE\u5F55\uFF0C\u81EA\u52A8\u8865\u5168 meta / kpis\uFF08overview \u5FEB\u7167\uFF09"
141168
- ).option("--out <file>", "\u8F93\u51FA HTML \u8DEF\u5F84\uFF08\u9ED8\u8BA4\u540C --data \u76EE\u5F55\uFF09").action(async (opts) => {
141895
+ ).option("--out <file>", "\u8F93\u51FA HTML \u8DEF\u5F84\uFF08\u9ED8\u8BA4\u540C --data \u76EE\u5F55\uFF09").option("--lang <lang>", "\u7EC8\u7A3F\u8BED\u8A00\uFF1Azh \u6216 en\u3002\u8986\u76D6 JSON meta.lang\uFF1B\u90FD\u6CA1\u6709\u5219\u4E2D\u6587").action(async (opts) => {
141169
141896
  await runFacebookAnalysisDiagnosisRender({
141170
141897
  dataFile: opts.data,
141171
141898
  snapshotDir: opts.snapshotDir,
141172
- out: opts.out
141899
+ out: opts.out,
141900
+ lang: opts.lang
141173
141901
  });
141174
141902
  });
141175
141903
  root.command("account-status").description(
@@ -142521,6 +143249,8 @@ function formatBingPeriodReportErrors(result) {
142521
143249
 
142522
143250
  // src/commands/bing-analysis/render-report.ts
142523
143251
  init_report_period();
143252
+ init_lang();
143253
+ init_html();
142524
143254
  var TEMPLATE_BASENAME3 = "bing-period-report.html";
142525
143255
  var INJECT_MARKER4 = "window.__BING_PERIOD_REPORT__ = window.__BING_PERIOD_REPORT__ || null;";
142526
143256
  function resolveSkillTemplatePath6(basename13) {
@@ -142552,12 +143282,13 @@ function asRecord34(value) {
142552
143282
  }
142553
143283
  throw new Error("\u62A5\u544A\u6570\u636E\u987B\u4E3A JSON \u5BF9\u8C61");
142554
143284
  }
142555
- function injectReportData5(html, payload) {
142556
- const json = JSON.stringify(attachPeriodChrome(payload)).replace(/</g, "\\u003c");
142557
- if (!html.includes(INJECT_MARKER4)) {
143285
+ function injectReportData5(html, payload, lang) {
143286
+ const localized = localizeReportHtml(html, lang);
143287
+ const json = JSON.stringify(attachPeriodChrome(stampMetaLang(payload, lang), lang)).replace(/</g, "\\u003c");
143288
+ if (!localized.includes(INJECT_MARKER4)) {
142558
143289
  throw new Error("\u62A5\u544A\u6A21\u677F\u7F3A\u5C11\u6570\u636E\u6CE8\u5165\u951A\u70B9\uFF0C\u8BF7\u66F4\u65B0 bing-period-report.html");
142559
143290
  }
142560
- return html.replace(INJECT_MARKER4, `window.__BING_PERIOD_REPORT__ = ${json};`);
143291
+ return localized.replace(INJECT_MARKER4, `window.__BING_PERIOD_REPORT__ = ${json};`);
142561
143292
  }
142562
143293
  async function runBingAnalysisRender(opts) {
142563
143294
  const dataPath = path48.resolve(opts.dataFile);
@@ -142565,6 +143296,7 @@ async function runBingAnalysisRender(opts) {
142565
143296
  if (opts.snapshotDir?.trim()) {
142566
143297
  data = await mergeBingAnalysisSnapshotIntoReport(data, path48.resolve(opts.snapshotDir));
142567
143298
  }
143299
+ const lang = readReportLangOrExit({ flag: opts.lang, meta: data.meta });
142568
143300
  const contentCheck = validateBingPeriodReportContent(data);
142569
143301
  if (!contentCheck.ok) {
142570
143302
  console.error(`
@@ -142594,7 +143326,7 @@ async function runBingAnalysisRender(opts) {
142594
143326
  );
142595
143327
  const outDir = path48.dirname(outPath);
142596
143328
  await fsPromises5.mkdir(outDir, { recursive: true });
142597
- await fsPromises5.writeFile(outPath, injectReportData5(html, data), "utf8");
143329
+ await fsPromises5.writeFile(outPath, injectReportData5(html, data, lang), "utf8");
142598
143330
  const { stats } = contentCheck;
142599
143331
  const cover = periodWord(attachPeriodChrome(data).periodChrome.unit, "\u8D26\u6237");
142600
143332
  console.log(`
@@ -142661,11 +143393,12 @@ function registerBingAnalysisCommands(program2) {
142661
143393
  ).requiredOption("--data <file>", "Agent \u4EA7\u51FA\u7684 bing-period-report.json").option(
142662
143394
  "--snapshot-dir <dir>",
142663
143395
  "\u53EF\u9009\uFF1Abing-analysis --json-out \u76EE\u5F55\uFF0C\u81EA\u52A8\u8865\u5168 meta / kpis\uFF08overview \u5FEB\u7167\uFF09"
142664
- ).option("--out <file>", "\u8F93\u51FA HTML \u8DEF\u5F84\uFF08\u9ED8\u8BA4\u540C --data \u76EE\u5F55\uFF09").action(async (opts) => {
143396
+ ).option("--out <file>", "\u8F93\u51FA HTML \u8DEF\u5F84\uFF08\u9ED8\u8BA4\u540C --data \u76EE\u5F55\uFF09").option("--lang <lang>", "\u7EC8\u7A3F\u8BED\u8A00\uFF1Azh \u6216 en\u3002\u8986\u76D6 JSON meta.lang\uFF1B\u90FD\u6CA1\u6709\u5219\u4E2D\u6587").action(async (opts) => {
142665
143397
  await runBingAnalysisRender({
142666
143398
  dataFile: opts.data,
142667
143399
  snapshotDir: opts.snapshotDir,
142668
- out: opts.out
143400
+ out: opts.out,
143401
+ lang: opts.lang
142669
143402
  });
142670
143403
  });
142671
143404
  root.command("account-status").description("\u8D26\u6237\u72B6\u6001\uFF08GET \u2026/account-status\uFF1Bstatus=Active \u4E3A\u6B63\u5E38\u6295\u653E\uFF1B\u4E0D\u5E26\u65E5\u671F\u533A\u95F4\uFF09").requiredOption(...ACCOUNT_OPTION2).option("-t, --token <token>", "Auth Token").option("--json-out <path>", JSON_OUT_OPTION_DESC2, void 0).option("--verbose", "\u8BE6\u7EC6\u9519\u8BEF\u4FE1\u606F", false).action(
@@ -143420,6 +144153,8 @@ function formatTikTokPeriodReportErrors(result) {
143420
144153
 
143421
144154
  // src/commands/tiktok-analysis/render-report.ts
143422
144155
  init_report_period();
144156
+ init_lang();
144157
+ init_html();
143423
144158
  var TEMPLATE_BASENAME4 = "tiktok-period-report.html";
143424
144159
  var INJECT_MARKER5 = "window.__TIKTOK_PERIOD_REPORT__ = window.__TIKTOK_PERIOD_REPORT__ || null;";
143425
144160
  function resolveSkillTemplatePath7(basename13) {
@@ -143451,12 +144186,13 @@ function asRecord37(value) {
143451
144186
  }
143452
144187
  throw new Error("\u62A5\u544A\u6570\u636E\u987B\u4E3A JSON \u5BF9\u8C61");
143453
144188
  }
143454
- function injectReportData6(html, payload) {
143455
- const json = JSON.stringify(attachPeriodChrome(payload)).replace(/</g, "\\u003c");
143456
- if (!html.includes(INJECT_MARKER5)) {
144189
+ function injectReportData6(html, payload, lang) {
144190
+ const localized = localizeReportHtml(html, lang);
144191
+ const json = JSON.stringify(attachPeriodChrome(stampMetaLang(payload, lang), lang)).replace(/</g, "\\u003c");
144192
+ if (!localized.includes(INJECT_MARKER5)) {
143457
144193
  throw new Error("\u62A5\u544A\u6A21\u677F\u7F3A\u5C11\u6570\u636E\u6CE8\u5165\u951A\u70B9\uFF0C\u8BF7\u66F4\u65B0 tiktok-period-report.html");
143458
144194
  }
143459
- return html.replace(INJECT_MARKER5, `window.__TIKTOK_PERIOD_REPORT__ = ${json};`);
144195
+ return localized.replace(INJECT_MARKER5, `window.__TIKTOK_PERIOD_REPORT__ = ${json};`);
143460
144196
  }
143461
144197
  async function runTikTokAnalysisRender(opts) {
143462
144198
  const dataPath = path51.resolve(opts.dataFile);
@@ -143464,6 +144200,7 @@ async function runTikTokAnalysisRender(opts) {
143464
144200
  if (opts.snapshotDir?.trim()) {
143465
144201
  data = await mergeTikTokAnalysisSnapshotIntoReport(data, path51.resolve(opts.snapshotDir));
143466
144202
  }
144203
+ const lang = readReportLangOrExit({ flag: opts.lang, meta: data.meta });
143467
144204
  const contentCheck = validateTikTokPeriodReportContent(data);
143468
144205
  if (!contentCheck.ok) {
143469
144206
  console.error(`
@@ -143493,7 +144230,7 @@ async function runTikTokAnalysisRender(opts) {
143493
144230
  );
143494
144231
  const outDir = path51.dirname(outPath);
143495
144232
  await fsPromises6.mkdir(outDir, { recursive: true });
143496
- await fsPromises6.writeFile(outPath, injectReportData6(html, data), "utf8");
144233
+ await fsPromises6.writeFile(outPath, injectReportData6(html, data, lang), "utf8");
143497
144234
  const { stats } = contentCheck;
143498
144235
  const cover = periodWord(attachPeriodChrome(data).periodChrome.unit, "\u8D26\u6237");
143499
144236
  console.log(`
@@ -144239,11 +144976,12 @@ function registerTikTokAnalysisCommands(program2) {
144239
144976
  ).requiredOption("--data <file>", "Agent \u4EA7\u51FA\u7684 tiktok-period-report.json").option(
144240
144977
  "--snapshot-dir <dir>",
144241
144978
  "\u53EF\u9009\uFF1Atiktok-analysis --json-out \u76EE\u5F55\uFF0C\u81EA\u52A8\u8865\u5168 meta\uFF08accountId / \u65E5\u671F\u533A\u95F4 / \u8D27\u5E01\uFF09"
144242
- ).option("--out <file>", "\u8F93\u51FA HTML \u8DEF\u5F84\uFF08\u9ED8\u8BA4\u540C --data \u76EE\u5F55\uFF09").action(async (opts) => {
144979
+ ).option("--out <file>", "\u8F93\u51FA HTML \u8DEF\u5F84\uFF08\u9ED8\u8BA4\u540C --data \u76EE\u5F55\uFF09").option("--lang <lang>", "\u7EC8\u7A3F\u8BED\u8A00\uFF1Azh \u6216 en\u3002\u8986\u76D6 JSON meta.lang\uFF1B\u90FD\u6CA1\u6709\u5219\u4E2D\u6587").action(async (opts) => {
144243
144980
  await runTikTokAnalysisRender({
144244
144981
  dataFile: opts.data,
144245
144982
  snapshotDir: opts.snapshotDir,
144246
- out: opts.out
144983
+ out: opts.out,
144984
+ lang: opts.lang
144247
144985
  });
144248
144986
  });
144249
144987
  root.command("areacode").description("TikTok \u5730\u533A\u4EE3\u7801\u679A\u4E3E\uFF08\u4E3B\u5E73\u53F0 query/media-account/tiktok/TikTokAreacode/Read\uFF09").option("-t, --token <token>", "Auth Token").option(
@@ -145196,6 +145934,8 @@ function formatYandexPeriodReportErrors(result) {
145196
145934
 
145197
145935
  // src/commands/yandex-analysis/render-report.ts
145198
145936
  init_report_period();
145937
+ init_lang();
145938
+ init_html();
145199
145939
  var TEMPLATE_BASENAME5 = "yandex-period-report.html";
145200
145940
  var INJECT_MARKER6 = "window.__YANDEX_PERIOD_REPORT__ = window.__YANDEX_PERIOD_REPORT__ || null;";
145201
145941
  function resolveSkillTemplatePath8(basename13) {
@@ -145240,12 +145980,13 @@ function refreshWeeklyFromDaily(data) {
145240
145980
  }
145241
145981
  };
145242
145982
  }
145243
- function injectReportData7(html, payload) {
145244
- const json = JSON.stringify(attachPeriodChrome(payload)).replace(/</g, "\\u003c");
145245
- if (!html.includes(INJECT_MARKER6)) {
145983
+ function injectReportData7(html, payload, lang) {
145984
+ const localized = localizeReportHtml(html, lang);
145985
+ const json = JSON.stringify(attachPeriodChrome(stampMetaLang(payload, lang), lang)).replace(/</g, "\\u003c");
145986
+ if (!localized.includes(INJECT_MARKER6)) {
145246
145987
  throw new Error("\u62A5\u544A\u6A21\u677F\u7F3A\u5C11\u6570\u636E\u6CE8\u5165\u951A\u70B9\uFF0C\u8BF7\u66F4\u65B0 yandex-period-report.html");
145247
145988
  }
145248
- return html.replace(INJECT_MARKER6, `window.__YANDEX_PERIOD_REPORT__ = ${json};`);
145989
+ return localized.replace(INJECT_MARKER6, `window.__YANDEX_PERIOD_REPORT__ = ${json};`);
145249
145990
  }
145250
145991
  async function runYandexAnalysisRender(opts) {
145251
145992
  const dataPath = path54.resolve(opts.dataFile);
@@ -145254,6 +145995,7 @@ async function runYandexAnalysisRender(opts) {
145254
145995
  data = await mergeYandexAnalysisSnapshotIntoReport(data, path54.resolve(opts.snapshotDir));
145255
145996
  }
145256
145997
  data = refreshWeeklyFromDaily(data);
145998
+ const lang = readReportLangOrExit({ flag: opts.lang, meta: data.meta });
145257
145999
  const contentCheck = validateYandexPeriodReportContent(data);
145258
146000
  if (!contentCheck.ok) {
145259
146001
  console.error(`
@@ -145281,7 +146023,7 @@ async function runYandexAnalysisRender(opts) {
145281
146023
  );
145282
146024
  const outDir = path54.dirname(outPath);
145283
146025
  await fsPromises7.mkdir(outDir, { recursive: true });
145284
- await fsPromises7.writeFile(outPath, injectReportData7(html, data), "utf8");
146026
+ await fsPromises7.writeFile(outPath, injectReportData7(html, data, lang), "utf8");
145285
146027
  const { stats } = contentCheck;
145286
146028
  const cover = periodWord(attachPeriodChrome(data).periodChrome.unit, "\u8D26\u6237");
145287
146029
  console.log(`
@@ -145793,11 +146535,12 @@ function registerYandexAnalysisCommands(program2) {
145793
146535
  ).requiredOption("--data <file>", "Agent \u4EA7\u51FA\u7684 yandex-period-report.json").option(
145794
146536
  "--snapshot-dir <dir>",
145795
146537
  "\u53EF\u9009\uFF1Ayandex-analysis --json-out \u76EE\u5F55\uFF0C\u81EA\u52A8\u8865\u5168 meta / kpis / tables"
145796
- ).option("--out <file>", "\u8F93\u51FA HTML \u8DEF\u5F84\uFF08\u9ED8\u8BA4\u540C --data \u76EE\u5F55\uFF09").action(async (opts) => {
146538
+ ).option("--out <file>", "\u8F93\u51FA HTML \u8DEF\u5F84\uFF08\u9ED8\u8BA4\u540C --data \u76EE\u5F55\uFF09").option("--lang <lang>", "\u7EC8\u7A3F\u8BED\u8A00\uFF1Azh \u6216 en\u3002\u8986\u76D6 JSON meta.lang\uFF1B\u90FD\u6CA1\u6709\u5219\u4E2D\u6587").action(async (opts) => {
145797
146539
  await runYandexAnalysisRender({
145798
146540
  dataFile: opts.data,
145799
146541
  snapshotDir: opts.snapshotDir,
145800
- out: opts.out
146542
+ out: opts.out,
146543
+ lang: opts.lang
145801
146544
  });
145802
146545
  });
145803
146546
  root.command("account-status").description(