@dualkit/convert 0.3.0-beta.0 → 0.4.0-beta.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/dist/index.cjs +87 -8
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +87 -8
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -151,6 +151,11 @@ function detectPackageManager(root) {
|
|
|
151
151
|
if (top || (0, import_node_path.dirname)(dir) === dir) return "npm";
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
|
+
var I18N_LIBRARIES = ["next-intl", "react-i18next", "i18next", "next-i18next", "react-intl", "@lingui/core", "next-international", "@inlang/paraglide-js"];
|
|
155
|
+
function existingI18n(root) {
|
|
156
|
+
const deps = readDeps(root);
|
|
157
|
+
return I18N_LIBRARIES.find((name) => name in deps) ?? null;
|
|
158
|
+
}
|
|
154
159
|
function detectI18n(deps) {
|
|
155
160
|
if ("next-intl" in deps) return "next-intl";
|
|
156
161
|
if ("react-i18next" in deps) return "react-i18next";
|
|
@@ -327,7 +332,7 @@ async function stagedFiles(root) {
|
|
|
327
332
|
return out.split("\0").filter(Boolean);
|
|
328
333
|
}
|
|
329
334
|
async function commit(root, message) {
|
|
330
|
-
await must(root, ["commit", "-q", "-m", message]);
|
|
335
|
+
await must(root, ["commit", "-q", "--no-verify", "-m", message]);
|
|
331
336
|
}
|
|
332
337
|
async function trackedFiles(root) {
|
|
333
338
|
const out = await must(root, ["ls-files", "-z"]);
|
|
@@ -562,6 +567,26 @@ function writeConfigAnswers(disk, answers2, file = CONFIG_FILE) {
|
|
|
562
567
|
return writeText(disk, file, mergedConfigText(readText(disk.root, file), answers2, file));
|
|
563
568
|
}
|
|
564
569
|
|
|
570
|
+
// src/closed-routes.ts
|
|
571
|
+
var CLOSED = /* @__PURE__ */ new Set([401, 403, 404]);
|
|
572
|
+
var STATUS_TIMEOUT_MS = 6e4;
|
|
573
|
+
var closedStatus = async (url) => {
|
|
574
|
+
try {
|
|
575
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(STATUS_TIMEOUT_MS), redirect: "manual" });
|
|
576
|
+
return CLOSED.has(res.status) ? res.status : null;
|
|
577
|
+
} catch {
|
|
578
|
+
return null;
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
async function closedRoutes(base, routes) {
|
|
582
|
+
const found = [];
|
|
583
|
+
for (const route of routes) {
|
|
584
|
+
const status = await closedStatus(`${base}${route}`);
|
|
585
|
+
if (status) found.push({ route, reason: `the English page returns ${status}, so its Arabic page was not checked` });
|
|
586
|
+
}
|
|
587
|
+
return found;
|
|
588
|
+
}
|
|
589
|
+
|
|
565
590
|
// src/baseline.ts
|
|
566
591
|
var import_node_fs8 = require("fs");
|
|
567
592
|
var import_node_path9 = require("path");
|
|
@@ -706,6 +731,7 @@ ${tail2()}`);
|
|
|
706
731
|
|
|
707
732
|
// src/baseline.ts
|
|
708
733
|
var BASELINE_DIR = `${STATE_DIR}/baseline`;
|
|
734
|
+
var CLOSED_FILE = `${BASELINE_DIR}/closed.json`;
|
|
709
735
|
var WIDTHS = [390, 1280];
|
|
710
736
|
var DEFAULT_PORTS = { "next-app": 3e3, "vite-react": 5173 };
|
|
711
737
|
var routeSlug = (route) => route.replace(/^\/+|\/+$/g, "").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-|-$/g, "") || "home";
|
|
@@ -717,6 +743,7 @@ async function shootBaseline(project, config, shoot, log) {
|
|
|
717
743
|
const server = await startServer(config.start, port, project.root);
|
|
718
744
|
const shots = [];
|
|
719
745
|
try {
|
|
746
|
+
(0, import_node_fs8.writeFileSync)((0, import_node_path9.join)(project.root, CLOSED_FILE), JSON.stringify((await closedRoutes(server.url, routes)).map((c) => c.route)));
|
|
720
747
|
for (const route of routes) {
|
|
721
748
|
for (const width of WIDTHS) {
|
|
722
749
|
const path = baselinePath(route, width);
|
|
@@ -1093,6 +1120,7 @@ function pieceOf(child, ctx) {
|
|
|
1093
1120
|
const source = ctx.file.text.slice(ctx.base + expression.start, ctx.base + expression.end);
|
|
1094
1121
|
return { kind: "value", name, source: mayBeEmpty(expression) ? `${source} ?? ''` : source, start: start2, end };
|
|
1095
1122
|
}
|
|
1123
|
+
var OWN_DIRECTION = /setAttribute\(\s*['"]dir['"]|documentElement\.dir\s*=[^=]/;
|
|
1096
1124
|
var NODE_NAMES = /^(children|icon|avatar|logo|image|element|node)$/i;
|
|
1097
1125
|
var mayBeEmpty = (expression) => /^Optional/.test(expression.type) || expression.type === "CallExpression" && !textMethodTarget(expression);
|
|
1098
1126
|
var jsxText = (value) => value.replace(/^\s*\n\s*/, "").replace(/\s*\n\s*$/, "");
|
|
@@ -1342,6 +1370,15 @@ function extractStrings(file, lib, taken) {
|
|
|
1342
1370
|
for (const block of file.js) {
|
|
1343
1371
|
const program = block.ast.program;
|
|
1344
1372
|
if (isServerFile(program)) continue;
|
|
1373
|
+
if (OWN_DIRECTION.test(file.text.slice(block.offset))) {
|
|
1374
|
+
flag(
|
|
1375
|
+
{ file, flags },
|
|
1376
|
+
block.offset,
|
|
1377
|
+
"unsupported",
|
|
1378
|
+
"This file sets the page direction itself, which can clash with the Arabic layout.",
|
|
1379
|
+
"Check the Arabic pages, and drive this direction from the locale so components read it too."
|
|
1380
|
+
);
|
|
1381
|
+
}
|
|
1345
1382
|
const ctx = { file, base: block.offset, lib, stem: stemOf(file.path), used, byText: /* @__PURE__ */ new Map(), entries, edits: edits2, flags, components: /* @__PURE__ */ new Set(), sawJsx: false, consumed: /* @__PURE__ */ new Set(), usedTrans: false };
|
|
1346
1383
|
walk(program, ctx, []);
|
|
1347
1384
|
if (ctx.sawJsx) flagModuleStrings(program, ctx);
|
|
@@ -2096,7 +2133,7 @@ export function DualkitLanguageSwitch() {
|
|
|
2096
2133
|
)
|
|
2097
2134
|
}
|
|
2098
2135
|
`;
|
|
2099
|
-
var INLINE_SCRIPT = `<script>(function(){var m=document.cookie.match(${COOKIE_PATTERN});var l=m&&m[1]==='ar'?'ar':'en';var h=document.documentElement;h.lang=l;h.dir=l==='ar'?'rtl':'ltr'})()</script>`;
|
|
2136
|
+
var INLINE_SCRIPT = `<script>(function(){var m=document.cookie.match(${COOKIE_PATTERN});var l=m&&m[1]==='ar'?'ar':'en';var h=document.documentElement;h.lang=l;h.dir=l==='ar'?'rtl':'ltr';if(l==='ar')new MutationObserver(function(){if(h.dir!=='rtl')h.dir='rtl'}).observe(h,{attributes:true,attributeFilter:['dir']})})()</script>`;
|
|
2100
2137
|
function htmlEdits2(html, mode) {
|
|
2101
2138
|
if (mode === "bilingual") {
|
|
2102
2139
|
if (html.includes(LOCALE_COOKIE)) return [];
|
|
@@ -2692,12 +2729,27 @@ function visitExpression(container, ancestors, ctx) {
|
|
|
2692
2729
|
ctx.edits.push({ start: ctx.base + container.start, end: ctx.base + container.start, text: "<bdi>" });
|
|
2693
2730
|
ctx.edits.push({ start: ctx.base + container.end, end: ctx.base + container.end, text: "</bdi>" });
|
|
2694
2731
|
}
|
|
2732
|
+
var SIGNED_NUMBER = /^(?:[+\-\u2212]\d[\d,.]*\s?%?|\d[\d,.]*\s?%)$/;
|
|
2733
|
+
var NO_BDI = /* @__PURE__ */ new Set(["svg", "text", "option", "title", "textarea", "style", "script"]);
|
|
2734
|
+
function visitText(text, ancestors, ctx) {
|
|
2735
|
+
const parent = ancestors[ancestors.length - 1];
|
|
2736
|
+
if (parent.type !== "JSXElement" && parent.type !== "JSXFragment") return;
|
|
2737
|
+
const value = String(text.value);
|
|
2738
|
+
const trimmed = value.trim();
|
|
2739
|
+
if (!SIGNED_NUMBER.test(trimmed)) return;
|
|
2740
|
+
const elements = ancestors.filter((a) => a.type === "JSXElement");
|
|
2741
|
+
if (elements.some((a) => isolates(a) || NO_BDI.has(tagName2(a.openingElement) ?? ""))) return;
|
|
2742
|
+
const start2 = ctx.base + text.start + value.indexOf(trimmed);
|
|
2743
|
+
ctx.edits.push({ start: start2, end: start2, text: "<bdi>" });
|
|
2744
|
+
ctx.edits.push({ start: start2 + trimmed.length, end: start2 + trimmed.length, text: "</bdi>" });
|
|
2745
|
+
}
|
|
2695
2746
|
function walk3(node, ctx, ancestors) {
|
|
2696
2747
|
if (Array.isArray(node)) return node.forEach((child) => walk3(child, ctx, ancestors));
|
|
2697
2748
|
if (!node || typeof node !== "object") return;
|
|
2698
2749
|
const n = node;
|
|
2699
2750
|
if (n.type === "JSXOpeningElement") visitOpening(n, ctx, ancestors);
|
|
2700
2751
|
if (n.type === "JSXExpressionContainer" && ancestors.length) visitExpression(n, ancestors, ctx);
|
|
2752
|
+
if (n.type === "JSXText" && ancestors.length) visitText(n, ancestors, ctx);
|
|
2701
2753
|
const next = typeof n.type === "string" ? [...ancestors, n] : ancestors;
|
|
2702
2754
|
for (const [key, child] of Object.entries(n)) if (!SKIP6.has(key)) walk3(child, ctx, next);
|
|
2703
2755
|
}
|
|
@@ -2839,6 +2891,15 @@ function groups(fragments) {
|
|
|
2839
2891
|
for (const f of fragments) byGroup.set(f.group, [...byGroup.get(f.group) ?? [], f]);
|
|
2840
2892
|
return [...byGroup.values()];
|
|
2841
2893
|
}
|
|
2894
|
+
var TRACKED = /^tracking-(?!normal$)/;
|
|
2895
|
+
function trackingEdits(fragments) {
|
|
2896
|
+
const tokens = tokensOf(fragments);
|
|
2897
|
+
if (tokens.some((t) => t.parsed.core.startsWith("tracking-") && t.parsed.variants.includes("rtl"))) return [];
|
|
2898
|
+
const first = tokens.find((t) => TRACKED.test(t.parsed.core) && KEPT(t));
|
|
2899
|
+
if (!first) return [];
|
|
2900
|
+
const end = first.at + first.text.length;
|
|
2901
|
+
return [{ start: end, end, text: " rtl:tracking-normal" }];
|
|
2902
|
+
}
|
|
2842
2903
|
function tailwindEdits(file, version) {
|
|
2843
2904
|
const parsed = parseVersion(version);
|
|
2844
2905
|
const fragments = (0, import_lint10.classStrings)(file);
|
|
@@ -2851,7 +2912,7 @@ function tailwindEdits(file, version) {
|
|
|
2851
2912
|
return { edits: [], flags: [{ file: file.path, line: lineOf(file.text, first.offset), kind: "unsupported", message, suggestion }] };
|
|
2852
2913
|
}
|
|
2853
2914
|
const sources = groups(fragments);
|
|
2854
|
-
const edits2 = sources.flatMap((g) => [...physicalEdits(g, parsed), ...parsed[0] < 4 ? reverseEdits(g) : []]);
|
|
2915
|
+
const edits2 = sources.flatMap((g) => [...physicalEdits(g, parsed), ...parsed[0] < 4 ? reverseEdits(g) : [], ...trackingEdits(g)]);
|
|
2855
2916
|
const flags = [...sources.flatMap((g) => formFlags(file, g)), ...computedFlags(file)];
|
|
2856
2917
|
return { edits: edits2, flags: flags.sort((a, b) => a.line - b.line) };
|
|
2857
2918
|
}
|
|
@@ -3096,6 +3157,8 @@ async function convert(options, ioIn) {
|
|
|
3096
3157
|
if (options.configAnswers) checkConfigAnswers(root, options.configFile);
|
|
3097
3158
|
const io = options.configAnswers ? { ...ioIn, config: { ...ioIn.config, ...options.configAnswers } } : ioIn;
|
|
3098
3159
|
const project = detectProject(root);
|
|
3160
|
+
const library = rerun ? null : existingI18n(root);
|
|
3161
|
+
if (library) throw new UnsupportedError(`This app already uses ${library}. Convert adds its own translation setup and the two collide, so nothing was changed. Convert supports apps with no translation library yet.`);
|
|
3099
3162
|
const branch = branchFor(mode);
|
|
3100
3163
|
const generated = new Set(state?.generated ?? []);
|
|
3101
3164
|
const existing = readLedger(root);
|
|
@@ -3269,7 +3332,8 @@ function english(data) {
|
|
|
3269
3332
|
}
|
|
3270
3333
|
function arabic(data) {
|
|
3271
3334
|
const lines = data.arabic.map(
|
|
3272
|
-
(p) => !Number.isFinite(p.errors) ? `- ${esc(p.route)}: the check returned no result` : `- ${esc(p.route)}: score ${p.score}, ${plural(p.errors, "error", "errors")}, ${plural(p.warnings, "warning", "warnings")}`
|
|
3335
|
+
(p) => !Number.isFinite(p.errors) ? `- ${esc(p.route)}: ${esc(p.problems?.[0] ?? "the check returned no result")}` : `- ${esc(p.route)}: score ${p.score}, ${plural(p.errors, "error", "errors")}, ${plural(p.warnings, "warning", "warnings")}${(p.problems ?? []).map((m) => `
|
|
3336
|
+
- ${esc(m)}`).join("")}`
|
|
3273
3337
|
);
|
|
3274
3338
|
return section("Arabic pages", lines, "No pages were checked.");
|
|
3275
3339
|
}
|
|
@@ -3385,11 +3449,21 @@ async function arabicChecks(project, state, base, routes, audit) {
|
|
|
3385
3449
|
const result = await audit(`${base}${path}`, width, options);
|
|
3386
3450
|
const usable = result && Number.isFinite(result.errors);
|
|
3387
3451
|
const failed = { score: Number.NaN, errors: Number.NaN, warnings: Number.NaN };
|
|
3388
|
-
arabic2.push({ route: `${path} at ${width}px`, ...usable ? result : failed });
|
|
3452
|
+
arabic2.push({ route: `${path} at ${width}px`, ...usable ? result : { ...failed, ...result?.problems ? { problems: result.problems } : {} } });
|
|
3389
3453
|
}
|
|
3390
3454
|
}
|
|
3391
3455
|
return arabic2;
|
|
3392
3456
|
}
|
|
3457
|
+
function closedAtBaseline(root) {
|
|
3458
|
+
const path = (0, import_node_path19.join)(root, CLOSED_FILE);
|
|
3459
|
+
if (!(0, import_node_fs11.existsSync)(path)) return /* @__PURE__ */ new Set();
|
|
3460
|
+
try {
|
|
3461
|
+
const routes = JSON.parse((0, import_node_fs11.readFileSync)(path, "utf8"));
|
|
3462
|
+
return new Set(Array.isArray(routes) ? routes.filter((r) => typeof r === "string") : []);
|
|
3463
|
+
} catch {
|
|
3464
|
+
return /* @__PURE__ */ new Set();
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3393
3467
|
async function runChecks(project, state, routes, { config, shoot, audit }, step) {
|
|
3394
3468
|
const { start: cmd } = config;
|
|
3395
3469
|
if (!cmd || !shoot) throw new RefusedError(NEEDS_BROWSER);
|
|
@@ -3400,8 +3474,13 @@ async function runChecks(project, state, routes, { config, shoot, audit }, step)
|
|
|
3400
3474
|
});
|
|
3401
3475
|
try {
|
|
3402
3476
|
step.name = "checking the pages";
|
|
3403
|
-
const
|
|
3404
|
-
|
|
3477
|
+
const closed = await closedRoutes(server.url, routes);
|
|
3478
|
+
const shut = new Set(closed.map((c) => c.route));
|
|
3479
|
+
const open = routes.filter((route) => !shut.has(route));
|
|
3480
|
+
const before = closedAtBaseline(project.root);
|
|
3481
|
+
const compared = routes.filter((route) => !(shut.has(route) && before.has(route)));
|
|
3482
|
+
const english2 = state.mode === "bilingual" ? await englishChecks(project.root, server.url, compared, shoot) : { english: [], noBaseline: [] };
|
|
3483
|
+
return { ...english2, closed, arabic: await arabicChecks(project, state, server.url, open, audit) };
|
|
3405
3484
|
} finally {
|
|
3406
3485
|
await server.stop();
|
|
3407
3486
|
}
|
|
@@ -3442,7 +3521,7 @@ async function run(ready, step) {
|
|
|
3442
3521
|
ledger,
|
|
3443
3522
|
...checks,
|
|
3444
3523
|
...earlier ? {} : { summaryMissing: true },
|
|
3445
|
-
unreachable,
|
|
3524
|
+
unreachable: [...unreachable, ...checks.closed],
|
|
3446
3525
|
remainingLint,
|
|
3447
3526
|
...tests2 ? { tests: { code: tests2.code, tail: tail(tests2.out) } } : {}
|
|
3448
3527
|
};
|
package/dist/index.d.cts
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -116,6 +116,11 @@ function detectPackageManager(root) {
|
|
|
116
116
|
if (top || dirname(dir) === dir) return "npm";
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
|
+
var I18N_LIBRARIES = ["next-intl", "react-i18next", "i18next", "next-i18next", "react-intl", "@lingui/core", "next-international", "@inlang/paraglide-js"];
|
|
120
|
+
function existingI18n(root) {
|
|
121
|
+
const deps = readDeps(root);
|
|
122
|
+
return I18N_LIBRARIES.find((name) => name in deps) ?? null;
|
|
123
|
+
}
|
|
119
124
|
function detectI18n(deps) {
|
|
120
125
|
if ("next-intl" in deps) return "next-intl";
|
|
121
126
|
if ("react-i18next" in deps) return "react-i18next";
|
|
@@ -292,7 +297,7 @@ async function stagedFiles(root) {
|
|
|
292
297
|
return out.split("\0").filter(Boolean);
|
|
293
298
|
}
|
|
294
299
|
async function commit(root, message) {
|
|
295
|
-
await must(root, ["commit", "-q", "-m", message]);
|
|
300
|
+
await must(root, ["commit", "-q", "--no-verify", "-m", message]);
|
|
296
301
|
}
|
|
297
302
|
async function trackedFiles(root) {
|
|
298
303
|
const out = await must(root, ["ls-files", "-z"]);
|
|
@@ -527,6 +532,26 @@ function writeConfigAnswers(disk, answers2, file = CONFIG_FILE) {
|
|
|
527
532
|
return writeText(disk, file, mergedConfigText(readText(disk.root, file), answers2, file));
|
|
528
533
|
}
|
|
529
534
|
|
|
535
|
+
// src/closed-routes.ts
|
|
536
|
+
var CLOSED = /* @__PURE__ */ new Set([401, 403, 404]);
|
|
537
|
+
var STATUS_TIMEOUT_MS = 6e4;
|
|
538
|
+
var closedStatus = async (url) => {
|
|
539
|
+
try {
|
|
540
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(STATUS_TIMEOUT_MS), redirect: "manual" });
|
|
541
|
+
return CLOSED.has(res.status) ? res.status : null;
|
|
542
|
+
} catch {
|
|
543
|
+
return null;
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
async function closedRoutes(base, routes) {
|
|
547
|
+
const found = [];
|
|
548
|
+
for (const route of routes) {
|
|
549
|
+
const status = await closedStatus(`${base}${route}`);
|
|
550
|
+
if (status) found.push({ route, reason: `the English page returns ${status}, so its Arabic page was not checked` });
|
|
551
|
+
}
|
|
552
|
+
return found;
|
|
553
|
+
}
|
|
554
|
+
|
|
530
555
|
// src/baseline.ts
|
|
531
556
|
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
532
557
|
import { join as join7 } from "path";
|
|
@@ -671,6 +696,7 @@ ${tail2()}`);
|
|
|
671
696
|
|
|
672
697
|
// src/baseline.ts
|
|
673
698
|
var BASELINE_DIR = `${STATE_DIR}/baseline`;
|
|
699
|
+
var CLOSED_FILE = `${BASELINE_DIR}/closed.json`;
|
|
674
700
|
var WIDTHS = [390, 1280];
|
|
675
701
|
var DEFAULT_PORTS = { "next-app": 3e3, "vite-react": 5173 };
|
|
676
702
|
var routeSlug = (route) => route.replace(/^\/+|\/+$/g, "").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-|-$/g, "") || "home";
|
|
@@ -682,6 +708,7 @@ async function shootBaseline(project, config, shoot, log) {
|
|
|
682
708
|
const server = await startServer(config.start, port, project.root);
|
|
683
709
|
const shots = [];
|
|
684
710
|
try {
|
|
711
|
+
writeFileSync5(join7(project.root, CLOSED_FILE), JSON.stringify((await closedRoutes(server.url, routes)).map((c) => c.route)));
|
|
685
712
|
for (const route of routes) {
|
|
686
713
|
for (const width of WIDTHS) {
|
|
687
714
|
const path = baselinePath(route, width);
|
|
@@ -1058,6 +1085,7 @@ function pieceOf(child, ctx) {
|
|
|
1058
1085
|
const source = ctx.file.text.slice(ctx.base + expression.start, ctx.base + expression.end);
|
|
1059
1086
|
return { kind: "value", name, source: mayBeEmpty(expression) ? `${source} ?? ''` : source, start: start2, end };
|
|
1060
1087
|
}
|
|
1088
|
+
var OWN_DIRECTION = /setAttribute\(\s*['"]dir['"]|documentElement\.dir\s*=[^=]/;
|
|
1061
1089
|
var NODE_NAMES = /^(children|icon|avatar|logo|image|element|node)$/i;
|
|
1062
1090
|
var mayBeEmpty = (expression) => /^Optional/.test(expression.type) || expression.type === "CallExpression" && !textMethodTarget(expression);
|
|
1063
1091
|
var jsxText = (value) => value.replace(/^\s*\n\s*/, "").replace(/\s*\n\s*$/, "");
|
|
@@ -1307,6 +1335,15 @@ function extractStrings(file, lib, taken) {
|
|
|
1307
1335
|
for (const block of file.js) {
|
|
1308
1336
|
const program = block.ast.program;
|
|
1309
1337
|
if (isServerFile(program)) continue;
|
|
1338
|
+
if (OWN_DIRECTION.test(file.text.slice(block.offset))) {
|
|
1339
|
+
flag(
|
|
1340
|
+
{ file, flags },
|
|
1341
|
+
block.offset,
|
|
1342
|
+
"unsupported",
|
|
1343
|
+
"This file sets the page direction itself, which can clash with the Arabic layout.",
|
|
1344
|
+
"Check the Arabic pages, and drive this direction from the locale so components read it too."
|
|
1345
|
+
);
|
|
1346
|
+
}
|
|
1310
1347
|
const ctx = { file, base: block.offset, lib, stem: stemOf(file.path), used, byText: /* @__PURE__ */ new Map(), entries, edits: edits2, flags, components: /* @__PURE__ */ new Set(), sawJsx: false, consumed: /* @__PURE__ */ new Set(), usedTrans: false };
|
|
1311
1348
|
walk(program, ctx, []);
|
|
1312
1349
|
if (ctx.sawJsx) flagModuleStrings(program, ctx);
|
|
@@ -2061,7 +2098,7 @@ export function DualkitLanguageSwitch() {
|
|
|
2061
2098
|
)
|
|
2062
2099
|
}
|
|
2063
2100
|
`;
|
|
2064
|
-
var INLINE_SCRIPT = `<script>(function(){var m=document.cookie.match(${COOKIE_PATTERN});var l=m&&m[1]==='ar'?'ar':'en';var h=document.documentElement;h.lang=l;h.dir=l==='ar'?'rtl':'ltr'})()</script>`;
|
|
2101
|
+
var INLINE_SCRIPT = `<script>(function(){var m=document.cookie.match(${COOKIE_PATTERN});var l=m&&m[1]==='ar'?'ar':'en';var h=document.documentElement;h.lang=l;h.dir=l==='ar'?'rtl':'ltr';if(l==='ar')new MutationObserver(function(){if(h.dir!=='rtl')h.dir='rtl'}).observe(h,{attributes:true,attributeFilter:['dir']})})()</script>`;
|
|
2065
2102
|
function htmlEdits2(html, mode) {
|
|
2066
2103
|
if (mode === "bilingual") {
|
|
2067
2104
|
if (html.includes(LOCALE_COOKIE)) return [];
|
|
@@ -2657,12 +2694,27 @@ function visitExpression(container, ancestors, ctx) {
|
|
|
2657
2694
|
ctx.edits.push({ start: ctx.base + container.start, end: ctx.base + container.start, text: "<bdi>" });
|
|
2658
2695
|
ctx.edits.push({ start: ctx.base + container.end, end: ctx.base + container.end, text: "</bdi>" });
|
|
2659
2696
|
}
|
|
2697
|
+
var SIGNED_NUMBER = /^(?:[+\-\u2212]\d[\d,.]*\s?%?|\d[\d,.]*\s?%)$/;
|
|
2698
|
+
var NO_BDI = /* @__PURE__ */ new Set(["svg", "text", "option", "title", "textarea", "style", "script"]);
|
|
2699
|
+
function visitText(text, ancestors, ctx) {
|
|
2700
|
+
const parent = ancestors[ancestors.length - 1];
|
|
2701
|
+
if (parent.type !== "JSXElement" && parent.type !== "JSXFragment") return;
|
|
2702
|
+
const value = String(text.value);
|
|
2703
|
+
const trimmed = value.trim();
|
|
2704
|
+
if (!SIGNED_NUMBER.test(trimmed)) return;
|
|
2705
|
+
const elements = ancestors.filter((a) => a.type === "JSXElement");
|
|
2706
|
+
if (elements.some((a) => isolates(a) || NO_BDI.has(tagName2(a.openingElement) ?? ""))) return;
|
|
2707
|
+
const start2 = ctx.base + text.start + value.indexOf(trimmed);
|
|
2708
|
+
ctx.edits.push({ start: start2, end: start2, text: "<bdi>" });
|
|
2709
|
+
ctx.edits.push({ start: start2 + trimmed.length, end: start2 + trimmed.length, text: "</bdi>" });
|
|
2710
|
+
}
|
|
2660
2711
|
function walk3(node, ctx, ancestors) {
|
|
2661
2712
|
if (Array.isArray(node)) return node.forEach((child) => walk3(child, ctx, ancestors));
|
|
2662
2713
|
if (!node || typeof node !== "object") return;
|
|
2663
2714
|
const n = node;
|
|
2664
2715
|
if (n.type === "JSXOpeningElement") visitOpening(n, ctx, ancestors);
|
|
2665
2716
|
if (n.type === "JSXExpressionContainer" && ancestors.length) visitExpression(n, ancestors, ctx);
|
|
2717
|
+
if (n.type === "JSXText" && ancestors.length) visitText(n, ancestors, ctx);
|
|
2666
2718
|
const next = typeof n.type === "string" ? [...ancestors, n] : ancestors;
|
|
2667
2719
|
for (const [key, child] of Object.entries(n)) if (!SKIP6.has(key)) walk3(child, ctx, next);
|
|
2668
2720
|
}
|
|
@@ -2804,6 +2856,15 @@ function groups(fragments) {
|
|
|
2804
2856
|
for (const f of fragments) byGroup.set(f.group, [...byGroup.get(f.group) ?? [], f]);
|
|
2805
2857
|
return [...byGroup.values()];
|
|
2806
2858
|
}
|
|
2859
|
+
var TRACKED = /^tracking-(?!normal$)/;
|
|
2860
|
+
function trackingEdits(fragments) {
|
|
2861
|
+
const tokens = tokensOf(fragments);
|
|
2862
|
+
if (tokens.some((t) => t.parsed.core.startsWith("tracking-") && t.parsed.variants.includes("rtl"))) return [];
|
|
2863
|
+
const first = tokens.find((t) => TRACKED.test(t.parsed.core) && KEPT(t));
|
|
2864
|
+
if (!first) return [];
|
|
2865
|
+
const end = first.at + first.text.length;
|
|
2866
|
+
return [{ start: end, end, text: " rtl:tracking-normal" }];
|
|
2867
|
+
}
|
|
2807
2868
|
function tailwindEdits(file, version) {
|
|
2808
2869
|
const parsed = parseVersion(version);
|
|
2809
2870
|
const fragments = classStrings(file);
|
|
@@ -2816,7 +2877,7 @@ function tailwindEdits(file, version) {
|
|
|
2816
2877
|
return { edits: [], flags: [{ file: file.path, line: lineOf(file.text, first.offset), kind: "unsupported", message, suggestion }] };
|
|
2817
2878
|
}
|
|
2818
2879
|
const sources = groups(fragments);
|
|
2819
|
-
const edits2 = sources.flatMap((g) => [...physicalEdits(g, parsed), ...parsed[0] < 4 ? reverseEdits(g) : []]);
|
|
2880
|
+
const edits2 = sources.flatMap((g) => [...physicalEdits(g, parsed), ...parsed[0] < 4 ? reverseEdits(g) : [], ...trackingEdits(g)]);
|
|
2820
2881
|
const flags = [...sources.flatMap((g) => formFlags(file, g)), ...computedFlags(file)];
|
|
2821
2882
|
return { edits: edits2, flags: flags.sort((a, b) => a.line - b.line) };
|
|
2822
2883
|
}
|
|
@@ -3061,6 +3122,8 @@ async function convert(options, ioIn) {
|
|
|
3061
3122
|
if (options.configAnswers) checkConfigAnswers(root, options.configFile);
|
|
3062
3123
|
const io = options.configAnswers ? { ...ioIn, config: { ...ioIn.config, ...options.configAnswers } } : ioIn;
|
|
3063
3124
|
const project = detectProject(root);
|
|
3125
|
+
const library = rerun ? null : existingI18n(root);
|
|
3126
|
+
if (library) throw new UnsupportedError(`This app already uses ${library}. Convert adds its own translation setup and the two collide, so nothing was changed. Convert supports apps with no translation library yet.`);
|
|
3064
3127
|
const branch = branchFor(mode);
|
|
3065
3128
|
const generated = new Set(state?.generated ?? []);
|
|
3066
3129
|
const existing = readLedger(root);
|
|
@@ -3234,7 +3297,8 @@ function english(data) {
|
|
|
3234
3297
|
}
|
|
3235
3298
|
function arabic(data) {
|
|
3236
3299
|
const lines = data.arabic.map(
|
|
3237
|
-
(p) => !Number.isFinite(p.errors) ? `- ${esc(p.route)}: the check returned no result` : `- ${esc(p.route)}: score ${p.score}, ${plural(p.errors, "error", "errors")}, ${plural(p.warnings, "warning", "warnings")}`
|
|
3300
|
+
(p) => !Number.isFinite(p.errors) ? `- ${esc(p.route)}: ${esc(p.problems?.[0] ?? "the check returned no result")}` : `- ${esc(p.route)}: score ${p.score}, ${plural(p.errors, "error", "errors")}, ${plural(p.warnings, "warning", "warnings")}${(p.problems ?? []).map((m) => `
|
|
3301
|
+
- ${esc(m)}`).join("")}`
|
|
3238
3302
|
);
|
|
3239
3303
|
return section("Arabic pages", lines, "No pages were checked.");
|
|
3240
3304
|
}
|
|
@@ -3350,11 +3414,21 @@ async function arabicChecks(project, state, base, routes, audit) {
|
|
|
3350
3414
|
const result = await audit(`${base}${path}`, width, options);
|
|
3351
3415
|
const usable = result && Number.isFinite(result.errors);
|
|
3352
3416
|
const failed = { score: Number.NaN, errors: Number.NaN, warnings: Number.NaN };
|
|
3353
|
-
arabic2.push({ route: `${path} at ${width}px`, ...usable ? result : failed });
|
|
3417
|
+
arabic2.push({ route: `${path} at ${width}px`, ...usable ? result : { ...failed, ...result?.problems ? { problems: result.problems } : {} } });
|
|
3354
3418
|
}
|
|
3355
3419
|
}
|
|
3356
3420
|
return arabic2;
|
|
3357
3421
|
}
|
|
3422
|
+
function closedAtBaseline(root) {
|
|
3423
|
+
const path = join11(root, CLOSED_FILE);
|
|
3424
|
+
if (!existsSync8(path)) return /* @__PURE__ */ new Set();
|
|
3425
|
+
try {
|
|
3426
|
+
const routes = JSON.parse(readFileSync7(path, "utf8"));
|
|
3427
|
+
return new Set(Array.isArray(routes) ? routes.filter((r) => typeof r === "string") : []);
|
|
3428
|
+
} catch {
|
|
3429
|
+
return /* @__PURE__ */ new Set();
|
|
3430
|
+
}
|
|
3431
|
+
}
|
|
3358
3432
|
async function runChecks(project, state, routes, { config, shoot, audit }, step) {
|
|
3359
3433
|
const { start: cmd } = config;
|
|
3360
3434
|
if (!cmd || !shoot) throw new RefusedError(NEEDS_BROWSER);
|
|
@@ -3365,8 +3439,13 @@ async function runChecks(project, state, routes, { config, shoot, audit }, step)
|
|
|
3365
3439
|
});
|
|
3366
3440
|
try {
|
|
3367
3441
|
step.name = "checking the pages";
|
|
3368
|
-
const
|
|
3369
|
-
|
|
3442
|
+
const closed = await closedRoutes(server.url, routes);
|
|
3443
|
+
const shut = new Set(closed.map((c) => c.route));
|
|
3444
|
+
const open = routes.filter((route) => !shut.has(route));
|
|
3445
|
+
const before = closedAtBaseline(project.root);
|
|
3446
|
+
const compared = routes.filter((route) => !(shut.has(route) && before.has(route)));
|
|
3447
|
+
const english2 = state.mode === "bilingual" ? await englishChecks(project.root, server.url, compared, shoot) : { english: [], noBaseline: [] };
|
|
3448
|
+
return { ...english2, closed, arabic: await arabicChecks(project, state, server.url, open, audit) };
|
|
3370
3449
|
} finally {
|
|
3371
3450
|
await server.stop();
|
|
3372
3451
|
}
|
|
@@ -3407,7 +3486,7 @@ async function run(ready, step) {
|
|
|
3407
3486
|
ledger,
|
|
3408
3487
|
...checks,
|
|
3409
3488
|
...earlier ? {} : { summaryMissing: true },
|
|
3410
|
-
unreachable,
|
|
3489
|
+
unreachable: [...unreachable, ...checks.closed],
|
|
3411
3490
|
remainingLint,
|
|
3412
3491
|
...tests2 ? { tests: { code: tests2.code, tail: tail(tests2.out) } } : {}
|
|
3413
3492
|
};
|