@wuchale/astro 0.2.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 +14 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +90 -0
- package/dist/runtime.d.ts +9 -0
- package/dist/runtime.js +18 -0
- package/dist/transformer.d.ts +40 -0
- package/dist/transformer.js +269 -0
- package/package.json +61 -0
- package/src/loaders/astro.bundle.js +21 -0
- package/src/loaders/astro.js +14 -0
package/README.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# `@wuchale/astro`
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@wuchale/astro) 
|
|
4
|
+
|
|
5
|
+
An adapter to integrate `wuchale` in Astro projects.
|
|
6
|
+
|
|
7
|
+
**`wuchale`** is a compile-time internationalization (i18n) toolkit that
|
|
8
|
+
requires zero code changes. Write your components naturally, and `wuchale`
|
|
9
|
+
automatically extracts and replaces translatable strings at build time.
|
|
10
|
+
|
|
11
|
+
- Main documentation: [wuchale.dev](https://wuchale.dev)
|
|
12
|
+
- Setup instructions: [Getting started](https://wuchale.dev/intro/start/)
|
|
13
|
+
- Adapter docs: [Astro](https://wuchale.dev/adapters/astro)
|
|
14
|
+
- Repository: [wuchalejs/wuchale](https://github.com/wuchalejs/wuchale)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { HeuristicFunc, Adapter, AdapterArgs, LoaderChoice, CreateHeuristicOpts } from "wuchale";
|
|
2
|
+
/**
|
|
3
|
+
* Create a heuristic function optimized for Astro files
|
|
4
|
+
* Uses the default heuristic which handles translatable vs non-translatable strings
|
|
5
|
+
*/
|
|
6
|
+
export declare function createAstroHeuristic(opts: CreateHeuristicOpts): HeuristicFunc;
|
|
7
|
+
/** Default Svelte heuristic which extracts top level variable assignments as well, leading to `$derived` being auto added when needed */
|
|
8
|
+
export declare const astroDefaultHeuristic: HeuristicFunc;
|
|
9
|
+
type LoadersAvailable = 'default';
|
|
10
|
+
export type AstroArgs = AdapterArgs<LoadersAvailable>;
|
|
11
|
+
export declare function getDefaultLoaderPath(loader: LoaderChoice<LoadersAvailable>, bundle: boolean): string | null;
|
|
12
|
+
/**
|
|
13
|
+
* Create an Astro adapter for wuchale
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```js
|
|
17
|
+
* // wuchale.config.js
|
|
18
|
+
* import { adapter as astro } from '@wuchale/astro'
|
|
19
|
+
*
|
|
20
|
+
* export default defineConfig({
|
|
21
|
+
* adapters: {
|
|
22
|
+
* astro: astro({ files: 'src/pages/**\/*.astro' })
|
|
23
|
+
* }
|
|
24
|
+
* })
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare const adapter: (args?: Partial<AstroArgs>) => Adapter;
|
|
28
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { defaultGenerateLoadID, deepMergeObjects, createHeuristic, defaultHeuristicOpts, } from "wuchale";
|
|
2
|
+
import { pluralPattern } from "wuchale/adapter-vanilla";
|
|
3
|
+
import { AstroTransformer, } from "./transformer.js";
|
|
4
|
+
import { loaderPathResolver } from "wuchale/adapter-utils";
|
|
5
|
+
/**
|
|
6
|
+
* Create a heuristic function optimized for Astro files
|
|
7
|
+
* Uses the default heuristic which handles translatable vs non-translatable strings
|
|
8
|
+
*/
|
|
9
|
+
export function createAstroHeuristic(opts) {
|
|
10
|
+
const defaultHeuristic = createHeuristic(opts);
|
|
11
|
+
return msg => {
|
|
12
|
+
const defRes = defaultHeuristic(msg);
|
|
13
|
+
if (!defRes) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
if (msg.details.scope !== 'script') {
|
|
17
|
+
return defRes;
|
|
18
|
+
}
|
|
19
|
+
if (msg.details.call?.startsWith('Astro.')) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
return defRes;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** Default Svelte heuristic which extracts top level variable assignments as well, leading to `$derived` being auto added when needed */
|
|
26
|
+
export const astroDefaultHeuristic = createAstroHeuristic(defaultHeuristicOpts);
|
|
27
|
+
const defaultRuntime = {
|
|
28
|
+
// Astro is SSR-only, so we use non-reactive runtime by default
|
|
29
|
+
initReactive: ({ funcName }) => funcName == null ? false : null, // Only init in top-level functions
|
|
30
|
+
// Astro is SSR - always use non-reactive
|
|
31
|
+
useReactive: () => false,
|
|
32
|
+
reactive: {
|
|
33
|
+
wrapInit: (expr) => expr,
|
|
34
|
+
wrapUse: (expr) => expr,
|
|
35
|
+
},
|
|
36
|
+
plain: {
|
|
37
|
+
wrapInit: (expr) => expr,
|
|
38
|
+
wrapUse: (expr) => expr,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
const defaultArgs = {
|
|
42
|
+
files: 'src/**/*.astro',
|
|
43
|
+
localesDir: './src/locales',
|
|
44
|
+
patterns: [pluralPattern],
|
|
45
|
+
heuristic: astroDefaultHeuristic,
|
|
46
|
+
granularLoad: false,
|
|
47
|
+
bundleLoad: false,
|
|
48
|
+
loader: 'default',
|
|
49
|
+
generateLoadID: defaultGenerateLoadID,
|
|
50
|
+
runtime: defaultRuntime,
|
|
51
|
+
};
|
|
52
|
+
const resolveLoaderPath = loaderPathResolver(import.meta.url, '../src/loaders', 'js');
|
|
53
|
+
export function getDefaultLoaderPath(loader, bundle) {
|
|
54
|
+
if (loader === 'custom') {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
// just 'default', so
|
|
58
|
+
let loaderName = 'astro';
|
|
59
|
+
if (bundle) {
|
|
60
|
+
loaderName += '.bundle';
|
|
61
|
+
}
|
|
62
|
+
return resolveLoaderPath(loaderName);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Create an Astro adapter for wuchale
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```js
|
|
69
|
+
* // wuchale.config.js
|
|
70
|
+
* import { adapter as astro } from '@wuchale/astro'
|
|
71
|
+
*
|
|
72
|
+
* export default defineConfig({
|
|
73
|
+
* adapters: {
|
|
74
|
+
* astro: astro({ files: 'src/pages/**\/*.astro' })
|
|
75
|
+
* }
|
|
76
|
+
* })
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export const adapter = (args = {}) => {
|
|
80
|
+
const { heuristic, patterns, runtime, loader, ...rest } = deepMergeObjects(args, defaultArgs);
|
|
81
|
+
return {
|
|
82
|
+
transform: async ({ content, filename, index, expr, matchUrl }) => {
|
|
83
|
+
return new AstroTransformer(content, filename, index, heuristic, patterns, expr, runtime, matchUrl).transformAs();
|
|
84
|
+
},
|
|
85
|
+
loaderExts: ['.js', '.ts'],
|
|
86
|
+
defaultLoaderPath: getDefaultLoaderPath(loader, rest.bundleLoad),
|
|
87
|
+
runtime,
|
|
88
|
+
...rest,
|
|
89
|
+
};
|
|
90
|
+
};
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export default ({ t, n, x, a }) => x.map((x, i) => {
|
|
2
|
+
if (typeof x === 'string') {
|
|
3
|
+
return x;
|
|
4
|
+
}
|
|
5
|
+
if (typeof x === 'number') {
|
|
6
|
+
if (!n || i > 0) {
|
|
7
|
+
return a[x];
|
|
8
|
+
}
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const tag = t[x[0]];
|
|
12
|
+
if (tag == null) {
|
|
13
|
+
return 'i18n-404:tag';
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
return tag(x);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Message } from "wuchale";
|
|
2
|
+
import type * as Estree from "acorn";
|
|
3
|
+
import { Transformer } from "wuchale/adapter-vanilla";
|
|
4
|
+
import type { IndexTracker, HeuristicFunc, TransformOutput, RuntimeConf, CatalogExpr, CodePattern, UrlMatcher } from "wuchale";
|
|
5
|
+
import { MixedVisitor, type CommentDirectives } from "wuchale/adapter-utils";
|
|
6
|
+
import type { ElementNode, TextNode, FragmentNode, Node, RootNode, ExpressionNode, AttributeNode, FrontmatterNode, ComponentNode, CustomElementNode } from "@astrojs/compiler/types";
|
|
7
|
+
type MixedAstroNodes = Node;
|
|
8
|
+
export declare class AstroTransformer extends Transformer {
|
|
9
|
+
currentElement?: string;
|
|
10
|
+
inCompoundText: boolean;
|
|
11
|
+
commentDirectivesStack: CommentDirectives[];
|
|
12
|
+
lastVisitIsComment: boolean;
|
|
13
|
+
frontMatterStart?: number;
|
|
14
|
+
mixedVisitor: MixedVisitor<MixedAstroNodes>;
|
|
15
|
+
correctedExprRanges: WeakMap<Node, {
|
|
16
|
+
start: number;
|
|
17
|
+
end: number;
|
|
18
|
+
}>;
|
|
19
|
+
constructor(content: string, filename: string, index: IndexTracker, heuristic: HeuristicFunc, patterns: CodePattern[], catalogExpr: CatalogExpr, rtConf: RuntimeConf, matchUrl: UrlMatcher);
|
|
20
|
+
_saveCorrectedExprRanges: (nodes: Node[], containerEnd: number) => void;
|
|
21
|
+
getRange: (node: Node | AttributeNode) => {
|
|
22
|
+
start: number;
|
|
23
|
+
end: number;
|
|
24
|
+
};
|
|
25
|
+
initMixedVisitor: () => MixedVisitor<Node>;
|
|
26
|
+
_parseAndVisitExpr: (expr: string, startOffset: number, startFromProgram?: boolean) => Message[];
|
|
27
|
+
visitexpression: (node: ExpressionNode) => Message[];
|
|
28
|
+
_visitChildren: (nodes: Node[]) => Message[];
|
|
29
|
+
visitFragmentNode: (node: FragmentNode) => Message[];
|
|
30
|
+
visitelement: (node: ElementNode) => Message[];
|
|
31
|
+
visitcomponent: (node: ComponentNode) => Message[];
|
|
32
|
+
['visitcustom-element']: (node: CustomElementNode) => Message[];
|
|
33
|
+
visitattribute: (node: AttributeNode) => Message[];
|
|
34
|
+
visittext: (node: TextNode) => Message[];
|
|
35
|
+
visitfrontmatter: (node: FrontmatterNode) => Message[];
|
|
36
|
+
visitroot: (node: RootNode) => Message[];
|
|
37
|
+
visitAs: (node: Node | AttributeNode | Estree.AnyNode) => Message[];
|
|
38
|
+
transformAs: () => Promise<TransformOutput>;
|
|
39
|
+
}
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import MagicString from "magic-string";
|
|
2
|
+
import { Message } from "wuchale";
|
|
3
|
+
import { parseScript, Transformer } from "wuchale/adapter-vanilla";
|
|
4
|
+
import { nonWhitespaceText, MixedVisitor, processCommentDirectives, } from "wuchale/adapter-utils";
|
|
5
|
+
import { parse } from "@astrojs/compiler";
|
|
6
|
+
// Astro nodes that can have children
|
|
7
|
+
const nodesWithChildren = [
|
|
8
|
+
"element",
|
|
9
|
+
"component",
|
|
10
|
+
"custom-element",
|
|
11
|
+
"fragment",
|
|
12
|
+
];
|
|
13
|
+
const rtRenderFunc = "_w_Tx_";
|
|
14
|
+
export class AstroTransformer extends Transformer {
|
|
15
|
+
// state
|
|
16
|
+
currentElement;
|
|
17
|
+
inCompoundText = false;
|
|
18
|
+
commentDirectivesStack = [];
|
|
19
|
+
lastVisitIsComment = false;
|
|
20
|
+
frontMatterStart;
|
|
21
|
+
mixedVisitor;
|
|
22
|
+
// astro's compiler gives wrong offsets for expressions
|
|
23
|
+
correctedExprRanges = new WeakMap();
|
|
24
|
+
constructor(content, filename, index, heuristic, patterns, catalogExpr, rtConf, matchUrl) {
|
|
25
|
+
// trim() is VERY important, without it offset positions become wrong due to astro's parser
|
|
26
|
+
super(content.trim(), filename, index, heuristic, patterns, catalogExpr, rtConf, matchUrl);
|
|
27
|
+
this.heuristciDetails.insideProgram = false;
|
|
28
|
+
}
|
|
29
|
+
_saveCorrectedExprRanges = (nodes, containerEnd) => {
|
|
30
|
+
for (const [i, child] of nodes.entries()) {
|
|
31
|
+
if (child.type !== 'expression') {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const nextChild = nodes[i + 1];
|
|
35
|
+
let actualEnd;
|
|
36
|
+
if (nextChild != null) {
|
|
37
|
+
actualEnd = nextChild.position?.start?.offset ?? 0;
|
|
38
|
+
if (nextChild.type === 'expression') {
|
|
39
|
+
actualEnd = this.content.indexOf('{', actualEnd);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
actualEnd = this.content.lastIndexOf('}', containerEnd) + 1;
|
|
44
|
+
}
|
|
45
|
+
this.correctedExprRanges.set(child, {
|
|
46
|
+
start: this.content.indexOf('{', child.position?.start?.offset ?? 0),
|
|
47
|
+
end: actualEnd
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
getRange = (node) => {
|
|
52
|
+
if (node.type === 'expression') {
|
|
53
|
+
return this.correctedExprRanges.get(node) ?? { start: -1, end: -1 };
|
|
54
|
+
}
|
|
55
|
+
let { start, end } = node.position ?? {};
|
|
56
|
+
return {
|
|
57
|
+
start: start?.offset ?? -1,
|
|
58
|
+
end: end?.offset ?? -1,
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
initMixedVisitor = () => new MixedVisitor({
|
|
62
|
+
mstr: this.mstr,
|
|
63
|
+
vars: this.vars,
|
|
64
|
+
getRange: this.getRange,
|
|
65
|
+
isText: node => node.type === 'text',
|
|
66
|
+
isComment: node => node.type === 'comment',
|
|
67
|
+
leaveInPlace: node => [''].includes(node.type),
|
|
68
|
+
isExpression: node => node.type === 'expression',
|
|
69
|
+
getTextContent: (node) => node.value,
|
|
70
|
+
getCommentData: (node) => node.value,
|
|
71
|
+
canHaveChildren: (node) => nodesWithChildren.includes(node.type),
|
|
72
|
+
visitFunc: (child, inCompoundText) => {
|
|
73
|
+
const inCompoundTextPrev = this.inCompoundText;
|
|
74
|
+
this.inCompoundText = inCompoundText;
|
|
75
|
+
const childTxts = this.visitAs(child);
|
|
76
|
+
this.inCompoundText = inCompoundTextPrev; // restore
|
|
77
|
+
return childTxts;
|
|
78
|
+
},
|
|
79
|
+
visitExpressionTag: this.visitexpression,
|
|
80
|
+
fullHeuristicDetails: this.fullHeuristicDetails,
|
|
81
|
+
checkHeuristic: this.getHeuristicMessageType,
|
|
82
|
+
index: this.index,
|
|
83
|
+
wrapNested: (msgInfo, hasExprs, nestedRanges, lastChildEnd) => {
|
|
84
|
+
let begin = `{${rtRenderFunc}({\nx: `;
|
|
85
|
+
if (this.inCompoundText) {
|
|
86
|
+
begin += `${this.vars().nestCtx},\nn: true`;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
const index = this.index.get(msgInfo.toKey());
|
|
90
|
+
begin += `${this.vars().rtCtx}(${index})`;
|
|
91
|
+
}
|
|
92
|
+
if (nestedRanges.length > 0) {
|
|
93
|
+
for (const [i, [childStart, _, haveCtx]] of nestedRanges.entries()) {
|
|
94
|
+
let toAppend;
|
|
95
|
+
if (i === 0) {
|
|
96
|
+
toAppend = `${begin},\nt: [`;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
toAppend = ', ';
|
|
100
|
+
}
|
|
101
|
+
this.mstr.appendRight(childStart, `${toAppend}${haveCtx ? this.vars().nestCtx : '()'} => `);
|
|
102
|
+
}
|
|
103
|
+
begin = `]`;
|
|
104
|
+
}
|
|
105
|
+
let end = '\n})}';
|
|
106
|
+
if (hasExprs) {
|
|
107
|
+
begin += ',\na: [';
|
|
108
|
+
end = ']' + end;
|
|
109
|
+
}
|
|
110
|
+
this.mstr.appendLeft(lastChildEnd, begin);
|
|
111
|
+
this.mstr.appendRight(lastChildEnd, end);
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
_parseAndVisitExpr = (expr, startOffset, startFromProgram = false) => {
|
|
115
|
+
const [ast, comments] = parseScript(expr);
|
|
116
|
+
this.comments = comments;
|
|
117
|
+
this.mstr.offset = startOffset;
|
|
118
|
+
// not just visit Program because visitProgram sets insideProgram to true
|
|
119
|
+
let msgs;
|
|
120
|
+
if (startFromProgram) {
|
|
121
|
+
msgs = this.visit(ast);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
msgs = ast.body.map(this.visit).flat();
|
|
125
|
+
}
|
|
126
|
+
this.mstr.offset = 0; // restore
|
|
127
|
+
return msgs;
|
|
128
|
+
};
|
|
129
|
+
visitexpression = (node) => {
|
|
130
|
+
let expr = '';
|
|
131
|
+
const msgs = [];
|
|
132
|
+
for (const part of node.children) {
|
|
133
|
+
if (part.type === 'text') {
|
|
134
|
+
expr += part.value;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
msgs.push(...this.visitAs(part));
|
|
138
|
+
const { start, end } = this.getRange(part);
|
|
139
|
+
expr += `"${' '.repeat(end - start)}"`;
|
|
140
|
+
}
|
|
141
|
+
const { start } = this.getRange(node);
|
|
142
|
+
msgs.push(...this._parseAndVisitExpr(expr, start + 1));
|
|
143
|
+
return msgs;
|
|
144
|
+
};
|
|
145
|
+
_visitChildren = (nodes) => this.mixedVisitor.visit({
|
|
146
|
+
children: nodes,
|
|
147
|
+
commentDirectives: this.commentDirectives,
|
|
148
|
+
inCompoundText: this.inCompoundText,
|
|
149
|
+
scope: 'markup',
|
|
150
|
+
element: this.currentElement,
|
|
151
|
+
useComponent: this.currentElement !== 'title'
|
|
152
|
+
});
|
|
153
|
+
visitFragmentNode = (node) => this._visitChildren(node.children);
|
|
154
|
+
visitelement = (node) => {
|
|
155
|
+
const currentElement = this.currentElement;
|
|
156
|
+
this.currentElement = node.name;
|
|
157
|
+
const msgs = [];
|
|
158
|
+
for (const attrib of node.attributes) {
|
|
159
|
+
msgs.push(...this.visitAs(attrib));
|
|
160
|
+
}
|
|
161
|
+
this._saveCorrectedExprRanges(node.children, node.position?.end?.offset ?? 0);
|
|
162
|
+
msgs.push(...this._visitChildren(node.children));
|
|
163
|
+
this.currentElement = currentElement;
|
|
164
|
+
return msgs;
|
|
165
|
+
};
|
|
166
|
+
visitcomponent = (node) => this.visitelement(node);
|
|
167
|
+
['visitcustom-element'] = (node) => this.visitelement(node);
|
|
168
|
+
visitattribute = (node) => {
|
|
169
|
+
const heurBase = {
|
|
170
|
+
scope: 'attribute',
|
|
171
|
+
element: this.currentElement,
|
|
172
|
+
attribute: node.name,
|
|
173
|
+
};
|
|
174
|
+
let { start } = this.getRange(node);
|
|
175
|
+
if (node.kind !== 'empty') {
|
|
176
|
+
start = this.content.indexOf('=', start) + 1;
|
|
177
|
+
}
|
|
178
|
+
if (node.kind === 'quoted') {
|
|
179
|
+
const [pass, msgInfo] = this.checkHeuristic(node.value, heurBase);
|
|
180
|
+
if (!pass) {
|
|
181
|
+
return [];
|
|
182
|
+
}
|
|
183
|
+
this.mstr.update(start, start + node.value.length + 2, `{${this.vars().rtTrans}(${this.index.get(msgInfo.toKey())})}`);
|
|
184
|
+
return [msgInfo];
|
|
185
|
+
}
|
|
186
|
+
if (node.kind === 'expression') {
|
|
187
|
+
heurBase.scope = 'script';
|
|
188
|
+
start = this.content.indexOf(node.value, start);
|
|
189
|
+
let expr = node.value;
|
|
190
|
+
if (expr.startsWith('...')) {
|
|
191
|
+
start += 3;
|
|
192
|
+
expr = expr.slice(3);
|
|
193
|
+
}
|
|
194
|
+
return this._parseAndVisitExpr(expr, start);
|
|
195
|
+
}
|
|
196
|
+
return [];
|
|
197
|
+
};
|
|
198
|
+
visittext = (node) => {
|
|
199
|
+
const [startWh, trimmed, endWh] = nonWhitespaceText(node.value);
|
|
200
|
+
const [pass, msgInfo] = this.checkHeuristic(trimmed, {
|
|
201
|
+
scope: 'markup',
|
|
202
|
+
element: this.currentElement,
|
|
203
|
+
});
|
|
204
|
+
if (!pass) {
|
|
205
|
+
return [];
|
|
206
|
+
}
|
|
207
|
+
const { start, end } = this.getRange(node);
|
|
208
|
+
this.mstr.update(start + startWh, end - endWh, `{${this.vars().rtTrans}(${this.index.get(msgInfo.toKey())})}`);
|
|
209
|
+
return [msgInfo];
|
|
210
|
+
};
|
|
211
|
+
visitfrontmatter = (node) => {
|
|
212
|
+
const { start } = this.getRange(node);
|
|
213
|
+
this.frontMatterStart = this.content.indexOf('---', start) + 3;
|
|
214
|
+
return this._parseAndVisitExpr(node.value, this.frontMatterStart, true);
|
|
215
|
+
};
|
|
216
|
+
visitroot = (node) => {
|
|
217
|
+
const msgs = [];
|
|
218
|
+
// ?? [] because it's undefined on an empty file
|
|
219
|
+
for (const rootChild of node.children ?? []) {
|
|
220
|
+
msgs.push(...this.visitAs(rootChild));
|
|
221
|
+
}
|
|
222
|
+
return msgs;
|
|
223
|
+
};
|
|
224
|
+
visitAs = (node) => {
|
|
225
|
+
if (node.type === 'comment') {
|
|
226
|
+
this.commentDirectives = processCommentDirectives(node.value.trim(), this.commentDirectives);
|
|
227
|
+
if (this.lastVisitIsComment) {
|
|
228
|
+
this.commentDirectivesStack[this.commentDirectivesStack.length - 1] = this.commentDirectives;
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
this.commentDirectivesStack.push(this.commentDirectives);
|
|
232
|
+
}
|
|
233
|
+
this.lastVisitIsComment = true;
|
|
234
|
+
return [];
|
|
235
|
+
}
|
|
236
|
+
if (node.type === 'text' && !node.value.trim()) {
|
|
237
|
+
return [];
|
|
238
|
+
}
|
|
239
|
+
let msgs = [];
|
|
240
|
+
const commentDirectivesPrev = this.commentDirectives;
|
|
241
|
+
if (this.lastVisitIsComment) {
|
|
242
|
+
this.commentDirectives = this.commentDirectivesStack.pop();
|
|
243
|
+
this.lastVisitIsComment = false;
|
|
244
|
+
}
|
|
245
|
+
if (this.commentDirectives.ignoreFile) {
|
|
246
|
+
return [];
|
|
247
|
+
}
|
|
248
|
+
if (this.commentDirectives.forceType !== false) {
|
|
249
|
+
msgs = this.visit(node);
|
|
250
|
+
}
|
|
251
|
+
this.commentDirectives = commentDirectivesPrev;
|
|
252
|
+
return msgs;
|
|
253
|
+
};
|
|
254
|
+
transformAs = async () => {
|
|
255
|
+
const { ast } = await parse(this.content);
|
|
256
|
+
this.mstr = new MagicString(this.content);
|
|
257
|
+
this.mixedVisitor = this.initMixedVisitor();
|
|
258
|
+
const msgs = this.visitAs(ast);
|
|
259
|
+
if (this.frontMatterStart == null) {
|
|
260
|
+
this.mstr.appendLeft(0, '---\n');
|
|
261
|
+
this.mstr.appendRight(0, '---\n');
|
|
262
|
+
}
|
|
263
|
+
const header = [
|
|
264
|
+
`import ${rtRenderFunc} from "@wuchale/astro/runtime.js"`,
|
|
265
|
+
this.initRuntime(),
|
|
266
|
+
].join('\n');
|
|
267
|
+
return this.finalize(msgs, this.frontMatterStart ?? 0, header);
|
|
268
|
+
};
|
|
269
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wuchale/astro",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Wuchale i18n adapter for Astro files",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "tsc --watch",
|
|
7
|
+
"build": "tsc",
|
|
8
|
+
"test": "node tests/index.ts"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"i18n",
|
|
12
|
+
"internationalization",
|
|
13
|
+
"translation",
|
|
14
|
+
"gettext",
|
|
15
|
+
"astro",
|
|
16
|
+
"vite",
|
|
17
|
+
"po",
|
|
18
|
+
"vite-plugin",
|
|
19
|
+
"compile-time",
|
|
20
|
+
"ast",
|
|
21
|
+
"translation-tooling",
|
|
22
|
+
"multilingual",
|
|
23
|
+
"localization",
|
|
24
|
+
"l10n",
|
|
25
|
+
"automatic-i18n"
|
|
26
|
+
],
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"src/loaders"
|
|
30
|
+
],
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"default": "./dist/index.js"
|
|
35
|
+
},
|
|
36
|
+
"./runtime.js": {
|
|
37
|
+
"types": "./dist/runtime.d.ts",
|
|
38
|
+
"default": "./dist/runtime.js"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "git+https://github.com/wuchalejs/wuchale.git"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://wuchale.dev",
|
|
46
|
+
"bugs": "https://github.com/wuchalejs/wuchale/issues",
|
|
47
|
+
"author": "K1DV5",
|
|
48
|
+
"license": "MIT",
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@astrojs/compiler": "^2.13.0",
|
|
51
|
+
"@sveltejs/acorn-typescript": "^1.0.8",
|
|
52
|
+
"acorn": "^8.15.0",
|
|
53
|
+
"magic-string": "^0.30.21",
|
|
54
|
+
"wuchale": "^0.19.0"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@types/estree-jsx": "^1.0.5",
|
|
58
|
+
"typescript": "^5.9.3"
|
|
59
|
+
},
|
|
60
|
+
"type": "module"
|
|
61
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Astro bundle loader template (server-side, synchronous, all locales bundled)
|
|
2
|
+
import { toRuntime } from 'wuchale/runtime'
|
|
3
|
+
|
|
4
|
+
const catalogs = __CATALOGS__
|
|
5
|
+
const locales = Object.keys(catalogs)
|
|
6
|
+
|
|
7
|
+
const store = {}
|
|
8
|
+
for (const locale of locales) {
|
|
9
|
+
store[locale] = toRuntime(catalogs[locale], locale)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Get current locale from global context (set by middleware)
|
|
13
|
+
function getCurrentLocale() {
|
|
14
|
+
return globalThis.__wuchale_locale__ || locales[0]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const getRuntime = (/** @type {string} */ _loadID) => {
|
|
18
|
+
return store[getCurrentLocale()]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const getRuntimeRx = getRuntime
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Astro loader template (server-side, synchronous)
|
|
2
|
+
// This is a template file that wuchale will use to generate the actual loader
|
|
3
|
+
import { loadCatalog, loadIDs } from '${PROXY_SYNC}'
|
|
4
|
+
import { currentRuntime } from 'wuchale/load-utils/server'
|
|
5
|
+
|
|
6
|
+
const key = '${KEY}'
|
|
7
|
+
|
|
8
|
+
export { loadCatalog, loadIDs, key }
|
|
9
|
+
|
|
10
|
+
// For non-reactive server-side rendering
|
|
11
|
+
export const getRuntime = (/** @type {string} */ loadID) => currentRuntime(key, loadID)
|
|
12
|
+
|
|
13
|
+
// Same function for compatibility
|
|
14
|
+
export const getRuntimeRx = getRuntime
|