@contentrain/wp-import 0.16.4 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -77,10 +77,16 @@ models get `i18n: true`, content and meta are written per locale
77
77
  (`content/{domain}/{model}/{locale}.json`), and every translation in a group
78
78
  shares one entry id — the canonical member is the default-locale post (else
79
79
  the lowest id), so `entry_source_map[wp_id]` addresses each post by that id
80
- and its own locale. Locales are primary subtags (`tr_TR` → `tr`). Taxonomies
81
- stay per-store; the `language` / `post_translations` bookkeeping taxonomies
82
- never become models. `report.locales` and `report.translation_groups` say
83
- what happened. A monolingual site is unchanged (`i18n: false`, `data.json`).
80
+ and its own locale. Locales are primary subtags (`tr_TR` → `tr`). A taxonomy
81
+ stays one store-wide model unless its terms carry a language (`RawTerm.lang`,
82
+ from the Bridge): then it is i18n too, and the Bridge's `term_pairs` fold each
83
+ term translation group into one entry id the same way (default-locale term,
84
+ else the lowest id). Every reference to a term — a post's taxonomy field, its
85
+ primary category, an ACF term field, a menu target, a parent — resolves to its
86
+ group's id. The `language` / `post_translations` bookkeeping taxonomies never
87
+ become models. `report.locales`, `report.translation_groups` and
88
+ `report.term_translation_groups` say what happened. A monolingual site is
89
+ unchanged (`i18n: false`, `data.json`).
84
90
 
85
91
  Few sites are translated evenly. A post type whose content exists in some of the
86
92
  site's languages gets `model.locales` — the locales it actually has posts in,
package/dist/index.d.mts CHANGED
@@ -126,10 +126,12 @@ interface ImportReport {
126
126
  fields: number;
127
127
  entries: number;
128
128
  }>;
129
- /** Default first, then every other language seen on a post. One entry on a monolingual site. */
129
+ /** Default first, then every other language seen on a post or a translated term. One entry on a monolingual site. */
130
130
  locales: string[];
131
131
  /** Translation groups that were folded into one entry id across locales. */
132
132
  translation_groups: number;
133
+ /** Term translation groups (`term_pairs`) folded into one entry id across locales. */
134
+ term_translation_groups: number;
133
135
  /**
134
136
  * Password-protected posts imported as `draft`, whatever their WordPress
135
137
  * status. Their body is the protected text, and nothing downstream (query
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/wxr.ts","../src/rest.ts","../src/contentrain.ts","../src/comments.ts","../src/php-unserialize.ts","../src/core.ts","../src/delta.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/wxr.ts","../src/rest.ts","../src/contentrain.ts","../src/comments.ts","../src/php-unserialize.ts","../src/core.ts","../src/delta.ts"],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -2233,7 +2233,9 @@ function rawToContentrain(input, opts) {
2233
2233
  const localeContents = {};
2234
2234
  const localeMetas = {};
2235
2235
  const postLocale = (p) => normLocale(p.lang ?? p.terms.find((t) => t.taxonomy === "language")?.slug) || locale;
2236
- const locales = [locale, ...[...new Set(raw.posts.filter((p) => !SKIP_TYPES.test(p.type)).map(postLocale))].filter((l) => l !== locale).toSorted()];
2236
+ const termLocale = (t) => normLocale(t.lang) || locale;
2237
+ const termLocales = raw.terms.filter((t) => t.lang && !LANGUAGE_TAXONOMIES.has(t.taxonomy)).map(termLocale);
2238
+ const locales = [locale, ...[...new Set([...raw.posts.filter((p) => !SKIP_TYPES.test(p.type)).map(postLocale), ...termLocales])].filter((l) => l !== locale).toSorted()];
2237
2239
  const multilingual = locales.length > 1;
2238
2240
  const postById = new Map(raw.posts.map((p) => [p.id, p]));
2239
2241
  const canonicalOf = /* @__PURE__ */ new Map();
@@ -2267,6 +2269,7 @@ function rawToContentrain(input, opts) {
2267
2269
  models: {},
2268
2270
  locales,
2269
2271
  translation_groups: translationGroups,
2272
+ term_translation_groups: 0,
2270
2273
  password_protected_drafts: 0,
2271
2274
  site_title_missing: false
2272
2275
  };
@@ -2291,6 +2294,19 @@ function rawToContentrain(input, opts) {
2291
2294
  };
