@18ways/mdx-translate 0.1.0-alpha.185b0ef4fee2

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.
@@ -0,0 +1,2 @@
1
+ export declare const transformCodeSample: (_source: string, _language: string | null | undefined, _options: unknown) => string | null;
2
+ //# sourceMappingURL=code-samples.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-samples.d.ts","sourceRoot":"","sources":["../src/code-samples.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,GAC9B,SAAS,MAAM,EACf,WAAW,MAAM,GAAG,IAAI,GAAG,SAAS,EACpC,UAAU,OAAO,KAChB,MAAM,GAAG,IAEX,CAAC"}
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.transformCodeSample = void 0;
4
+ const transformCodeSample = (_source, _language, _options) => {
5
+ return null;
6
+ };
7
+ exports.transformCodeSample = transformCodeSample;
8
+ //# sourceMappingURL=code-samples.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-samples.js","sourceRoot":"","sources":["../src/code-samples.ts"],"names":[],"mappings":";;;AAAO,MAAM,mBAAmB,GAAG,CACjC,OAAe,EACf,SAAoC,EACpC,QAAiB,EACF,EAAE;IACjB,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AANW,QAAA,mBAAmB,uBAM9B"}
@@ -0,0 +1,17 @@
1
+ import type { Root } from 'mdast';
2
+ type IncludedPath = RegExp | ((filePath: string) => boolean);
3
+ export interface RemarkMdxTranslateOptions {
4
+ include?: IncludedPath;
5
+ exclude?: IncludedPath;
6
+ attributeNames?: string[];
7
+ translationComponentName?: string;
8
+ translatedNodeComponentName?: string;
9
+ translateHookName?: string;
10
+ reactImportSource?: string;
11
+ runtimeImportSource?: string;
12
+ }
13
+ export declare function remarkMdxTranslate(userOptions?: RemarkMdxTranslateOptions): (tree: Root, file?: {
14
+ path?: string;
15
+ }) => void;
16
+ export {};
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,IAAI,EAUL,MAAM,OAAO,CAAC;AAyDf,KAAK,YAAY,GAAG,MAAM,GAAG,CAAC,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC;AAE7D,MAAM,WAAW,yBAAyB;IACxC,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAubD,wBAAgB,kBAAkB,CAAC,WAAW,GAAE,yBAA8B,IAcpE,MAAM,IAAI,EAAE,OAAO;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,UA8I7C"}
package/dist/index.js ADDED
@@ -0,0 +1,392 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.remarkMdxTranslate = remarkMdxTranslate;
4
+ const code_samples_1 = require("./code-samples");
5
+ const DEFAULT_ATTRIBUTE_NAMES = ['alt', 'title', 'aria-label', 'placeholder'];
6
+ const DEFAULT_REACT_IMPORT_SOURCE = '@18ways/react';
7
+ const DEFAULT_RUNTIME_IMPORT_SOURCE = '@18ways/mdx-translate/runtime';
8
+ const DEFAULT_TRANSLATION_COMPONENT_NAME = 'T';
9
+ const DEFAULT_TRANSLATED_NODE_COMPONENT_NAME = 'TranslatedNode';
10
+ const DEFAULT_TRANSLATE_HOOK_NAME = 'useT';
11
+ const createLiteral = (value) => ({
12
+ type: 'Literal',
13
+ value,
14
+ });
15
+ const createIdentifier = (name) => ({
16
+ type: 'Identifier',
17
+ name,
18
+ });
19
+ const createExpressionStatement = (expression) => ({
20
+ type: 'ExpressionStatement',
21
+ expression,
22
+ });
23
+ const createArrayExpression = (items) => ({
24
+ type: 'ArrayExpression',
25
+ elements: items.map((item) => createLiteral(item)),
26
+ });
27
+ const createExpressionAttribute = (name, value, expression) => ({
28
+ type: 'mdxJsxAttribute',
29
+ name,
30
+ value: {
31
+ type: 'mdxJsxAttributeValueExpression',
32
+ value,
33
+ data: {
34
+ estree: {
35
+ type: 'Program',
36
+ sourceType: 'module',
37
+ body: [createExpressionStatement(expression)],
38
+ },
39
+ },
40
+ },
41
+ });
42
+ const createImportSpecifier = (name) => ({
43
+ type: 'ImportSpecifier',
44
+ imported: createIdentifier(name),
45
+ local: createIdentifier(name),
46
+ });
47
+ const createDefaultImportSpecifier = (name) => ({
48
+ type: 'ImportDefaultSpecifier',
49
+ local: createIdentifier(name),
50
+ });
51
+ const createImportDeclaration = (source, names) => ({
52
+ type: 'ImportDeclaration',
53
+ source: createLiteral(source),
54
+ specifiers: names.map(createImportSpecifier),
55
+ attributes: [],
56
+ });
57
+ const createImportNode = (source, names) => ({
58
+ type: 'mdxjsEsm',
59
+ data: {
60
+ estree: {
61
+ type: 'Program',
62
+ sourceType: 'module',
63
+ body: [createImportDeclaration(source, names)],
64
+ },
65
+ },
66
+ });
67
+ const createDefaultImportNode = (source, localName) => ({
68
+ type: 'mdxjsEsm',
69
+ data: {
70
+ estree: {
71
+ type: 'Program',
72
+ sourceType: 'module',
73
+ body: [
74
+ {
75
+ type: 'ImportDeclaration',
76
+ source: createLiteral(source),
77
+ specifiers: [createDefaultImportSpecifier(localName)],
78
+ attributes: [],
79
+ },
80
+ ],
81
+ },
82
+ },
83
+ });
84
+ const matchesPath = (matcher, filePath) => {
85
+ if (!matcher) {
86
+ return true;
87
+ }
88
+ if (matcher instanceof RegExp) {
89
+ return matcher.test(filePath);
90
+ }
91
+ return matcher(filePath);
92
+ };
93
+ const shouldProcessFile = (filePath, options) => {
94
+ if (!filePath) {
95
+ return true;
96
+ }
97
+ if (options.exclude && matchesPath(options.exclude, filePath)) {
98
+ return false;
99
+ }
100
+ return matchesPath(options.include, filePath);
101
+ };
102
+ const isParent = (node) => Boolean(node && typeof node === 'object' && 'children' in node);
103
+ const visitTree = (node, visitor, parent = null, index = null) => {
104
+ visitor(node, parent, index);
105
+ if (!isParent(node) || !Array.isArray(node.children)) {
106
+ return;
107
+ }
108
+ for (let childIndex = 0; childIndex < node.children.length; childIndex += 1) {
109
+ const child = node.children[childIndex];
110
+ visitTree(child, visitor, node, childIndex);
111
+ }
112
+ };
113
+ const isWhitespaceOnlyText = (value) => value.trim().length === 0;
114
+ const isLowercaseHtmlName = (name) => typeof name === 'string' && /^[a-z][a-z0-9-]*$/.test(name);
115
+ const isExistingTranslationNode = (node, translationComponentName, translatedNodeComponentName) => (node.type === 'mdxJsxTextElement' || node.type === 'mdxJsxFlowElement') &&
116
+ (node.name === translationComponentName || node.name === translatedNodeComponentName);
117
+ const childrenHaveTranslatableText = (nodes) => {
118
+ for (const node of nodes) {
119
+ switch (node.type) {
120
+ case 'text':
121
+ case 'inlineCode':
122
+ if (!isWhitespaceOnlyText(node.value)) {
123
+ return true;
124
+ }
125
+ break;
126
+ default:
127
+ if (isParent(node) &&
128
+ Array.isArray(node.children) &&
129
+ childrenHaveTranslatableText(node.children)) {
130
+ return true;
131
+ }
132
+ break;
133
+ }
134
+ }
135
+ return false;
136
+ };
137
+ const createTranslationWrapper = (children, translationComponentName) => ({
138
+ type: 'mdxJsxTextElement',
139
+ name: translationComponentName,
140
+ attributes: [],
141
+ children: children,
142
+ });
143
+ const unwrapDirectTranslationChildren = (children, translationComponentName) => children.flatMap((child) => {
144
+ if (child.type === 'mdxJsxTextElement' &&
145
+ child.name === translationComponentName &&
146
+ child.attributes.length === 0) {
147
+ return child.children;
148
+ }
149
+ return [child];
150
+ });
151
+ const wrapContainerChildren = (node, options) => {
152
+ const normalizedChildren = unwrapDirectTranslationChildren(node.children, options.translationComponentName);
153
+ if (normalizedChildren.length === 1 &&
154
+ isExistingTranslationNode(normalizedChildren[0], options.translationComponentName, options.translatedNodeComponentName)) {
155
+ return false;
156
+ }
157
+ if (normalizedChildren.some((child) => isExistingTranslationNode(child, options.translationComponentName, options.translatedNodeComponentName))) {
158
+ return false;
159
+ }
160
+ if (!childrenHaveTranslatableText(normalizedChildren)) {
161
+ return false;
162
+ }
163
+ node.children = [
164
+ createTranslationWrapper(normalizedChildren, options.translationComponentName),
165
+ ];
166
+ return true;
167
+ };
168
+ const getImportDeclaration = (root, source) => {
169
+ for (const child of root.children) {
170
+ if (child.type !== 'mdxjsEsm' || !child.data?.estree?.body?.length) {
171
+ continue;
172
+ }
173
+ const declaration = child.data.estree.body.find((statement) => statement.type === 'ImportDeclaration' &&
174
+ statement.source.type === 'Literal' &&
175
+ statement.source.value === source);
176
+ if (declaration) {
177
+ return declaration;
178
+ }
179
+ }
180
+ return null;
181
+ };
182
+ const ensureNamedImport = (root, source, importName) => {
183
+ const existingDeclaration = getImportDeclaration(root, source);
184
+ if (existingDeclaration) {
185
+ const alreadyImported = existingDeclaration.specifiers.some((specifier) => specifier.type === 'ImportSpecifier' &&
186
+ specifier.imported.type === 'Identifier' &&
187
+ specifier.imported.name === importName &&
188
+ specifier.local.type === 'Identifier' &&
189
+ specifier.local.name === importName);
190
+ if (!alreadyImported) {
191
+ existingDeclaration.specifiers.push(createImportSpecifier(importName));
192
+ }
193
+ return;
194
+ }
195
+ root.children.unshift(createImportNode(source, [importName]));
196
+ };
197
+ const resolveImageReferenceUrl = (node, definitions) => {
198
+ if (node.type === 'image') {
199
+ return node.url;
200
+ }
201
+ return definitions.get(node.identifier)?.url ?? '';
202
+ };
203
+ const normalizeStaticImportSpecifier = (value) => value.replace(/[^a-zA-Z0-9_$]/g, '_');
204
+ const isExternalUrl = (value) => /^https?:\/\//i.test(value);
205
+ const isRelativeAssetUrl = (value) => Boolean(value) && !isExternalUrl(value) && !value.startsWith('/');
206
+ const createTranslatePropsAttribute = (attributeNames) => createExpressionAttribute('translateProps', `[${attributeNames.map((name) => JSON.stringify(name)).join(', ')}]`, createArrayExpression(attributeNames));
207
+ const createTranslatedNode = ({ originalType, originalName, originalAttributes, originalChildren, translatedNodeComponentName, translateProps, }) => ({
208
+ type: originalType,
209
+ name: translatedNodeComponentName,
210
+ attributes: [
211
+ {
212
+ type: 'mdxJsxAttribute',
213
+ name: 'component',
214
+ value: originalName,
215
+ },
216
+ createTranslatePropsAttribute(translateProps),
217
+ ...originalAttributes,
218
+ ],
219
+ children: originalType === 'mdxJsxTextElement'
220
+ ? originalChildren
221
+ : originalChildren,
222
+ });
223
+ const getTranslatableAttributeNames = (attributes, attributeNames) => attributes
224
+ .filter((attribute) => attributeNames.has(attribute.name) && typeof attribute.value === 'string')
225
+ .map((attribute) => attribute.name);
226
+ const buildImportedImageAttribute = (variableName) => createExpressionAttribute('src', variableName, createIdentifier(variableName));
227
+ const createImageAttributes = ({ srcValue, alt, title, }) => {
228
+ const attributes = [
229
+ {
230
+ type: 'mdxJsxAttribute',
231
+ name: 'src',
232
+ value: srcValue,
233
+ },
234
+ ];
235
+ if (typeof alt === 'string') {
236
+ attributes.push({
237
+ type: 'mdxJsxAttribute',
238
+ name: 'alt',
239
+ value: alt,
240
+ });
241
+ }
242
+ if (typeof title === 'string') {
243
+ attributes.push({
244
+ type: 'mdxJsxAttribute',
245
+ name: 'title',
246
+ value: title,
247
+ });
248
+ }
249
+ return attributes;
250
+ };
251
+ const createTranslatedImageNode = ({ srcValue, alt, title, translatedNodeComponentName, asTextElement, }) => {
252
+ const translateProps = ['alt', 'title'].filter((name) => name === 'alt'
253
+ ? typeof alt === 'string' && alt.length > 0
254
+ : typeof title === 'string' && title.length > 0);
255
+ return {
256
+ type: asTextElement ? 'mdxJsxTextElement' : 'mdxJsxFlowElement',
257
+ name: translatedNodeComponentName,
258
+ attributes: [
259
+ {
260
+ type: 'mdxJsxAttribute',
261
+ name: 'component',
262
+ value: 'img',
263
+ },
264
+ createTranslatePropsAttribute(translateProps),
265
+ ...createImageAttributes({ srcValue, alt, title }),
266
+ ],
267
+ children: [],
268
+ };
269
+ };
270
+ function remarkMdxTranslate(userOptions = {}) {
271
+ const options = {
272
+ include: userOptions.include,
273
+ exclude: userOptions.exclude,
274
+ attributeNames: new Set(userOptions.attributeNames || DEFAULT_ATTRIBUTE_NAMES),
275
+ translationComponentName: userOptions.translationComponentName || DEFAULT_TRANSLATION_COMPONENT_NAME,
276
+ translatedNodeComponentName: userOptions.translatedNodeComponentName || DEFAULT_TRANSLATED_NODE_COMPONENT_NAME,
277
+ translateHookName: userOptions.translateHookName || DEFAULT_TRANSLATE_HOOK_NAME,
278
+ reactImportSource: userOptions.reactImportSource || DEFAULT_REACT_IMPORT_SOURCE,
279
+ runtimeImportSource: userOptions.runtimeImportSource || DEFAULT_RUNTIME_IMPORT_SOURCE,
280
+ };
281
+ return (tree, file) => {
282
+ const root = tree;
283
+ if (!shouldProcessFile(file?.path, { include: options.include, exclude: options.exclude })) {
284
+ return;
285
+ }
286
+ const definitions = new Map();
287
+ visitTree(root, (node) => {
288
+ if (node.type === 'definition') {
289
+ definitions.set(node.identifier, node);
290
+ }
291
+ if (node.type === 'code') {
292
+ const transformed = (0, code_samples_1.transformCodeSample)(node.value, node.lang, {
293
+ translationComponentName: options.translationComponentName,
294
+ translateHookName: options.translateHookName,
295
+ reactImportSource: options.reactImportSource,
296
+ attributeNames: Array.from(options.attributeNames),
297
+ });
298
+ if (typeof transformed === 'string' && transformed !== node.value) {
299
+ node.value = transformed;
300
+ }
301
+ }
302
+ });
303
+ let needsTranslationComponentImport = false;
304
+ let needsTranslatedNodeImport = false;
305
+ const importedImages = new Map();
306
+ const ensureImportedImageExpression = (url) => {
307
+ if (!isRelativeAssetUrl(url)) {
308
+ return url;
309
+ }
310
+ let variableName = importedImages.get(url);
311
+ if (!variableName) {
312
+ variableName = `__mdxTranslateImg_${normalizeStaticImportSpecifier(String(importedImages.size))}`;
313
+ importedImages.set(url, variableName);
314
+ }
315
+ return buildImportedImageAttribute(variableName).value;
316
+ };
317
+ visitTree(root, (node, parent, index) => {
318
+ if (node.type === 'heading' || node.type === 'paragraph' || node.type === 'tableCell') {
319
+ const didWrap = wrapContainerChildren(node, {
320
+ translationComponentName: options.translationComponentName,
321
+ translatedNodeComponentName: options.translatedNodeComponentName,
322
+ });
323
+ if (didWrap) {
324
+ needsTranslationComponentImport = true;
325
+ }
326
+ }
327
+ if ((node.type === 'mdxJsxTextElement' || node.type === 'mdxJsxFlowElement') &&
328
+ node.name !== options.translatedNodeComponentName &&
329
+ node.name !== options.translationComponentName &&
330
+ isLowercaseHtmlName(node.name)) {
331
+ const translateProps = getTranslatableAttributeNames(node.attributes, options.attributeNames);
332
+ if (!translateProps.length) {
333
+ return;
334
+ }
335
+ const translatedNode = createTranslatedNode({
336
+ originalType: node.type,
337
+ originalName: node.name,
338
+ originalAttributes: node.attributes.map((attribute) => {
339
+ if (node.name === 'img' &&
340
+ attribute.name === 'src' &&
341
+ typeof attribute.value === 'string' &&
342
+ isRelativeAssetUrl(attribute.value)) {
343
+ return {
344
+ ...attribute,
345
+ value: ensureImportedImageExpression(attribute.value),
346
+ };
347
+ }
348
+ return attribute;
349
+ }),
350
+ originalChildren: node.children,
351
+ translatedNodeComponentName: options.translatedNodeComponentName,
352
+ translateProps,
353
+ });
354
+ Object.assign(node, translatedNode);
355
+ needsTranslatedNodeImport = true;
356
+ }
357
+ if (!parent || index === null) {
358
+ return;
359
+ }
360
+ if (node.type !== 'image' && node.type !== 'imageReference') {
361
+ return;
362
+ }
363
+ const url = resolveImageReferenceUrl(node, definitions);
364
+ const alt = 'alt' in node ? (node.alt ?? null) : null;
365
+ const title = 'title' in node ? (node.title ?? null) : null;
366
+ const translateProps = ['alt', 'title'].filter((name) => name === 'alt'
367
+ ? typeof alt === 'string' && alt.length > 0
368
+ : typeof title === 'string' && title.length > 0);
369
+ if (!url || !translateProps.length) {
370
+ return;
371
+ }
372
+ parent.children[index] = createTranslatedImageNode({
373
+ srcValue: ensureImportedImageExpression(url),
374
+ alt,
375
+ title,
376
+ translatedNodeComponentName: options.translatedNodeComponentName,
377
+ asTextElement: parent.type === 'paragraph' || parent.type === 'heading',
378
+ });
379
+ needsTranslatedNodeImport = true;
380
+ });
381
+ if (needsTranslationComponentImport) {
382
+ ensureNamedImport(root, options.reactImportSource, options.translationComponentName);
383
+ }
384
+ if (needsTranslatedNodeImport) {
385
+ ensureNamedImport(root, options.runtimeImportSource, options.translatedNodeComponentName);
386
+ }
387
+ for (const [url, variableName] of Array.from(importedImages.entries()).reverse()) {
388
+ root.children.unshift(createDefaultImportNode(url, variableName));
389
+ }
390
+ };
391
+ }
392
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import { type ReactNode } from 'react';
2
+ type TranslatedNodeProps = {
3
+ component: keyof JSX.IntrinsicElements;
4
+ translateProps?: string[];
5
+ children?: ReactNode;
6
+ } & Record<string, unknown>;
7
+ export declare function TranslatedNode({ component, translateProps, children, ...props }: TranslatedNodeProps): import("react").DOMElement<Record<string, unknown>, Element>;
8
+ export {};
9
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAiB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAStD,KAAK,mBAAmB,GAAG;IACzB,SAAS,EAAE,MAAM,GAAG,CAAC,iBAAiB,CAAC;IACvC,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAU5B,wBAAgB,cAAc,CAAC,EAC7B,SAAS,EACT,cAAmB,EACnB,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,mBAAmB,gEA8BrB"}
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ 'use client';
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.TranslatedNode = TranslatedNode;
5
+ const react_1 = require("react");
6
+ const react_2 = require("@18ways/react");
7
+ const isStaticImageLike = (value) => Boolean(value &&
8
+ typeof value === 'object' &&
9
+ 'src' in value &&
10
+ typeof value.src === 'string');
11
+ function TranslatedNode({ component, translateProps = [], children, ...props }) {
12
+ const t = (0, react_2.useT)();
13
+ const translatedProps = { ...props };
14
+ for (const propName of translateProps) {
15
+ const value = translatedProps[propName];
16
+ if (typeof value === 'string' && value.trim()) {
17
+ translatedProps[propName] = t(value);
18
+ }
19
+ }
20
+ if (component === 'img') {
21
+ const src = translatedProps.src;
22
+ if (isStaticImageLike(src)) {
23
+ translatedProps.src = src.src;
24
+ if (translatedProps.width == null && typeof src.width === 'number') {
25
+ translatedProps.width = src.width;
26
+ }
27
+ if (translatedProps.height == null && typeof src.height === 'number') {
28
+ translatedProps.height = src.height;
29
+ }
30
+ }
31
+ delete translatedProps.placeholder;
32
+ delete translatedProps.blurDataURL;
33
+ }
34
+ return (0, react_1.createElement)(component, translatedProps, children);
35
+ }
36
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.tsx"],"names":[],"mappings":";AAAA,YAAY,CAAC;;AAyBb,wCAmCC;AA1DD,iCAAsD;AACtD,yCAAqC;AAcrC,MAAM,iBAAiB,GAAG,CAAC,KAAc,EAA4B,EAAE,CACrE,OAAO,CACL,KAAK;IACL,OAAO,KAAK,KAAK,QAAQ;IACzB,KAAK,IAAI,KAAK;IACd,OAAQ,KAAyB,CAAC,GAAG,KAAK,QAAQ,CACnD,CAAC;AAEJ,SAAgB,cAAc,CAAC,EAC7B,SAAS,EACT,cAAc,GAAG,EAAE,EACnB,QAAQ,EACR,GAAG,KAAK,EACY;IACpB,MAAM,CAAC,GAAG,IAAA,YAAI,GAAE,CAAC;IACjB,MAAM,eAAe,GAA4B,EAAE,GAAG,KAAK,EAAE,CAAC;IAE9D,KAAK,MAAM,QAAQ,IAAI,cAAc,EAAE,CAAC;QACtC,MAAM,KAAK,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;QACxC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;YAC9C,eAAe,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,CAAW,CAAC;QACjD,CAAC;IACH,CAAC;IAED,IAAI,SAAS,KAAK,KAAK,EAAE,CAAC;QACxB,MAAM,GAAG,GAAG,eAAe,CAAC,GAAG,CAAC;QAChC,IAAI,iBAAiB,CAAC,GAAG,CAAC,EAAE,CAAC;YAC3B,eAAe,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;YAE9B,IAAI,eAAe,CAAC,KAAK,IAAI,IAAI,IAAI,OAAO,GAAG,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;gBACnE,eAAe,CAAC,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC;YACpC,CAAC;YAED,IAAI,eAAe,CAAC,MAAM,IAAI,IAAI,IAAI,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;gBACrE,eAAe,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;YACtC,CAAC;QACH,CAAC;QAED,OAAO,eAAe,CAAC,WAAW,CAAC;QACnC,OAAO,eAAe,CAAC,WAAW,CAAC;IACrC,CAAC;IAED,OAAO,IAAA,qBAAa,EAAC,SAAS,EAAE,eAAe,EAAE,QAAQ,CAAC,CAAC;AAC7D,CAAC"}
package/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './src/index';
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@18ways/mdx-translate",
3
+ "version": "0.1.0-alpha.185b0ef4fee2",
4
+ "description": "Remark-based MDX translation transforms for 18ways",
5
+ "main": "dist/index.js",
6
+ "module": "./index.ts",
7
+ "types": "./index.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./index.ts",
11
+ "import": "./index.ts",
12
+ "require": "./dist/index.js"
13
+ },
14
+ "./runtime": {
15
+ "types": "./runtime.tsx",
16
+ "import": "./runtime.tsx",
17
+ "require": "./dist/runtime.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "index.ts",
23
+ "runtime.tsx",
24
+ "src"
25
+ ],
26
+ "scripts": {
27
+ "build": "tsc",
28
+ "dev": "tsc --watch",
29
+ "ts:check": "tsc --noEmit",
30
+ "test": "vitest run",
31
+ "test:watch": "vitest"
32
+ },
33
+ "dependencies": {
34
+ "@18ways/react": "1.0.0-alpha.185b0ef4fee2",
35
+ "@babel/generator": "^7.28.5",
36
+ "@babel/parser": "^7.28.5",
37
+ "@babel/traverse": "^7.28.5",
38
+ "@babel/types": "^7.28.5"
39
+ },
40
+ "peerDependencies": {
41
+ "react": "^18.0.0 || ^19.0.0"
42
+ },
43
+ "devDependencies": {
44
+ "@mdx-js/mdx": "^3.1.1",
45
+ "@types/estree": "^1.0.8",
46
+ "@types/mdast": "^4.0.4",
47
+ "@types/node": "^22.10.0",
48
+ "@types/react": "^18.3.12",
49
+ "@types/unist": "^3.0.3",
50
+ "react": "^18.3.1",
51
+ "typescript": "^5.6.2",
52
+ "vitest": "^3.2.4"
53
+ },
54
+ "license": "MIT",
55
+ "packageManager": "bun@1.3.8"
56
+ }
package/runtime.tsx ADDED
@@ -0,0 +1 @@
1
+ export * from './src/runtime';
@@ -0,0 +1,7 @@
1
+ export const transformCodeSample = (
2
+ _source: string,
3
+ _language: string | null | undefined,
4
+ _options: unknown
5
+ ): string | null => {
6
+ return null;
7
+ };
package/src/index.ts ADDED
@@ -0,0 +1,675 @@
1
+ import type {
2
+ Root,
3
+ Content,
4
+ Definition,
5
+ Heading,
6
+ Paragraph,
7
+ TableCell,
8
+ Code,
9
+ Image,
10
+ ImageReference,
11
+ PhrasingContent,
12
+ } from 'mdast';
13
+ import type { Parent } from 'unist';
14
+ import type {
15
+ ImportDeclaration,
16
+ ImportDefaultSpecifier,
17
+ ImportSpecifier,
18
+ Program,
19
+ Expression,
20
+ Identifier,
21
+ ArrayExpression,
22
+ Literal,
23
+ ExpressionStatement,
24
+ } from 'estree';
25
+ import { transformCodeSample } from './code-samples';
26
+
27
+ type MdxJsxAttributeValueExpression = {
28
+ type: 'mdxJsxAttributeValueExpression';
29
+ value: string;
30
+ data: {
31
+ estree: Program;
32
+ };
33
+ };
34
+
35
+ type MdxJsxAttribute = {
36
+ type: 'mdxJsxAttribute';
37
+ name: string;
38
+ value?: string | null | MdxJsxAttributeValueExpression;
39
+ };
40
+
41
+ type MdxJsxElement = {
42
+ type: 'mdxJsxTextElement' | 'mdxJsxFlowElement';
43
+ name: string | null;
44
+ attributes: MdxJsxAttribute[];
45
+ children: Array<Content | MdxJsxTextElement | MdxJsxFlowElement>;
46
+ };
47
+
48
+ type MdxJsxTextElement = MdxJsxElement & {
49
+ type: 'mdxJsxTextElement';
50
+ children: PhrasingContent[];
51
+ };
52
+
53
+ type MdxJsxFlowElement = MdxJsxElement & {
54
+ type: 'mdxJsxFlowElement';
55
+ };
56
+
57
+ type MdxjsEsm = {
58
+ type: 'mdxjsEsm';
59
+ data?: {
60
+ estree?: Program;
61
+ };
62
+ };
63
+
64
+ type TreeContent = Content | MdxJsxTextElement | MdxJsxFlowElement | MdxjsEsm;
65
+ type TreeRoot = Omit<Root, 'children'> & { children: TreeContent[] };
66
+ type TreeParent = Omit<Parent, 'children'> & { children: TreeContent[] };
67
+ type TreePhrasingContent = PhrasingContent | MdxJsxTextElement;
68
+
69
+ type IncludedPath = RegExp | ((filePath: string) => boolean);
70
+
71
+ export interface RemarkMdxTranslateOptions {
72
+ include?: IncludedPath;
73
+ exclude?: IncludedPath;
74
+ attributeNames?: string[];
75
+ translationComponentName?: string;
76
+ translatedNodeComponentName?: string;
77
+ translateHookName?: string;
78
+ reactImportSource?: string;
79
+ runtimeImportSource?: string;
80
+ }
81
+
82
+ const DEFAULT_ATTRIBUTE_NAMES = ['alt', 'title', 'aria-label', 'placeholder'];
83
+ const DEFAULT_REACT_IMPORT_SOURCE = '@18ways/react';
84
+ const DEFAULT_RUNTIME_IMPORT_SOURCE = '@18ways/mdx-translate/runtime';
85
+ const DEFAULT_TRANSLATION_COMPONENT_NAME = 'T';
86
+ const DEFAULT_TRANSLATED_NODE_COMPONENT_NAME = 'TranslatedNode';
87
+ const DEFAULT_TRANSLATE_HOOK_NAME = 'useT';
88
+
89
+ const createLiteral = (value: string): Literal => ({
90
+ type: 'Literal',
91
+ value,
92
+ });
93
+
94
+ const createIdentifier = (name: string): Identifier => ({
95
+ type: 'Identifier',
96
+ name,
97
+ });
98
+
99
+ const createExpressionStatement = (expression: Expression): ExpressionStatement => ({
100
+ type: 'ExpressionStatement',
101
+ expression,
102
+ });
103
+
104
+ const createArrayExpression = (items: string[]): ArrayExpression => ({
105
+ type: 'ArrayExpression',
106
+ elements: items.map((item) => createLiteral(item)),
107
+ });
108
+
109
+ const createExpressionAttribute = (
110
+ name: string,
111
+ value: string,
112
+ expression: Expression
113
+ ): MdxJsxAttribute => ({
114
+ type: 'mdxJsxAttribute',
115
+ name,
116
+ value: {
117
+ type: 'mdxJsxAttributeValueExpression',
118
+ value,
119
+ data: {
120
+ estree: {
121
+ type: 'Program',
122
+ sourceType: 'module',
123
+ body: [createExpressionStatement(expression)],
124
+ },
125
+ },
126
+ },
127
+ });
128
+
129
+ const createImportSpecifier = (name: string): ImportSpecifier => ({
130
+ type: 'ImportSpecifier',
131
+ imported: createIdentifier(name),
132
+ local: createIdentifier(name),
133
+ });
134
+
135
+ const createDefaultImportSpecifier = (name: string): ImportDefaultSpecifier => ({
136
+ type: 'ImportDefaultSpecifier',
137
+ local: createIdentifier(name),
138
+ });
139
+
140
+ const createImportDeclaration = (source: string, names: string[]): ImportDeclaration => ({
141
+ type: 'ImportDeclaration',
142
+ source: createLiteral(source),
143
+ specifiers: names.map(createImportSpecifier),
144
+ attributes: [],
145
+ });
146
+
147
+ const createImportNode = (source: string, names: string[]): MdxjsEsm => ({
148
+ type: 'mdxjsEsm',
149
+ data: {
150
+ estree: {
151
+ type: 'Program',
152
+ sourceType: 'module',
153
+ body: [createImportDeclaration(source, names)],
154
+ },
155
+ },
156
+ });
157
+
158
+ const createDefaultImportNode = (source: string, localName: string): MdxjsEsm => ({
159
+ type: 'mdxjsEsm',
160
+ data: {
161
+ estree: {
162
+ type: 'Program',
163
+ sourceType: 'module',
164
+ body: [
165
+ {
166
+ type: 'ImportDeclaration',
167
+ source: createLiteral(source),
168
+ specifiers: [createDefaultImportSpecifier(localName)],
169
+ attributes: [],
170
+ },
171
+ ],
172
+ },
173
+ },
174
+ });
175
+
176
+ const matchesPath = (matcher: IncludedPath | undefined, filePath: string): boolean => {
177
+ if (!matcher) {
178
+ return true;
179
+ }
180
+
181
+ if (matcher instanceof RegExp) {
182
+ return matcher.test(filePath);
183
+ }
184
+
185
+ return matcher(filePath);
186
+ };
187
+
188
+ const shouldProcessFile = (
189
+ filePath: string | undefined,
190
+ options: Pick<RemarkMdxTranslateOptions, 'include' | 'exclude'>
191
+ ): boolean => {
192
+ if (!filePath) {
193
+ return true;
194
+ }
195
+
196
+ if (options.exclude && matchesPath(options.exclude, filePath)) {
197
+ return false;
198
+ }
199
+
200
+ return matchesPath(options.include, filePath);
201
+ };
202
+
203
+ const isParent = (node: unknown): node is TreeParent | TreeRoot =>
204
+ Boolean(node && typeof node === 'object' && 'children' in (node as Record<string, unknown>));
205
+
206
+ const visitTree = (
207
+ node: TreeContent | TreeRoot,
208
+ visitor: (node: TreeContent | TreeRoot, parent: TreeParent | null, index: number | null) => void,
209
+ parent: TreeParent | null = null,
210
+ index: number | null = null
211
+ ): void => {
212
+ visitor(node, parent, index);
213
+
214
+ if (!isParent(node) || !Array.isArray(node.children)) {
215
+ return;
216
+ }
217
+
218
+ for (let childIndex = 0; childIndex < node.children.length; childIndex += 1) {
219
+ const child = node.children[childIndex];
220
+ visitTree(child, visitor, node, childIndex);
221
+ }
222
+ };
223
+
224
+ const isWhitespaceOnlyText = (value: string): boolean => value.trim().length === 0;
225
+
226
+ const isLowercaseHtmlName = (name: string | null): name is string =>
227
+ typeof name === 'string' && /^[a-z][a-z0-9-]*$/.test(name);
228
+
229
+ const isExistingTranslationNode = (
230
+ node: TreeContent | TreePhrasingContent,
231
+ translationComponentName: string,
232
+ translatedNodeComponentName: string
233
+ ): boolean =>
234
+ (node.type === 'mdxJsxTextElement' || node.type === 'mdxJsxFlowElement') &&
235
+ (node.name === translationComponentName || node.name === translatedNodeComponentName);
236
+
237
+ const childrenHaveTranslatableText = (nodes: Array<TreeContent | TreePhrasingContent>): boolean => {
238
+ for (const node of nodes) {
239
+ switch (node.type) {
240
+ case 'text':
241
+ case 'inlineCode':
242
+ if (!isWhitespaceOnlyText(node.value)) {
243
+ return true;
244
+ }
245
+ break;
246
+ default:
247
+ if (
248
+ isParent(node) &&
249
+ Array.isArray(node.children) &&
250
+ childrenHaveTranslatableText(node.children)
251
+ ) {
252
+ return true;
253
+ }
254
+ break;
255
+ }
256
+ }
257
+
258
+ return false;
259
+ };
260
+
261
+ const createTranslationWrapper = (
262
+ children: TreePhrasingContent[],
263
+ translationComponentName: string
264
+ ): MdxJsxTextElement => ({
265
+ type: 'mdxJsxTextElement',
266
+ name: translationComponentName,
267
+ attributes: [],
268
+ children: children as PhrasingContent[],
269
+ });
270
+
271
+ const unwrapDirectTranslationChildren = (
272
+ children: TreePhrasingContent[],
273
+ translationComponentName: string
274
+ ): TreePhrasingContent[] =>
275
+ children.flatMap((child) => {
276
+ if (
277
+ child.type === 'mdxJsxTextElement' &&
278
+ child.name === translationComponentName &&
279
+ child.attributes.length === 0
280
+ ) {
281
+ return child.children as TreePhrasingContent[];
282
+ }
283
+
284
+ return [child];
285
+ });
286
+
287
+ const wrapContainerChildren = (
288
+ node: Heading | Paragraph | TableCell,
289
+ options: {
290
+ translationComponentName: string;
291
+ translatedNodeComponentName: string;
292
+ }
293
+ ): boolean => {
294
+ const normalizedChildren = unwrapDirectTranslationChildren(
295
+ node.children as TreePhrasingContent[],
296
+ options.translationComponentName
297
+ );
298
+
299
+ if (
300
+ normalizedChildren.length === 1 &&
301
+ isExistingTranslationNode(
302
+ normalizedChildren[0],
303
+ options.translationComponentName,
304
+ options.translatedNodeComponentName
305
+ )
306
+ ) {
307
+ return false;
308
+ }
309
+
310
+ if (
311
+ normalizedChildren.some((child) =>
312
+ isExistingTranslationNode(
313
+ child,
314
+ options.translationComponentName,
315
+ options.translatedNodeComponentName
316
+ )
317
+ )
318
+ ) {
319
+ return false;
320
+ }
321
+
322
+ if (!childrenHaveTranslatableText(normalizedChildren)) {
323
+ return false;
324
+ }
325
+
326
+ node.children = [
327
+ createTranslationWrapper(normalizedChildren, options.translationComponentName),
328
+ ] as unknown as PhrasingContent[];
329
+
330
+ return true;
331
+ };
332
+
333
+ const getImportDeclaration = (root: TreeRoot, source: string): ImportDeclaration | null => {
334
+ for (const child of root.children) {
335
+ if (child.type !== 'mdxjsEsm' || !child.data?.estree?.body?.length) {
336
+ continue;
337
+ }
338
+
339
+ const declaration = child.data.estree.body.find(
340
+ (statement): statement is ImportDeclaration =>
341
+ statement.type === 'ImportDeclaration' &&
342
+ statement.source.type === 'Literal' &&
343
+ statement.source.value === source
344
+ );
345
+
346
+ if (declaration) {
347
+ return declaration;
348
+ }
349
+ }
350
+
351
+ return null;
352
+ };
353
+
354
+ const ensureNamedImport = (root: TreeRoot, source: string, importName: string): void => {
355
+ const existingDeclaration = getImportDeclaration(root, source);
356
+
357
+ if (existingDeclaration) {
358
+ const alreadyImported = existingDeclaration.specifiers.some(
359
+ (specifier) =>
360
+ specifier.type === 'ImportSpecifier' &&
361
+ specifier.imported.type === 'Identifier' &&
362
+ specifier.imported.name === importName &&
363
+ specifier.local.type === 'Identifier' &&
364
+ specifier.local.name === importName
365
+ );
366
+
367
+ if (!alreadyImported) {
368
+ existingDeclaration.specifiers.push(createImportSpecifier(importName));
369
+ }
370
+
371
+ return;
372
+ }
373
+
374
+ root.children.unshift(createImportNode(source, [importName]));
375
+ };
376
+
377
+ const resolveImageReferenceUrl = (
378
+ node: Image | ImageReference,
379
+ definitions: Map<string, Definition>
380
+ ): string => {
381
+ if (node.type === 'image') {
382
+ return node.url;
383
+ }
384
+
385
+ return definitions.get(node.identifier)?.url ?? '';
386
+ };
387
+
388
+ const normalizeStaticImportSpecifier = (value: string): string =>
389
+ value.replace(/[^a-zA-Z0-9_$]/g, '_');
390
+
391
+ const isExternalUrl = (value: string): boolean => /^https?:\/\//i.test(value);
392
+
393
+ const isRelativeAssetUrl = (value: string): boolean =>
394
+ Boolean(value) && !isExternalUrl(value) && !value.startsWith('/');
395
+
396
+ const createTranslatePropsAttribute = (attributeNames: string[]): MdxJsxAttribute =>
397
+ createExpressionAttribute(
398
+ 'translateProps',
399
+ `[${attributeNames.map((name) => JSON.stringify(name)).join(', ')}]`,
400
+ createArrayExpression(attributeNames)
401
+ );
402
+
403
+ const createTranslatedNode = ({
404
+ originalType,
405
+ originalName,
406
+ originalAttributes,
407
+ originalChildren,
408
+ translatedNodeComponentName,
409
+ translateProps,
410
+ }: {
411
+ originalType: 'mdxJsxTextElement' | 'mdxJsxFlowElement';
412
+ originalName: string;
413
+ originalAttributes: MdxJsxAttribute[];
414
+ originalChildren: Array<Content | MdxJsxTextElement | MdxJsxFlowElement>;
415
+ translatedNodeComponentName: string;
416
+ translateProps: string[];
417
+ }): MdxJsxElement => ({
418
+ type: originalType,
419
+ name: translatedNodeComponentName,
420
+ attributes: [
421
+ {
422
+ type: 'mdxJsxAttribute',
423
+ name: 'component',
424
+ value: originalName,
425
+ },
426
+ createTranslatePropsAttribute(translateProps),
427
+ ...originalAttributes,
428
+ ],
429
+ children:
430
+ originalType === 'mdxJsxTextElement'
431
+ ? (originalChildren as PhrasingContent[])
432
+ : originalChildren,
433
+ });
434
+
435
+ const getTranslatableAttributeNames = (
436
+ attributes: MdxJsxAttribute[],
437
+ attributeNames: Set<string>
438
+ ): string[] =>
439
+ attributes
440
+ .filter(
441
+ (attribute) => attributeNames.has(attribute.name) && typeof attribute.value === 'string'
442
+ )
443
+ .map((attribute) => attribute.name);
444
+
445
+ const buildImportedImageAttribute = (variableName: string): MdxJsxAttribute =>
446
+ createExpressionAttribute('src', variableName, createIdentifier(variableName));
447
+
448
+ const createImageAttributes = ({
449
+ srcValue,
450
+ alt,
451
+ title,
452
+ }: {
453
+ srcValue: MdxJsxAttribute['value'];
454
+ alt?: string | null;
455
+ title?: string | null;
456
+ }): MdxJsxAttribute[] => {
457
+ const attributes: MdxJsxAttribute[] = [
458
+ {
459
+ type: 'mdxJsxAttribute',
460
+ name: 'src',
461
+ value: srcValue,
462
+ },
463
+ ];
464
+
465
+ if (typeof alt === 'string') {
466
+ attributes.push({
467
+ type: 'mdxJsxAttribute',
468
+ name: 'alt',
469
+ value: alt,
470
+ });
471
+ }
472
+
473
+ if (typeof title === 'string') {
474
+ attributes.push({
475
+ type: 'mdxJsxAttribute',
476
+ name: 'title',
477
+ value: title,
478
+ });
479
+ }
480
+
481
+ return attributes;
482
+ };
483
+
484
+ const createTranslatedImageNode = ({
485
+ srcValue,
486
+ alt,
487
+ title,
488
+ translatedNodeComponentName,
489
+ asTextElement,
490
+ }: {
491
+ srcValue: MdxJsxAttribute['value'];
492
+ alt?: string | null;
493
+ title?: string | null;
494
+ translatedNodeComponentName: string;
495
+ asTextElement: boolean;
496
+ }): MdxJsxElement => {
497
+ const translateProps = ['alt', 'title'].filter((name) =>
498
+ name === 'alt'
499
+ ? typeof alt === 'string' && alt.length > 0
500
+ : typeof title === 'string' && title.length > 0
501
+ );
502
+
503
+ return {
504
+ type: asTextElement ? 'mdxJsxTextElement' : 'mdxJsxFlowElement',
505
+ name: translatedNodeComponentName,
506
+ attributes: [
507
+ {
508
+ type: 'mdxJsxAttribute',
509
+ name: 'component',
510
+ value: 'img',
511
+ },
512
+ createTranslatePropsAttribute(translateProps),
513
+ ...createImageAttributes({ srcValue, alt, title }),
514
+ ],
515
+ children: [],
516
+ };
517
+ };
518
+
519
+ export function remarkMdxTranslate(userOptions: RemarkMdxTranslateOptions = {}) {
520
+ const options = {
521
+ include: userOptions.include,
522
+ exclude: userOptions.exclude,
523
+ attributeNames: new Set(userOptions.attributeNames || DEFAULT_ATTRIBUTE_NAMES),
524
+ translationComponentName:
525
+ userOptions.translationComponentName || DEFAULT_TRANSLATION_COMPONENT_NAME,
526
+ translatedNodeComponentName:
527
+ userOptions.translatedNodeComponentName || DEFAULT_TRANSLATED_NODE_COMPONENT_NAME,
528
+ translateHookName: userOptions.translateHookName || DEFAULT_TRANSLATE_HOOK_NAME,
529
+ reactImportSource: userOptions.reactImportSource || DEFAULT_REACT_IMPORT_SOURCE,
530
+ runtimeImportSource: userOptions.runtimeImportSource || DEFAULT_RUNTIME_IMPORT_SOURCE,
531
+ };
532
+
533
+ return (tree: Root, file?: { path?: string }) => {
534
+ const root = tree as TreeRoot;
535
+
536
+ if (!shouldProcessFile(file?.path, { include: options.include, exclude: options.exclude })) {
537
+ return;
538
+ }
539
+
540
+ const definitions = new Map<string, Definition>();
541
+ visitTree(root, (node) => {
542
+ if (node.type === 'definition') {
543
+ definitions.set(node.identifier, node);
544
+ }
545
+
546
+ if (node.type === 'code') {
547
+ const transformed = transformCodeSample(node.value, node.lang, {
548
+ translationComponentName: options.translationComponentName,
549
+ translateHookName: options.translateHookName,
550
+ reactImportSource: options.reactImportSource,
551
+ attributeNames: Array.from(options.attributeNames),
552
+ });
553
+
554
+ if (typeof transformed === 'string' && transformed !== node.value) {
555
+ (node as Code).value = transformed;
556
+ }
557
+ }
558
+ });
559
+
560
+ let needsTranslationComponentImport = false;
561
+ let needsTranslatedNodeImport = false;
562
+ const importedImages = new Map<string, string>();
563
+
564
+ const ensureImportedImageExpression = (url: string): MdxJsxAttribute['value'] => {
565
+ if (!isRelativeAssetUrl(url)) {
566
+ return url;
567
+ }
568
+
569
+ let variableName = importedImages.get(url);
570
+ if (!variableName) {
571
+ variableName = `__mdxTranslateImg_${normalizeStaticImportSpecifier(String(importedImages.size))}`;
572
+ importedImages.set(url, variableName);
573
+ }
574
+
575
+ return buildImportedImageAttribute(variableName).value;
576
+ };
577
+
578
+ visitTree(root, (node, parent, index) => {
579
+ if (node.type === 'heading' || node.type === 'paragraph' || node.type === 'tableCell') {
580
+ const didWrap = wrapContainerChildren(node, {
581
+ translationComponentName: options.translationComponentName,
582
+ translatedNodeComponentName: options.translatedNodeComponentName,
583
+ });
584
+
585
+ if (didWrap) {
586
+ needsTranslationComponentImport = true;
587
+ }
588
+ }
589
+
590
+ if (
591
+ (node.type === 'mdxJsxTextElement' || node.type === 'mdxJsxFlowElement') &&
592
+ node.name !== options.translatedNodeComponentName &&
593
+ node.name !== options.translationComponentName &&
594
+ isLowercaseHtmlName(node.name)
595
+ ) {
596
+ const translateProps = getTranslatableAttributeNames(
597
+ node.attributes,
598
+ options.attributeNames
599
+ );
600
+ if (!translateProps.length) {
601
+ return;
602
+ }
603
+
604
+ const translatedNode = createTranslatedNode({
605
+ originalType: node.type,
606
+ originalName: node.name,
607
+ originalAttributes: node.attributes.map((attribute) => {
608
+ if (
609
+ node.name === 'img' &&
610
+ attribute.name === 'src' &&
611
+ typeof attribute.value === 'string' &&
612
+ isRelativeAssetUrl(attribute.value)
613
+ ) {
614
+ return {
615
+ ...attribute,
616
+ value: ensureImportedImageExpression(attribute.value),
617
+ };
618
+ }
619
+
620
+ return attribute;
621
+ }),
622
+ originalChildren: node.children as Array<Content | MdxJsxTextElement | MdxJsxFlowElement>,
623
+ translatedNodeComponentName: options.translatedNodeComponentName,
624
+ translateProps,
625
+ });
626
+
627
+ Object.assign(node, translatedNode);
628
+ needsTranslatedNodeImport = true;
629
+ }
630
+
631
+ if (!parent || index === null) {
632
+ return;
633
+ }
634
+
635
+ if (node.type !== 'image' && node.type !== 'imageReference') {
636
+ return;
637
+ }
638
+
639
+ const url = resolveImageReferenceUrl(node, definitions);
640
+ const alt = 'alt' in node ? (node.alt ?? null) : null;
641
+ const title = 'title' in node ? (node.title ?? null) : null;
642
+ const translateProps = ['alt', 'title'].filter((name) =>
643
+ name === 'alt'
644
+ ? typeof alt === 'string' && alt.length > 0
645
+ : typeof title === 'string' && title.length > 0
646
+ );
647
+
648
+ if (!url || !translateProps.length) {
649
+ return;
650
+ }
651
+
652
+ parent.children[index] = createTranslatedImageNode({
653
+ srcValue: ensureImportedImageExpression(url),
654
+ alt,
655
+ title,
656
+ translatedNodeComponentName: options.translatedNodeComponentName,
657
+ asTextElement: parent.type === 'paragraph' || parent.type === 'heading',
658
+ }) as TreeContent;
659
+
660
+ needsTranslatedNodeImport = true;
661
+ });
662
+
663
+ if (needsTranslationComponentImport) {
664
+ ensureNamedImport(root, options.reactImportSource, options.translationComponentName);
665
+ }
666
+
667
+ if (needsTranslatedNodeImport) {
668
+ ensureNamedImport(root, options.runtimeImportSource, options.translatedNodeComponentName);
669
+ }
670
+
671
+ for (const [url, variableName] of Array.from(importedImages.entries()).reverse()) {
672
+ root.children.unshift(createDefaultImportNode(url, variableName));
673
+ }
674
+ };
675
+ }
@@ -0,0 +1,9 @@
1
+ declare module '@18ways/react' {
2
+ import type { ReactNode } from 'react';
3
+
4
+ export function useT(): (text: string) => string;
5
+ export const T: <X extends ReactNode | string>(props: {
6
+ children: X;
7
+ vars?: Record<string, unknown>;
8
+ }) => ReactNode;
9
+ }
@@ -0,0 +1,61 @@
1
+ 'use client';
2
+
3
+ import { createElement, type ReactNode } from 'react';
4
+ import { useT } from '@18ways/react';
5
+
6
+ type StaticImageLike = {
7
+ src?: string;
8
+ width?: number;
9
+ height?: number;
10
+ };
11
+
12
+ type TranslatedNodeProps = {
13
+ component: keyof JSX.IntrinsicElements;
14
+ translateProps?: string[];
15
+ children?: ReactNode;
16
+ } & Record<string, unknown>;
17
+
18
+ const isStaticImageLike = (value: unknown): value is StaticImageLike =>
19
+ Boolean(
20
+ value &&
21
+ typeof value === 'object' &&
22
+ 'src' in value &&
23
+ typeof (value as StaticImageLike).src === 'string'
24
+ );
25
+
26
+ export function TranslatedNode({
27
+ component,
28
+ translateProps = [],
29
+ children,
30
+ ...props
31
+ }: TranslatedNodeProps) {
32
+ const t = useT();
33
+ const translatedProps: Record<string, unknown> = { ...props };
34
+
35
+ for (const propName of translateProps) {
36
+ const value = translatedProps[propName];
37
+ if (typeof value === 'string' && value.trim()) {
38
+ translatedProps[propName] = t(value) as string;
39
+ }
40
+ }
41
+
42
+ if (component === 'img') {
43
+ const src = translatedProps.src;
44
+ if (isStaticImageLike(src)) {
45
+ translatedProps.src = src.src;
46
+
47
+ if (translatedProps.width == null && typeof src.width === 'number') {
48
+ translatedProps.width = src.width;
49
+ }
50
+
51
+ if (translatedProps.height == null && typeof src.height === 'number') {
52
+ translatedProps.height = src.height;
53
+ }
54
+ }
55
+
56
+ delete translatedProps.placeholder;
57
+ delete translatedProps.blurDataURL;
58
+ }
59
+
60
+ return createElement(component, translatedProps, children);
61
+ }