@babelize/engine 0.2.0 → 0.2.1
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/analysis/analyze.js +1 -1
- package/dist/injection/html/html-injector.d.ts +2 -0
- package/dist/injection/html/html-injector.js +55 -6
- package/dist/local/translate-core.d.ts +2 -2
- package/dist/local/translate-core.js +2 -2
- package/dist/modules/finalization/finalize.js +6 -4
- package/dist/modules/finalization/static-runtime.d.ts +2 -2
- package/dist/modules/finalization/static-runtime.js +10 -5
- package/package.json +1 -1
package/dist/analysis/analyze.js
CHANGED
|
@@ -139,7 +139,7 @@ async function analyzeWorkspace(workspacePath, options = {}) {
|
|
|
139
139
|
const framework = workspace.framework;
|
|
140
140
|
const support = frameworkSupport(framework);
|
|
141
141
|
if (support === 'unsupported') {
|
|
142
|
-
return empty(framework, `Detected ${framework}, which local translation does not support. Supported: React, Next.js and
|
|
142
|
+
return empty(framework, `Detected ${framework}, which local translation does not support. Supported: React, Next.js, Django and static HTML (Vue and React Native in beta).`);
|
|
143
143
|
}
|
|
144
144
|
const phases = (types_1.FRAMEWORK_CONFIGS[framework] ?? types_1.FRAMEWORK_CONFIGS.unknown).phases;
|
|
145
145
|
// Same ICU decision as a local translate run (whose translator is an LLM).
|
|
@@ -7,5 +7,7 @@ interface HtmlInjectOptions {
|
|
|
7
7
|
targetLocales?: string[];
|
|
8
8
|
sourceLocale?: string;
|
|
9
9
|
}
|
|
10
|
+
/** `<script src>` for the root i18n.js as seen from this page: `i18n.js`, `../i18n.js`, ... */
|
|
11
|
+
export declare function runtimeScriptSrc(filePath: string): string;
|
|
10
12
|
export declare function injectHtml(options: HtmlInjectOptions): Promise<FileInjection>;
|
|
11
13
|
export {};
|
|
@@ -32,10 +32,36 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
35
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.runtimeScriptSrc = runtimeScriptSrc;
|
|
36
40
|
exports.injectHtml = injectHtml;
|
|
41
|
+
const path_1 = __importDefault(require("path"));
|
|
37
42
|
const string_utils_1 = require("../../utils/string-utils");
|
|
38
43
|
const html_extractor_1 = require("../../utils/html-extractor");
|
|
44
|
+
// <meta> tags whose content the extractor collects (html-extractor extractHeadStrings).
|
|
45
|
+
const SEO_META = ['description', 'og:title', 'og:description', 'og:site_name', 'twitter:title', 'twitter:description'];
|
|
46
|
+
/**
|
|
47
|
+
* Key for a string in this file found by its text alone. Head strings are extracted
|
|
48
|
+
* with ids like `file:meta:<hash>`, and a string already seen in the body is not
|
|
49
|
+
* extracted twice, so a line-based id can't find them.
|
|
50
|
+
*/
|
|
51
|
+
function keyByText(filePath, text, keyMapping) {
|
|
52
|
+
const prefix = `${filePath}:`;
|
|
53
|
+
const suffix = `:${(0, string_utils_1.hashString)(text)}`;
|
|
54
|
+
for (const [id, key] of keyMapping) {
|
|
55
|
+
if (id.startsWith(prefix) && id.endsWith(suffix))
|
|
56
|
+
return key;
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
/** `<script src>` for the root i18n.js as seen from this page: `i18n.js`, `../i18n.js`, ... */
|
|
61
|
+
function runtimeScriptSrc(filePath) {
|
|
62
|
+
const dir = path_1.default.posix.dirname(filePath.split(path_1.default.sep).join('/'));
|
|
63
|
+
return path_1.default.posix.relative(dir, 'i18n.js') || 'i18n.js';
|
|
64
|
+
}
|
|
39
65
|
function asElement(node) {
|
|
40
66
|
return node.nodeType === 1 ? node : null;
|
|
41
67
|
}
|
|
@@ -60,13 +86,26 @@ function walkHtmlNode(node, filePath, content, keyMapping, matchedIds, injection
|
|
|
60
86
|
}
|
|
61
87
|
}
|
|
62
88
|
const element = asElement(node);
|
|
89
|
+
if (element && tagName === 'meta') {
|
|
90
|
+
const name = element.getAttribute('name') || element.getAttribute('property');
|
|
91
|
+
const value = element.getAttribute('content');
|
|
92
|
+
if (name && SEO_META.includes(name) && value?.trim() && !element.getAttribute('data-i18n-content')) {
|
|
93
|
+
const key = keyByText(filePath, value.trim(), keyMapping);
|
|
94
|
+
if (key) {
|
|
95
|
+
element.setAttribute('data-i18n-content', key);
|
|
96
|
+
injections.push({ file: filePath, line: 0, column: 0, original: value, replacement: `data-i18n-content="${key}"`, key, confidence: 100 });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
63
101
|
if (textChild && !nonTextChild && element && !element.getAttribute('data-i18n')) {
|
|
64
102
|
const text = textChild.text.trim();
|
|
65
103
|
if (node.range) {
|
|
66
104
|
const line = (content.slice(0, node.range[0]).match(/\n/g) || []).length + 1;
|
|
67
105
|
const id = `${filePath}:${line}:${(0, string_utils_1.hashString)(text)}`;
|
|
68
|
-
|
|
69
|
-
|
|
106
|
+
// <title> may only exist under a head id (`file:title:<hash>`) or share a body string's key.
|
|
107
|
+
if (matchedIds.has(id) || tagName === 'title') {
|
|
108
|
+
const key = keyMapping.get(id) ?? (tagName === 'title' ? keyByText(filePath, text, keyMapping) : undefined);
|
|
70
109
|
if (key) {
|
|
71
110
|
element.setAttribute('data-i18n', key);
|
|
72
111
|
injections.push({
|
|
@@ -159,13 +198,23 @@ async function injectHtml(options) {
|
|
|
159
198
|
const matchedIds = new Set(keyMapping.keys());
|
|
160
199
|
walkHtmlNode(htmlEl, filePath, content, keyMapping, matchedIds, injections);
|
|
161
200
|
let modifiedContent = root.toString();
|
|
162
|
-
|
|
163
|
-
|
|
201
|
+
// i18n.js lives at the site root, so pages in subfolders need ../i18n.js. Older runs
|
|
202
|
+
// wrote a bare src="i18n.js" everywhere; repoint those instead of adding a second tag.
|
|
203
|
+
const src = runtimeScriptSrc(filePath);
|
|
204
|
+
const existingScript = /<script([^>]*)src\s*=\s*["']((?:\.\.\/)*i18n\.js)["']/.exec(modifiedContent);
|
|
205
|
+
if (existingScript) {
|
|
206
|
+
if (existingScript[2] !== src) {
|
|
207
|
+
modifiedContent = modifiedContent.replace(existingScript[0], `<script${existingScript[1]}src="${src}"`);
|
|
208
|
+
importAdded = true;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
const tag = ` <script src="${src}"></script>\n`;
|
|
164
213
|
if (modifiedContent.includes('</body>')) {
|
|
165
|
-
modifiedContent = modifiedContent.replace('</body>',
|
|
214
|
+
modifiedContent = modifiedContent.replace('</body>', `${tag}</body>`);
|
|
166
215
|
}
|
|
167
216
|
else {
|
|
168
|
-
modifiedContent = modifiedContent.replace('</head>',
|
|
217
|
+
modifiedContent = modifiedContent.replace('</head>', `${tag}</head>`);
|
|
169
218
|
}
|
|
170
219
|
importAdded = true;
|
|
171
220
|
}
|
|
@@ -10,8 +10,8 @@ import { type FileDiffStat, type ApplyIo, type WorkspaceChanges } from './worksp
|
|
|
10
10
|
/** Lockfile scope for local runs: `.babelize/local/babelize-lock.json` in the project root. */
|
|
11
11
|
export declare const LOCAL_PROJECT_ID = "local";
|
|
12
12
|
export declare const LOCAL_LOCKFILE = ".babelize/local/babelize-lock.json";
|
|
13
|
-
export declare const SUPPORTED_LOCAL_FRAMEWORKS: readonly ["react", "nextjs", "django"];
|
|
14
|
-
export declare const BETA_LOCAL_FRAMEWORKS: readonly ["vue", "
|
|
13
|
+
export declare const SUPPORTED_LOCAL_FRAMEWORKS: readonly ["react", "nextjs", "django", "static"];
|
|
14
|
+
export declare const BETA_LOCAL_FRAMEWORKS: readonly ["vue", "react-native"];
|
|
15
15
|
export interface LocalTranslateOptions {
|
|
16
16
|
projectDir: string;
|
|
17
17
|
sourceLocale: string;
|
|
@@ -72,8 +72,8 @@ const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
|
72
72
|
/** Lockfile scope for local runs: `.babelize/local/babelize-lock.json` in the project root. */
|
|
73
73
|
exports.LOCAL_PROJECT_ID = 'local';
|
|
74
74
|
exports.LOCAL_LOCKFILE = `.babelize/${exports.LOCAL_PROJECT_ID}/babelize-lock.json`;
|
|
75
|
-
exports.SUPPORTED_LOCAL_FRAMEWORKS = ['react', 'nextjs', 'django'];
|
|
76
|
-
exports.BETA_LOCAL_FRAMEWORKS = ['vue', '
|
|
75
|
+
exports.SUPPORTED_LOCAL_FRAMEWORKS = ['react', 'nextjs', 'django', 'static'];
|
|
76
|
+
exports.BETA_LOCAL_FRAMEWORKS = ['vue', 'react-native'];
|
|
77
77
|
class LocalTranslateError extends Error {
|
|
78
78
|
constructor(message, code,
|
|
79
79
|
/** Files left changed in the project (only for a failed, un-rolled-back apply). */
|
|
@@ -334,10 +334,12 @@ async function finalizeProject(workspace, dryRun, options = {}) {
|
|
|
334
334
|
}
|
|
335
335
|
else {
|
|
336
336
|
// 'missing' or 'ours' — write/overwrite
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
337
|
+
// sourceDirs[0] is the scan root ("."), not a locale. Targets: this run's plus any
|
|
338
|
+
// locale files already on disk, so the switcher lists every language.
|
|
339
|
+
const sourceLocale = options.sourceLocale || 'en';
|
|
340
|
+
const targetLocales = [...new Set([...(options.targetLocales ?? []), ...(workspace.existingLocales ?? [])])]
|
|
341
|
+
.filter((l) => l && l !== sourceLocale && /^[A-Za-z]{2,3}([-_][A-Za-z0-9]+)*$/.test(l));
|
|
342
|
+
const runtimeContent = (0, static_runtime_1.buildStaticRuntime)({ sourceLocale, targetLocales });
|
|
341
343
|
await writer.write(i18nJsPath, runtimeContent);
|
|
342
344
|
configFileCreated = true;
|
|
343
345
|
if (regenerationStatus === 'ours') {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export declare const STATIC_RUNTIME_VERSION = "1.
|
|
2
|
-
export declare const RUNTIME_HEADER = "/* babelize:static-runtime v1.
|
|
1
|
+
export declare const STATIC_RUNTIME_VERSION = "1.1.0";
|
|
2
|
+
export declare const RUNTIME_HEADER = "/* babelize:static-runtime v1.1.0 */";
|
|
3
3
|
export declare function buildStaticRuntime(config: {
|
|
4
4
|
sourceLocale: string;
|
|
5
5
|
targetLocales: string[];
|
|
@@ -4,8 +4,9 @@ exports.RUNTIME_HEADER = exports.STATIC_RUNTIME_VERSION = void 0;
|
|
|
4
4
|
exports.buildStaticRuntime = buildStaticRuntime;
|
|
5
5
|
exports.buildSwitcherPlaceholder = buildSwitcherPlaceholder;
|
|
6
6
|
exports.checkRuntimeNeedsRegeneration = checkRuntimeNeedsRegeneration;
|
|
7
|
-
exports.STATIC_RUNTIME_VERSION = '1.
|
|
8
|
-
|
|
7
|
+
exports.STATIC_RUNTIME_VERSION = '1.1.0';
|
|
8
|
+
const RUNTIME_MARKER = '/* babelize:static-runtime v';
|
|
9
|
+
exports.RUNTIME_HEADER = `${RUNTIME_MARKER}${exports.STATIC_RUNTIME_VERSION} */`;
|
|
9
10
|
function buildStaticRuntime(config) {
|
|
10
11
|
const locales = JSON.stringify([config.sourceLocale, ...config.targetLocales]);
|
|
11
12
|
const baseDir = config.baseDir ? JSON.stringify(config.baseDir) : 'null';
|
|
@@ -113,8 +114,9 @@ function buildStaticRuntime(config) {
|
|
|
113
114
|
}
|
|
114
115
|
|
|
115
116
|
function applyTranslations() {
|
|
117
|
+
// The whole document, so <title> and SEO <meta> tags in <head> are translated too.
|
|
116
118
|
var walk = document.createTreeWalker(
|
|
117
|
-
document.
|
|
119
|
+
document.documentElement,
|
|
118
120
|
NodeFilter.SHOW_ELEMENT,
|
|
119
121
|
null,
|
|
120
122
|
false
|
|
@@ -150,7 +152,7 @@ function buildStaticRuntime(config) {
|
|
|
150
152
|
}
|
|
151
153
|
}
|
|
152
154
|
}
|
|
153
|
-
var attrNames = ['alt', 'title', 'placeholder', 'aria-label', 'aria-description'];
|
|
155
|
+
var attrNames = ['alt', 'title', 'placeholder', 'aria-label', 'aria-description', 'content'];
|
|
154
156
|
for (var a = 0; a < attrNames.length; a++) {
|
|
155
157
|
var attrKey = node.getAttribute('data-i18n-' + attrNames[a]);
|
|
156
158
|
if (attrKey) {
|
|
@@ -162,6 +164,8 @@ function buildStaticRuntime(config) {
|
|
|
162
164
|
}
|
|
163
165
|
|
|
164
166
|
function updateHtmlAttributes() {
|
|
167
|
+
var select = document.querySelector('[data-babelize-switcher-select]');
|
|
168
|
+
if (select) select.value = lang;
|
|
165
169
|
var meta = (loadedLocales[lang] || {})._meta;
|
|
166
170
|
if (!meta) return;
|
|
167
171
|
var html = document.documentElement;
|
|
@@ -254,7 +258,8 @@ function checkRuntimeNeedsRegeneration(existingContent) {
|
|
|
254
258
|
if (!existingContent || existingContent.trim().length === 0) {
|
|
255
259
|
return 'missing';
|
|
256
260
|
}
|
|
257
|
-
|
|
261
|
+
// Any version we generated, so older runtimes get upgraded rather than treated as hand-written.
|
|
262
|
+
if (existingContent.includes(RUNTIME_MARKER)) {
|
|
258
263
|
return 'ours';
|
|
259
264
|
}
|
|
260
265
|
return 'user';
|
package/package.json
CHANGED