2292
2295
  const authorId = (login) => hexId(`authors:${login}`);
2293
2296
  const termId = (tax, slug) => hexId(`${tax}:${slug}`);
2297
+ const termByWpId = new Map(raw.terms.filter((t) => t.id !== null).map((t) => [t.id, t]));
2298
+ const canonicalTermSlug = /* @__PURE__ */ new Map();
2299
+ for (const pair of raw.term_pairs ?? []) {
2300
+ const members = [...new Set(Object.values(pair.translations))].map((id) => termByWpId.get(id)).filter((t) => t !== void 0 && t.taxonomy === pair.taxonomy);
2301
+ if (members.length < 2 || new Set(members.map(termLocale)).size !== members.length) continue;
2302
+ const canonical = members.find((t) => termLocale(t) === locale) ?? members.reduce((a, b) => a.id <= b.id ? a : b);
2303
+ for (const t of members) canonicalTermSlug.set(`${t.taxonomy}:${t.slug}`, canonical.slug);
2304
+ report.term_translation_groups++;
2305
+ }
2306
+ /** The entry id a reference to a term resolves to: its group's canonical member's. */
2307
+ const termRef = (tax, slug) => termId(tax, canonicalTermSlug.get(`${tax}:${slug}`) ?? slug);
2308
+ const i18nTaxonomies = new Set(multilingual ? raw.terms.filter((t) => t.lang).map((t) => t.taxonomy) : []);
2309
+ const termExists = (mid, id) => contents[mid]?.[id] !== void 0 || Object.values(localeContents[mid] ?? {}).some((bucket) => bucket[id] !== void 0);
2294
2310
  const usedSlugs = /* @__PURE__ */ new Map();
