@tuwaio/docs-ui 0.5.7 → 0.6.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 +91 -3
- package/bin/i18n-check.js +19 -0
- package/dist/components/DocsLayout.cjs +82 -0
- package/dist/components/DocsLayout.d.cts +50 -0
- package/dist/components/DocsLayout.d.ts +50 -0
- package/dist/components/DocsLayout.js +58 -0
- package/dist/components/LocalizedPage.cjs +42 -0
- package/dist/components/LocalizedPage.d.cts +37 -0
- package/dist/components/LocalizedPage.d.ts +37 -0
- package/dist/components/LocalizedPage.js +18 -0
- package/dist/docsI18n.cjs +33 -0
- package/dist/docsI18n.d.cts +16 -0
- package/dist/docsI18n.d.ts +16 -0
- package/dist/docsI18n.js +8 -0
- package/dist/docsLabels.cjs +140 -0
- package/dist/docsLabels.d.cts +72 -0
- package/dist/docsLabels.d.ts +72 -0
- package/dist/docsLabels.js +116 -0
- package/dist/docsPage.cjs +70 -0
- package/dist/docsPage.d.cts +56 -0
- package/dist/docsPage.d.ts +56 -0
- package/dist/docsPage.js +46 -0
- package/dist/i18n.d.cts +1 -1
- package/dist/i18n.d.ts +1 -1
- package/dist/index.cjs +4 -0
- package/dist/index.css +3 -0
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/nextra.cjs +26 -0
- package/dist/nextra.d.cts +10 -0
- package/dist/nextra.d.ts +10 -0
- package/dist/nextra.js +3 -0
- package/dist/pageMap.cjs +55 -0
- package/dist/pageMap.d.cts +20 -0
- package/dist/pageMap.d.ts +20 -0
- package/dist/pageMap.js +31 -0
- package/dist/translationCheck.cjs +186 -0
- package/dist/translationCheck.d.cts +89 -0
- package/dist/translationCheck.d.ts +89 -0
- package/dist/translationCheck.js +157 -0
- package/package.json +15 -3
package/dist/index.cjs
CHANGED
|
@@ -42,8 +42,10 @@ __reExport(index_exports, require("./components/Orb"), module.exports);
|
|
|
42
42
|
__reExport(index_exports, require("./components/PolicyDocument"), module.exports);
|
|
43
43
|
var import_StarryBackground = __toESM(require("./components/StarryBackground"), 1);
|
|
44
44
|
__reExport(index_exports, require("./components/ThemeSwitcher"), module.exports);
|
|
45
|
+
__reExport(index_exports, require("./docsLabels"), module.exports);
|
|
45
46
|
__reExport(index_exports, require("./i18n"), module.exports);
|
|
46
47
|
__reExport(index_exports, require("./locale"), module.exports);
|
|
48
|
+
__reExport(index_exports, require("./pageMap"), module.exports);
|
|
47
49
|
__reExport(index_exports, require("./policies"), module.exports);
|
|
48
50
|
__reExport(index_exports, require("./preferences"), module.exports);
|
|
49
51
|
__reExport(index_exports, require("./utils"), module.exports);
|
|
@@ -58,8 +60,10 @@ __reExport(index_exports, require("./utils"), module.exports);
|
|
|
58
60
|
...require("./components/Orb"),
|
|
59
61
|
...require("./components/PolicyDocument"),
|
|
60
62
|
...require("./components/ThemeSwitcher"),
|
|
63
|
+
...require("./docsLabels"),
|
|
61
64
|
...require("./i18n"),
|
|
62
65
|
...require("./locale"),
|
|
66
|
+
...require("./pageMap"),
|
|
63
67
|
...require("./policies"),
|
|
64
68
|
...require("./preferences"),
|
|
65
69
|
...require("./utils")
|
package/dist/index.css
CHANGED
package/dist/index.d.cts
CHANGED
|
@@ -7,9 +7,12 @@ export { Orb, OrbProps, PackageType } from './components/Orb.cjs';
|
|
|
7
7
|
export { PolicyDocument, PolicyDocumentProps } from './components/PolicyDocument.cjs';
|
|
8
8
|
export { default as StarryBackground } from './components/StarryBackground.cjs';
|
|
9
9
|
export { ThemeSwitcher, ThemeSwitcherProps } from './components/ThemeSwitcher.cjs';
|
|
10
|
+
export { DocsLabels, docsLabels } from './docsLabels.cjs';
|
|
10
11
|
export { I18nContextValue, I18nKit, I18nProviderProps, LocaleState, LocaleStateOptions, LocaleVariant, createI18n, useLocaleState } from './i18n.cjs';
|
|
11
12
|
export { DEFAULT_LOCALE, LOCALE_STORAGE_KEY, LocaleSignals, TUWA_LOCALES, TuwaLocale, detectLocale, formatLabel, normalizeLocale, policyLanguageOf, resolveClientLocale } from './locale.cjs';
|
|
13
|
+
export { localizePageMap } from './pageMap.cjs';
|
|
12
14
|
export { POLICIES_INDEX_URL, POLICY_DOCUMENT_LABELS, POLICY_LANGUAGES, POLICY_LANGUAGE_NAMES, POLICY_SITE_URL, POLICY_SOURCE, Policy, PolicyContent, PolicyDocumentLabels, PolicyLanguage, PolicySection, PolicySlug, footerPolicies, formatPolicyDate, getPolicyFileUrl, getPolicyPageUrl, loadPolicy, policies, policyLanguageFrom, publishedPolicies } from './policies.cjs';
|
|
13
15
|
export { PREFERENCE_DAYS, PreferenceExpiry, expireStoredPreferences } from './preferences.cjs';
|
|
14
16
|
export { baseFooterLinks, baseNavLinks } from './utils.cjs';
|
|
15
17
|
import 'react';
|
|
18
|
+
import 'nextra';
|
package/dist/index.d.ts
CHANGED
|
@@ -7,9 +7,12 @@ export { Orb, OrbProps, PackageType } from './components/Orb.js';
|
|
|
7
7
|
export { PolicyDocument, PolicyDocumentProps } from './components/PolicyDocument.js';
|
|
8
8
|
export { default as StarryBackground } from './components/StarryBackground.js';
|
|
9
9
|
export { ThemeSwitcher, ThemeSwitcherProps } from './components/ThemeSwitcher.js';
|
|
10
|
+
export { DocsLabels, docsLabels } from './docsLabels.js';
|
|
10
11
|
export { I18nContextValue, I18nKit, I18nProviderProps, LocaleState, LocaleStateOptions, LocaleVariant, createI18n, useLocaleState } from './i18n.js';
|
|
11
12
|
export { DEFAULT_LOCALE, LOCALE_STORAGE_KEY, LocaleSignals, TUWA_LOCALES, TuwaLocale, detectLocale, formatLabel, normalizeLocale, policyLanguageOf, resolveClientLocale } from './locale.js';
|
|
13
|
+
export { localizePageMap } from './pageMap.js';
|
|
12
14
|
export { POLICIES_INDEX_URL, POLICY_DOCUMENT_LABELS, POLICY_LANGUAGES, POLICY_LANGUAGE_NAMES, POLICY_SITE_URL, POLICY_SOURCE, Policy, PolicyContent, PolicyDocumentLabels, PolicyLanguage, PolicySection, PolicySlug, footerPolicies, formatPolicyDate, getPolicyFileUrl, getPolicyPageUrl, loadPolicy, policies, policyLanguageFrom, publishedPolicies } from './policies.js';
|
|
13
15
|
export { PREFERENCE_DAYS, PreferenceExpiry, expireStoredPreferences } from './preferences.js';
|
|
14
16
|
export { baseFooterLinks, baseNavLinks } from './utils.js';
|
|
15
17
|
import 'react';
|
|
18
|
+
import 'nextra';
|
package/dist/index.js
CHANGED
|
@@ -7,8 +7,10 @@ export * from "./components/Orb";
|
|
|
7
7
|
export * from "./components/PolicyDocument";
|
|
8
8
|
import { default as default3 } from "./components/StarryBackground";
|
|
9
9
|
export * from "./components/ThemeSwitcher";
|
|
10
|
+
export * from "./docsLabels";
|
|
10
11
|
export * from "./i18n";
|
|
11
12
|
export * from "./locale";
|
|
13
|
+
export * from "./pageMap";
|
|
12
14
|
export * from "./policies";
|
|
13
15
|
export * from "./preferences";
|
|
14
16
|
export * from "./utils";
|
package/dist/nextra.cjs
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __copyProps = (to, from, except, desc) => {
|
|
7
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
8
|
+
for (let key of __getOwnPropNames(from))
|
|
9
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
10
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
11
|
+
}
|
|
12
|
+
return to;
|
|
13
|
+
};
|
|
14
|
+
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
15
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
16
|
+
var nextra_exports = {};
|
|
17
|
+
module.exports = __toCommonJS(nextra_exports);
|
|
18
|
+
__reExport(nextra_exports, require("./components/DocsLayout"), module.exports);
|
|
19
|
+
__reExport(nextra_exports, require("./components/LocalizedPage"), module.exports);
|
|
20
|
+
__reExport(nextra_exports, require("./docsI18n"), module.exports);
|
|
21
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
22
|
+
0 && (module.exports = {
|
|
23
|
+
...require("./components/DocsLayout"),
|
|
24
|
+
...require("./components/LocalizedPage"),
|
|
25
|
+
...require("./docsI18n")
|
|
26
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { DocsLayout, DocsLayoutProps } from './components/DocsLayout.cjs';
|
|
2
|
+
export { LocalizedPage, LocalizedPageProps, LocalizedPageVariant } from './components/LocalizedPage.cjs';
|
|
3
|
+
export { DocsI18nProvider, useDocsI18n } from './docsI18n.cjs';
|
|
4
|
+
import 'react';
|
|
5
|
+
import 'nextra';
|
|
6
|
+
import './locale.cjs';
|
|
7
|
+
import './components/NavBar.cjs';
|
|
8
|
+
import './docsLabels.cjs';
|
|
9
|
+
import './policies.cjs';
|
|
10
|
+
import './i18n.cjs';
|
package/dist/nextra.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { DocsLayout, DocsLayoutProps } from './components/DocsLayout.js';
|
|
2
|
+
export { LocalizedPage, LocalizedPageProps, LocalizedPageVariant } from './components/LocalizedPage.js';
|
|
3
|
+
export { DocsI18nProvider, useDocsI18n } from './docsI18n.js';
|
|
4
|
+
import 'react';
|
|
5
|
+
import 'nextra';
|
|
6
|
+
import './locale.js';
|
|
7
|
+
import './components/NavBar.js';
|
|
8
|
+
import './docsLabels.js';
|
|
9
|
+
import './policies.js';
|
|
10
|
+
import './i18n.js';
|
package/dist/nextra.js
ADDED
package/dist/pageMap.cjs
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var pageMap_exports = {};
|
|
20
|
+
__export(pageMap_exports, {
|
|
21
|
+
localizePageMap: () => localizePageMap
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(pageMap_exports);
|
|
24
|
+
function localizeMetaValue(value, titles) {
|
|
25
|
+
if (typeof value === "string") return titles[value] ?? value;
|
|
26
|
+
if (value && typeof value === "object" && typeof value.title === "string") {
|
|
27
|
+
const translated = titles[value.title];
|
|
28
|
+
return translated ? { ...value, title: translated } : value;
|
|
29
|
+
}
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
function localizePageMap(pageMap, titles) {
|
|
33
|
+
if (Object.keys(titles).length === 0) return pageMap;
|
|
34
|
+
return pageMap.map((item) => {
|
|
35
|
+
if ("data" in item) {
|
|
36
|
+
const data = Object.fromEntries(
|
|
37
|
+
Object.entries(item.data).map(([key, value]) => [key, localizeMetaValue(value, titles)])
|
|
38
|
+
);
|
|
39
|
+
return { ...item, data };
|
|
40
|
+
}
|
|
41
|
+
const copy = { ...item };
|
|
42
|
+
const title = item.title;
|
|
43
|
+
if (typeof title === "string" && titles[title]) copy.title = titles[title];
|
|
44
|
+
if ("frontMatter" in item && item.frontMatter && typeof item.frontMatter.title === "string") {
|
|
45
|
+
const title2 = titles[item.frontMatter.title];
|
|
46
|
+
if (title2) copy.frontMatter = { ...item.frontMatter, title: title2 };
|
|
47
|
+
}
|
|
48
|
+
if ("children" in item && Array.isArray(item.children)) copy.children = localizePageMap(item.children, titles);
|
|
49
|
+
return copy;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
53
|
+
0 && (module.exports = {
|
|
54
|
+
localizePageMap
|
|
55
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { PageMapItem } from 'nextra';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Translates the titles of a Nextra page map: the sidebar, the top-level menu, the breadcrumbs and the previous / next
|
|
5
|
+
* links all read them. Titles are matched by their English text (the `_meta` titles, the `title` of each item and the
|
|
6
|
+
* front matter `title`), so a site lists each English title once, whatever the page; titles without a translation,
|
|
7
|
+
* such as package names, stay as they are. The page map is not modified: changed items are copied.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* localizePageMap(pageMap, { Introduction: 'Вступ', Packages: 'Пакети' });
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* @param pageMap - The page map of the site, the result of `getPageMap()` of `nextra/page-map`.
|
|
15
|
+
* @param titles - Translated titles by English title.
|
|
16
|
+
* @returns The page map with translated titles.
|
|
17
|
+
*/
|
|
18
|
+
declare function localizePageMap(pageMap: PageMapItem[], titles: Readonly<Record<string, string>>): PageMapItem[];
|
|
19
|
+
|
|
20
|
+
export { localizePageMap };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { PageMapItem } from 'nextra';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Translates the titles of a Nextra page map: the sidebar, the top-level menu, the breadcrumbs and the previous / next
|
|
5
|
+
* links all read them. Titles are matched by their English text (the `_meta` titles, the `title` of each item and the
|
|
6
|
+
* front matter `title`), so a site lists each English title once, whatever the page; titles without a translation,
|
|
7
|
+
* such as package names, stay as they are. The page map is not modified: changed items are copied.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* localizePageMap(pageMap, { Introduction: 'Вступ', Packages: 'Пакети' });
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* @param pageMap - The page map of the site, the result of `getPageMap()` of `nextra/page-map`.
|
|
15
|
+
* @param titles - Translated titles by English title.
|
|
16
|
+
* @returns The page map with translated titles.
|
|
17
|
+
*/
|
|
18
|
+
declare function localizePageMap(pageMap: PageMapItem[], titles: Readonly<Record<string, string>>): PageMapItem[];
|
|
19
|
+
|
|
20
|
+
export { localizePageMap };
|
package/dist/pageMap.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
function localizeMetaValue(value, titles) {
|
|
2
|
+
if (typeof value === "string") return titles[value] ?? value;
|
|
3
|
+
if (value && typeof value === "object" && typeof value.title === "string") {
|
|
4
|
+
const translated = titles[value.title];
|
|
5
|
+
return translated ? { ...value, title: translated } : value;
|
|
6
|
+
}
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
9
|
+
function localizePageMap(pageMap, titles) {
|
|
10
|
+
if (Object.keys(titles).length === 0) return pageMap;
|
|
11
|
+
return pageMap.map((item) => {
|
|
12
|
+
if ("data" in item) {
|
|
13
|
+
const data = Object.fromEntries(
|
|
14
|
+
Object.entries(item.data).map(([key, value]) => [key, localizeMetaValue(value, titles)])
|
|
15
|
+
);
|
|
16
|
+
return { ...item, data };
|
|
17
|
+
}
|
|
18
|
+
const copy = { ...item };
|
|
19
|
+
const title = item.title;
|
|
20
|
+
if (typeof title === "string" && titles[title]) copy.title = titles[title];
|
|
21
|
+
if ("frontMatter" in item && item.frontMatter && typeof item.frontMatter.title === "string") {
|
|
22
|
+
const title2 = titles[item.frontMatter.title];
|
|
23
|
+
if (title2) copy.frontMatter = { ...item.frontMatter, title: title2 };
|
|
24
|
+
}
|
|
25
|
+
if ("children" in item && Array.isArray(item.children)) copy.children = localizePageMap(item.children, titles);
|
|
26
|
+
return copy;
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
export {
|
|
30
|
+
localizePageMap
|
|
31
|
+
};
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var translationCheck_exports = {};
|
|
20
|
+
__export(translationCheck_exports, {
|
|
21
|
+
checkSiteTranslations: () => checkSiteTranslations,
|
|
22
|
+
checkTranslation: () => checkTranslation,
|
|
23
|
+
extractCodeBlocks: () => extractCodeBlocks,
|
|
24
|
+
extractHeadings: () => extractHeadings,
|
|
25
|
+
extractLinks: () => extractLinks,
|
|
26
|
+
loadNextraSlugger: () => loadNextraSlugger
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(translationCheck_exports);
|
|
29
|
+
var import_node_fs = require("node:fs");
|
|
30
|
+
var import_node_module = require("node:module");
|
|
31
|
+
var import_node_path = require("node:path");
|
|
32
|
+
var import_node_url = require("node:url");
|
|
33
|
+
var import_locale = require("./locale.js");
|
|
34
|
+
const FENCE_RE = /^ {0,3}(`{3,}|~{3,})/;
|
|
35
|
+
function splitFences(source) {
|
|
36
|
+
const blocks = [];
|
|
37
|
+
const prose = [];
|
|
38
|
+
let block = null;
|
|
39
|
+
let fence = "";
|
|
40
|
+
for (const line of source.split("\n")) {
|
|
41
|
+
if (block) {
|
|
42
|
+
block.push(line);
|
|
43
|
+
if (line.trim().startsWith(fence) && line.trim().replace(/[`~]/g, "") === "") {
|
|
44
|
+
blocks.push(block.join("\n"));
|
|
45
|
+
block = null;
|
|
46
|
+
}
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const open = FENCE_RE.exec(line);
|
|
50
|
+
if (open) {
|
|
51
|
+
fence = open[1];
|
|
52
|
+
block = [line];
|
|
53
|
+
} else {
|
|
54
|
+
prose.push(line);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (block) blocks.push(block.join("\n"));
|
|
58
|
+
return { blocks, prose: prose.join("\n") };
|
|
59
|
+
}
|
|
60
|
+
function extractCodeBlocks(source) {
|
|
61
|
+
return splitFences(source).blocks;
|
|
62
|
+
}
|
|
63
|
+
function plainText(markdown) {
|
|
64
|
+
return markdown.replace(/!?\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/(\*\*|__)(.+?)\1/g, "$2").replace(/(\*|_)(.+?)\1/g, "$2").replace(/`([^`]*)`/g, "$1").trim();
|
|
65
|
+
}
|
|
66
|
+
function extractHeadings(source) {
|
|
67
|
+
const headings = [];
|
|
68
|
+
for (const line of splitFences(source).prose.split("\n")) {
|
|
69
|
+
const match = /^ {0,3}(#{1,6})\s+(.+?)\s*#*\s*$/.exec(line);
|
|
70
|
+
if (!match) continue;
|
|
71
|
+
const custom = /\s*\[#([^\]]+)\]\s*$/.exec(match[2]);
|
|
72
|
+
const text = plainText(custom ? match[2].slice(0, custom.index) : match[2]);
|
|
73
|
+
headings.push({ depth: match[1].length, text, ...custom && { customId: custom[1] } });
|
|
74
|
+
}
|
|
75
|
+
return headings;
|
|
76
|
+
}
|
|
77
|
+
function extractLinks(source) {
|
|
78
|
+
const prose = splitFences(source).prose.replace(/`[^`\n]*`/g, "");
|
|
79
|
+
const targets = [];
|
|
80
|
+
for (const match of prose.matchAll(/\]\(\s*<?([^)\s>]+)>?(?:\s+"[^"]*")?\s*\)|\b(?:href|src)=["']([^"']+)["']/g)) {
|
|
81
|
+
targets.push(match[1] ?? match[2]);
|
|
82
|
+
}
|
|
83
|
+
return targets;
|
|
84
|
+
}
|
|
85
|
+
function headingIds(headings, createSlugger) {
|
|
86
|
+
const slug = createSlugger();
|
|
87
|
+
return headings.filter((heading) => heading.depth > 1).map((heading) => slug(heading.customId ?? heading.text));
|
|
88
|
+
}
|
|
89
|
+
const sorted = (values) => [...values].sort();
|
|
90
|
+
function checkTranslation(english, translated, createSlugger) {
|
|
91
|
+
const issues = [];
|
|
92
|
+
const englishBlocks = extractCodeBlocks(english);
|
|
93
|
+
const translatedBlocks = extractCodeBlocks(translated);
|
|
94
|
+
if (englishBlocks.length !== translatedBlocks.length) {
|
|
95
|
+
issues.push(`has ${translatedBlocks.length} code blocks, the English page ${englishBlocks.length}`);
|
|
96
|
+
} else {
|
|
97
|
+
englishBlocks.forEach((block, index) => {
|
|
98
|
+
if (block !== translatedBlocks[index]) issues.push(`code block ${index + 1} differs from the English one`);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
const englishHeadings = extractHeadings(english);
|
|
102
|
+
const translatedHeadings = extractHeadings(translated);
|
|
103
|
+
const levels = (headings) => headings.map((heading) => "#".repeat(heading.depth)).join(" ");
|
|
104
|
+
if (levels(englishHeadings) !== levels(translatedHeadings)) {
|
|
105
|
+
issues.push(`heading levels "${levels(translatedHeadings)}" differ from the English "${levels(englishHeadings)}"`);
|
|
106
|
+
} else if (createSlugger) {
|
|
107
|
+
const englishIds = headingIds(englishHeadings, createSlugger);
|
|
108
|
+
const translatedIds = headingIds(translatedHeadings, createSlugger);
|
|
109
|
+
englishIds.forEach((id, index) => {
|
|
110
|
+
if (id !== translatedIds[index]) {
|
|
111
|
+
issues.push(
|
|
112
|
+
`heading "${translatedHeadings.filter((h) => h.depth > 1)[index].text}" has the id "${translatedIds[index]}", the English one "${id}" (add [#${id}])`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
const englishLinks = sorted(extractLinks(english));
|
|
118
|
+
const translatedLinks = sorted(extractLinks(translated));
|
|
119
|
+
const missing = englishLinks.filter((link) => !translatedLinks.includes(link));
|
|
120
|
+
const extra = translatedLinks.filter((link) => !englishLinks.includes(link));
|
|
121
|
+
if (missing.length || extra.length || englishLinks.length !== translatedLinks.length) {
|
|
122
|
+
issues.push(
|
|
123
|
+
`links differ from the English page (missing: ${missing.join(", ") || "none"}; not in English: ${extra.join(", ") || "none"}; ${translatedLinks.length} vs ${englishLinks.length})`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
return issues;
|
|
127
|
+
}
|
|
128
|
+
function pageFiles(dir) {
|
|
129
|
+
if (!(0, import_node_fs.existsSync)(dir)) return [];
|
|
130
|
+
return (0, import_node_fs.readdirSync)(dir, { recursive: true, encoding: "utf8" }).filter((file) => [".md", ".mdx"].includes((0, import_node_path.extname)(file))).map((file) => file.split(import_node_path.sep).join("/")).sort();
|
|
131
|
+
}
|
|
132
|
+
const withoutExtension = (file) => file.slice(0, -(0, import_node_path.extname)(file).length);
|
|
133
|
+
async function loadNextraSlugger(cwd) {
|
|
134
|
+
try {
|
|
135
|
+
const nextra = (0, import_node_module.createRequire)((0, import_node_path.join)(cwd, "package.json")).resolve("nextra/package.json");
|
|
136
|
+
const sluggerPath = (0, import_node_module.createRequire)(nextra).resolve("github-slugger");
|
|
137
|
+
const { default: GithubSlugger } = await import((0, import_node_url.pathToFileURL)(sluggerPath).href);
|
|
138
|
+
return () => {
|
|
139
|
+
const slugger = new GithubSlugger();
|
|
140
|
+
return (text) => slugger.slug(text);
|
|
141
|
+
};
|
|
142
|
+
} catch {
|
|
143
|
+
return void 0;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async function checkSiteTranslations(options = {}) {
|
|
147
|
+
const { cwd = process.cwd(), contentDir = "src/content", i18nDir = "src/i18n" } = options;
|
|
148
|
+
if (!(0, import_node_fs.existsSync)((0, import_node_path.join)(cwd, contentDir))) {
|
|
149
|
+
throw new Error(`No ${contentDir} in ${cwd}: run the check from the docs app of the site`);
|
|
150
|
+
}
|
|
151
|
+
const createSlugger = options.createSlugger ?? await loadNextraSlugger(cwd);
|
|
152
|
+
const locales = import_locale.TUWA_LOCALES.filter((locale) => locale !== import_locale.DEFAULT_LOCALE);
|
|
153
|
+
const filesByLocale = new Map(locales.map((locale) => [locale, pageFiles((0, import_node_path.join)(cwd, i18nDir, locale))]));
|
|
154
|
+
const pages = [...new Set([...filesByLocale.values()].flat().map(withoutExtension))].sort();
|
|
155
|
+
const issues = [];
|
|
156
|
+
let files = 0;
|
|
157
|
+
for (const page of pages) {
|
|
158
|
+
const englishFile = [".mdx", ".md"].map((ext) => (0, import_node_path.join)(cwd, contentDir, page + ext)).find(import_node_fs.existsSync);
|
|
159
|
+
for (const locale of locales) {
|
|
160
|
+
const translatedFile = filesByLocale.get(locale)?.find((file) => withoutExtension(file) === page);
|
|
161
|
+
const shown = (0, import_node_path.join)(i18nDir, locale, translatedFile ?? `${page}.mdx`).split(import_node_path.sep).join("/");
|
|
162
|
+
if (!translatedFile) {
|
|
163
|
+
issues.push({ file: shown, message: "is missing: every translated page exists in every language" });
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
files += 1;
|
|
167
|
+
if (!englishFile) {
|
|
168
|
+
issues.push({ file: shown, message: `has no English page ${(0, import_node_path.join)(contentDir, page)}.mdx` });
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const english = (0, import_node_fs.readFileSync)(englishFile, "utf8");
|
|
172
|
+
const translated = (0, import_node_fs.readFileSync)((0, import_node_path.join)(cwd, i18nDir, locale, translatedFile), "utf8");
|
|
173
|
+
for (const message of checkTranslation(english, translated, createSlugger)) issues.push({ file: shown, message });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return { files, issues };
|
|
177
|
+
}
|
|
178
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
179
|
+
0 && (module.exports = {
|
|
180
|
+
checkSiteTranslations,
|
|
181
|
+
checkTranslation,
|
|
182
|
+
extractCodeBlocks,
|
|
183
|
+
extractHeadings,
|
|
184
|
+
extractLinks,
|
|
185
|
+
loadNextraSlugger
|
|
186
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/** A heading of a Markdown or MDX page (levels 1 to 6), outside code blocks. */
|
|
2
|
+
interface MarkdownHeading {
|
|
3
|
+
/** The level: 1 for `#`, 2 for `##`. */
|
|
4
|
+
depth: number;
|
|
5
|
+
/** The text without Markdown syntax and without the custom id. */
|
|
6
|
+
text: string;
|
|
7
|
+
/** The id written as `[#id]` at the end of the heading, as Nextra reads it. */
|
|
8
|
+
customId?: string;
|
|
9
|
+
}
|
|
10
|
+
/** Turns a heading text into an id the way Nextra does (`github-slugger`, one instance per page). */
|
|
11
|
+
type HeadingSlugger = () => (text: string) => string;
|
|
12
|
+
/** A problem of a translated page. */
|
|
13
|
+
interface TranslationIssue {
|
|
14
|
+
/** The translated file, relative to the docs app. */
|
|
15
|
+
file: string;
|
|
16
|
+
/** What does not match the English page. */
|
|
17
|
+
message: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The fenced code blocks of a Markdown or MDX page, in order, each with its opening line (language and meta) and its
|
|
21
|
+
* closing fence.
|
|
22
|
+
*
|
|
23
|
+
* @param source - The page source.
|
|
24
|
+
* @returns The code blocks.
|
|
25
|
+
*/
|
|
26
|
+
declare function extractCodeBlocks(source: string): string[];
|
|
27
|
+
/**
|
|
28
|
+
* The headings of a Markdown or MDX page, outside code blocks.
|
|
29
|
+
*
|
|
30
|
+
* @param source - The page source.
|
|
31
|
+
* @returns The headings, in order.
|
|
32
|
+
*/
|
|
33
|
+
declare function extractHeadings(source: string): MarkdownHeading[];
|
|
34
|
+
/**
|
|
35
|
+
* The link and image targets of a Markdown or MDX page outside code blocks: `[text](url)`, `href="url"` and
|
|
36
|
+
* `src="url"`.
|
|
37
|
+
*
|
|
38
|
+
* @param source - The page source.
|
|
39
|
+
* @returns The targets, in order.
|
|
40
|
+
*/
|
|
41
|
+
declare function extractLinks(source: string): string[];
|
|
42
|
+
/**
|
|
43
|
+
* Compares a translated page with its English page. A translation keeps the structure of the English page so readers
|
|
44
|
+
* of every language get the same page: the same code blocks in the same order (examples are never translated), the
|
|
45
|
+
* same heading levels with the English heading ids (written as `[#id]`, so anchors work in every language) and the
|
|
46
|
+
* same links.
|
|
47
|
+
*
|
|
48
|
+
* @param english - The source of the English page.
|
|
49
|
+
* @param translated - The source of the translation.
|
|
50
|
+
* @param createSlugger - Creates the heading id function of a page (`() => { const s = new GithubSlugger(); return (text) => s.slug(text); }`);
|
|
51
|
+
* without it the ids are not compared.
|
|
52
|
+
* @returns What does not match, empty when the translation keeps the structure.
|
|
53
|
+
*/
|
|
54
|
+
declare function checkTranslation(english: string, translated: string, createSlugger?: HeadingSlugger): string[];
|
|
55
|
+
/**
|
|
56
|
+
* Loads the heading slugger of Nextra (`github-slugger`, a dependency of `nextra`) from the site, so the check gives
|
|
57
|
+
* headings the ids the site gives them.
|
|
58
|
+
*
|
|
59
|
+
* @param cwd - The docs app, whose `node_modules` has `nextra`.
|
|
60
|
+
* @returns The slugger factory, or `undefined` when `nextra` or `github-slugger` cannot be found.
|
|
61
|
+
*/
|
|
62
|
+
declare function loadNextraSlugger(cwd: string): Promise<HeadingSlugger | undefined>;
|
|
63
|
+
/** Options of {@link checkSiteTranslations}. */
|
|
64
|
+
interface CheckSiteTranslationsOptions {
|
|
65
|
+
/** The docs app (default: the working directory). */
|
|
66
|
+
cwd?: string;
|
|
67
|
+
/** The English pages, relative to `cwd` (default: `src/content`). */
|
|
68
|
+
contentDir?: string;
|
|
69
|
+
/** The translations, one folder per language, relative to `cwd` (default: `src/i18n`). */
|
|
70
|
+
i18nDir?: string;
|
|
71
|
+
/** The heading slugger (default: {@link loadNextraSlugger}). */
|
|
72
|
+
createSlugger?: HeadingSlugger;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Checks the translated pages of a documentation site: `<i18nDir>/<locale>/<path>.mdx` translates
|
|
76
|
+
* `<contentDir>/<path>.mdx` (or the generated `.md`). Every translated page must exist in every language and keep the
|
|
77
|
+
* structure of its English page ({@link checkTranslation}). Reads files only.
|
|
78
|
+
*
|
|
79
|
+
* @param options - See {@link CheckSiteTranslationsOptions}.
|
|
80
|
+
* @returns The number of translated files and the issues found.
|
|
81
|
+
* @throws When `contentDir` does not exist (the check runs from the wrong directory).
|
|
82
|
+
*/
|
|
83
|
+
declare function checkSiteTranslations(options?: CheckSiteTranslationsOptions): Promise<{
|
|
84
|
+
files: number;
|
|
85
|
+
issues: TranslationIssue[];
|
|
86
|
+
}>;
|
|
87
|
+
|
|
88
|
+
export { checkSiteTranslations, checkTranslation, extractCodeBlocks, extractHeadings, extractLinks, loadNextraSlugger };
|
|
89
|
+
export type { CheckSiteTranslationsOptions, HeadingSlugger, MarkdownHeading, TranslationIssue };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/** A heading of a Markdown or MDX page (levels 1 to 6), outside code blocks. */
|
|
2
|
+
interface MarkdownHeading {
|
|
3
|
+
/** The level: 1 for `#`, 2 for `##`. */
|
|
4
|
+
depth: number;
|
|
5
|
+
/** The text without Markdown syntax and without the custom id. */
|
|
6
|
+
text: string;
|
|
7
|
+
/** The id written as `[#id]` at the end of the heading, as Nextra reads it. */
|
|
8
|
+
customId?: string;
|
|
9
|
+
}
|
|
10
|
+
/** Turns a heading text into an id the way Nextra does (`github-slugger`, one instance per page). */
|
|
11
|
+
type HeadingSlugger = () => (text: string) => string;
|
|
12
|
+
/** A problem of a translated page. */
|
|
13
|
+
interface TranslationIssue {
|
|
14
|
+
/** The translated file, relative to the docs app. */
|
|
15
|
+
file: string;
|
|
16
|
+
/** What does not match the English page. */
|
|
17
|
+
message: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The fenced code blocks of a Markdown or MDX page, in order, each with its opening line (language and meta) and its
|
|
21
|
+
* closing fence.
|
|
22
|
+
*
|
|
23
|
+
* @param source - The page source.
|
|
24
|
+
* @returns The code blocks.
|
|
25
|
+
*/
|
|
26
|
+
declare function extractCodeBlocks(source: string): string[];
|
|
27
|
+
/**
|
|
28
|
+
* The headings of a Markdown or MDX page, outside code blocks.
|
|
29
|
+
*
|
|
30
|
+
* @param source - The page source.
|
|
31
|
+
* @returns The headings, in order.
|
|
32
|
+
*/
|
|
33
|
+
declare function extractHeadings(source: string): MarkdownHeading[];
|
|
34
|
+
/**
|
|
35
|
+
* The link and image targets of a Markdown or MDX page outside code blocks: `[text](url)`, `href="url"` and
|
|
36
|
+
* `src="url"`.
|
|
37
|
+
*
|
|
38
|
+
* @param source - The page source.
|
|
39
|
+
* @returns The targets, in order.
|
|
40
|
+
*/
|
|
41
|
+
declare function extractLinks(source: string): string[];
|
|
42
|
+
/**
|
|
43
|
+
* Compares a translated page with its English page. A translation keeps the structure of the English page so readers
|
|
44
|
+
* of every language get the same page: the same code blocks in the same order (examples are never translated), the
|
|
45
|
+
* same heading levels with the English heading ids (written as `[#id]`, so anchors work in every language) and the
|
|
46
|
+
* same links.
|
|
47
|
+
*
|
|
48
|
+
* @param english - The source of the English page.
|
|
49
|
+
* @param translated - The source of the translation.
|
|
50
|
+
* @param createSlugger - Creates the heading id function of a page (`() => { const s = new GithubSlugger(); return (text) => s.slug(text); }`);
|
|
51
|
+
* without it the ids are not compared.
|
|
52
|
+
* @returns What does not match, empty when the translation keeps the structure.
|
|
53
|
+
*/
|
|
54
|
+
declare function checkTranslation(english: string, translated: string, createSlugger?: HeadingSlugger): string[];
|
|
55
|
+
/**
|
|
56
|
+
* Loads the heading slugger of Nextra (`github-slugger`, a dependency of `nextra`) from the site, so the check gives
|
|
57
|
+
* headings the ids the site gives them.
|
|
58
|
+
*
|
|
59
|
+
* @param cwd - The docs app, whose `node_modules` has `nextra`.
|
|
60
|
+
* @returns The slugger factory, or `undefined` when `nextra` or `github-slugger` cannot be found.
|
|
61
|
+
*/
|
|
62
|
+
declare function loadNextraSlugger(cwd: string): Promise<HeadingSlugger | undefined>;
|
|
63
|
+
/** Options of {@link checkSiteTranslations}. */
|
|
64
|
+
interface CheckSiteTranslationsOptions {
|
|
65
|
+
/** The docs app (default: the working directory). */
|
|
66
|
+
cwd?: string;
|
|
67
|
+
/** The English pages, relative to `cwd` (default: `src/content`). */
|
|
68
|
+
contentDir?: string;
|
|
69
|
+
/** The translations, one folder per language, relative to `cwd` (default: `src/i18n`). */
|
|
70
|
+
i18nDir?: string;
|
|
71
|
+
/** The heading slugger (default: {@link loadNextraSlugger}). */
|
|
72
|
+
createSlugger?: HeadingSlugger;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Checks the translated pages of a documentation site: `<i18nDir>/<locale>/<path>.mdx` translates
|
|
76
|
+
* `<contentDir>/<path>.mdx` (or the generated `.md`). Every translated page must exist in every language and keep the
|
|
77
|
+
* structure of its English page ({@link checkTranslation}). Reads files only.
|
|
78
|
+
*
|
|
79
|
+
* @param options - See {@link CheckSiteTranslationsOptions}.
|
|
80
|
+
* @returns The number of translated files and the issues found.
|
|
81
|
+
* @throws When `contentDir` does not exist (the check runs from the wrong directory).
|
|
82
|
+
*/
|
|
83
|
+
declare function checkSiteTranslations(options?: CheckSiteTranslationsOptions): Promise<{
|
|
84
|
+
files: number;
|
|
85
|
+
issues: TranslationIssue[];
|
|
86
|
+
}>;
|
|
87
|
+
|
|
88
|
+
export { checkSiteTranslations, checkTranslation, extractCodeBlocks, extractHeadings, extractLinks, loadNextraSlugger };
|
|
89
|
+
export type { CheckSiteTranslationsOptions, HeadingSlugger, MarkdownHeading, TranslationIssue };
|