2295
2311
  const postSlug = (p) => {
2296
2312
  let s = addressSlug(p.slug);
@@ -2378,7 +2394,7 @@ function rawToContentrain(input, opts) {
2378
2394
  const t = termById.get(id);
2379
2395
  return t && models[taxModelId(t.taxonomy)] ? {
2380
2396
  model: taxModelId(t.taxonomy),
2381
- ref: termId(t.taxonomy, t.slug)
2397
+ ref: termRef(t.taxonomy, t.slug)
2382
2398
  } : null;
2383
2399
  }
2384
2400
  return null;
@@ -2548,12 +2564,15 @@ function rawToContentrain(input, opts) {
2548
2564
  const taxonomies = [...new Set(raw.terms.map((t) => t.taxonomy))].filter((t) => t !== "nav_menu" && t !== "post_format" && !LANGUAGE_TAXONOMIES.has(t));
2549
2565
  for (const tax of taxonomies) {
2550
2566
  const mid = taxModelId(tax);
2567
+ const i18n = i18nTaxonomies.has(tax);
2568
+ const covered = locales.filter((l) => l === locale || raw.terms.some((t) => t.taxonomy === tax && termLocale(t) === l));
2551
2569
  addModel({
2552
2570
  id: mid,
2553
2571
  name: mid,
2554
2572
  kind: "collection",
2555
2573
  domain: "blog",
2556
- i18n: false,
2574
+ i18n,
2575
+ ...i18n && covered.length < locales.length ? { locales: covered } : {},
2557
2576
  title_field: "name",
2558
2577
  fields: {
2559
2578
  name: {
@@ -2588,12 +2607,15 @@ function rawToContentrain(input, opts) {
2588
2607
  }
2589
2608
  });
2590
2609
  for (const t of raw.terms.filter((x) => x.taxonomy === tax)) {
2591
- const id = termId(tax, t.slug);
2592
- contents[mid][id] = pick({
2610
+ const id = termRef(tax, t.slug);
2611
+ const loc = termLocale(t);
2612
+ const content = i18n ? (localeContents[mid] ??= {})[loc] ??= {} : contents[mid];
2613
+ const meta = i18n ? (localeMetas[mid] ??= {})[loc] ??= {} : metas[mid];
2614
+ content[id] = pick({
2593
2615
  name: strip(t.name),
2594
2616
  slug: addressSlug(t.slug) || `term-${t.id}`,
2595
2617
  description: strip(t.description),
2596
- parent: t.parent && t.parent_resolved ? termId(tax, t.parent) : null,
2618
+ parent: t.parent && t.parent_resolved ? termRef(tax, t.parent) : null,
2597
2619
  wp_id: t.id
2598
2620
  }, new Set([
2599
2621
  "name",
@@ -2602,19 +2624,22 @@ function rawToContentrain(input, opts) {
2602
2624
  "parent",
2603
2625
  "wp_id"
2604
2626
  ]));
2605
- metas[mid][id] = importMeta("published");
2627
+ meta[id] = importMeta("published");
2606
2628
  }
2607
2629
  }
2608
2630
  for (const p of raw.posts) for (const t of p.terms) {
2609
2631
  if (t.taxonomy === "nav_menu" || t.taxonomy === "post_format" || t.resolved) continue;
2610
2632
  const mid = taxModelId(t.taxonomy);
2611
2633
  if (!models[mid]) continue;
2612
- const id = termId(t.taxonomy, t.slug);
2613
- contents[mid][id] ??= {
2634
+ const id = termRef(t.taxonomy, t.slug);
2635
+ if (termExists(mid, id)) continue;
2636
+ const content = models[mid].i18n ? (localeContents[mid] ??= {})[locale] ??= {} : contents[mid];
2637
+ const meta = models[mid].i18n ? (localeMetas[mid] ??= {})[locale] ??= {} : metas[mid];
2638
+ content[id] = {
2614
2639
  name: strip(t.name),
2615
2640
  slug: addressSlug(t.slug) || `term-${hexId(`${t.taxonomy}:${t.slug}`).slice(0, 8)}`
2616
2641
  };
2617
- metas[mid][id] ??= importMeta("published");
2642
+ meta[id] = importMeta("published");
2618
2643
  }
2619
2644
  const postTypes = [...new Set(raw.posts.map((p) => p.type))].filter((t) => {
2620
2645
  if (SKIP_TYPES.test(t)) {
@@ -2886,7 +2911,7 @@ function rawToContentrain(input, opts) {
2886
2911
  if (p.modified) e.modified_at = p.modified;
2887
2912
  if (p.link) e.link = p.link;
2888
2913
  if (fields.author && p.author) e.author = authorId(slugify(p.author));
2889
- for (const t of taxes) e[taxModelId(t)] = p.terms.filter((x) => x.taxonomy === t && contents[taxModelId(t)][termId(t, x.slug)]).map((x) => termId(t, x.slug));
2914
+ for (const t of taxes) e[taxModelId(t)] = p.terms.filter((x) => x.taxonomy === t && termExists(taxModelId(t), termRef(t, x.slug))).map((x) => termRef(t, x.slug));
2890
2915
  if (fields.cover && p.meta._thumbnail_id) {
2891
2916
  const m = mediaRef(Number(p.meta._thumbnail_id));
2892
2917
  if (m) e.cover = m;
@@ -2902,9 +2927,9 @@ function rawToContentrain(input, opts) {
2902
2927
  if (fields.primary_category) {
2903
2928
  const termWpId = primaryCategoryId(p);
2904
2929
  const term = termWpId === void 0 ? void 0 : termById.get(termWpId);
2905
- const ref = term && term.taxonomy === "category" ? termId("category", term.slug) : void 0;
2930
+ const ref = term && term.taxonomy === "category" ? termRef("category", term.slug) : void 0;
2906
2931
  const assigned = ref !== void 0 && Array.isArray(e[taxModelId("category")]) && e[taxModelId("category")].includes(ref);
2907
- if (ref && assigned && contents[taxModelId("category")]?.[ref]) e.primary_category = ref;
2932
+ if (ref && assigned && termExists(taxModelId("category"), ref)) e.primary_category = ref;
2908
2933
  else if (termWpId !== void 0) report.dropped_relations++;
2909
2934
  }
2910
2935
  if (fields.template && typeof p.meta._wp_page_template === "string" && p.meta._wp_page_template !== "default") e.template = p.meta._wp_page_template;
@@ -3087,7 +3112,7 @@ function rawToContentrain(input, opts) {
3087
3112
  }
3088
3113
  if (i.target.kind === "term" && i.target.resolved && i.target.slug && models[taxModelId(i.target.taxonomy)]) e.target = toTarget.ref({
3089
3114
  model: taxModelId(i.target.taxonomy),
3090
- ref: termId(i.target.taxonomy, i.target.slug)
3115
+ ref: termRef(i.target.taxonomy, i.target.slug)
3091
3116
  });
3092
3117
  if (i.classes?.length) e.classes = i.classes;
3093
3118
  if (i.description) e.description = strip(i.description);