langsys-js-react 0.6.6 → 0.7.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.
@@ -0,0 +1,122 @@
1
+ import { types, PluginObj, PluginPass } from '@babel/core';
2
+
3
+ /**
4
+ * langsys-js-react/babel — the placeholder transform (spec VAR-6), as a Babel plugin.
5
+ *
6
+ * A value interpolated into translated text becomes a named placeholder, and the value travels as
7
+ * its param, so one sentence registers once however many users it is shown to (VAR-1):
8
+ *
9
+ * <Translate>Hello {user.name}</Translate>
10
+ * → <Translate params={{ name: user.name }}>Hello %name%</Translate>
11
+ *
12
+ * t(`You have ${items.length} items`)
13
+ * → t('You have {items_count} items', undefined, { items_count: items.length })
14
+ *
15
+ * A subtree the core shows as written — `<DontTranslate>`, a host element with `translate="no"` or
16
+ * `data-notrans`, or code and notation (`<template>`, `<noscript>`, `<math>`) — is never translated:
17
+ * a phrase keys it as one opaque element and restores its content, and a block keeps it in its
18
+ * stored content. A value there therefore stays where it is, in both components, and is marked as a
19
+ * value (VAR-3) rather than replaced, so the page shows the value and a block's stored content holds
20
+ * its placeholder; where no marker can stand (below) it is left as written instead. A `translate`
21
+ * or `data-notrans` whose value is an expression is known only at runtime. In a `<Translate>` a
22
+ * value under it is marked and passed as a param too, so excluded it shows as written, and
23
+ * translated the core reads the marker as its placeholder and fills it from the param, which wins
24
+ * over the marked text. In a `<Phrase>` it is a placeholder like any other: translated, the phrase
25
+ * fills it; excluded, the core keys the element as one empty pair and fills the caller's params into
26
+ * an author's exclusion. A `<Phrase>` holding a marker of its own in its text (inside a conditional
27
+ * or a component too) is served as its source until a translation exists, so its other values are
28
+ * marked too, and passed as params; one in its translated text where no marker can stand is left
29
+ * as written, and the phrase gets `data-ls-unnamed`. A name a marker already carries is taken, as an
30
+ * explicit `%name%` is. An opt-out passed through a spread is not seen. Where no marker element
31
+ * can stand — inside MathML and SVG an HTML `<span>` draws nothing, and `<textarea>`, `<title>` and
32
+ * `<option>` hold text only — a value in such a subtree is left as written, and a `<Translate>`,
33
+ * which would store it, gets `data-ls-unnamed` and registers nothing (VAR-7); a `<Phrase>` keys the
34
+ * subtree as one empty pair. `<script>`, `<style>` and `<plaintext>` hold raw text, which a block
35
+ * stores as written and a phrase keys as one empty pair: a value there is left as written, and a `<Translate>`
36
+ * holding one gets `data-ls-unnamed`.
37
+ *
38
+ * <Translate>Signed in as <DontTranslate>{user.email}</DontTranslate></Translate>
39
+ * → <Translate>Signed in as <DontTranslate><span data-ls-param="email">{user.email}</span></DontTranslate></Translate>
40
+ *
41
+ * A block's tokens begin with each element's attributes a translator reads (TOK-3: the core's
42
+ * `TRANSLATABLE_ATTRIBUTES`, and the value of a button or a submit or button input), so in a
43
+ * `<Translate>` a value in one is a placeholder too. A value only in attributes' text travels as the
44
+ * string JavaScript writes there; one that is the whole attribute, or also in the content, travels
45
+ * as given and fills its placeholder as a value fills text, and one React renders as nodes (a list,
46
+ * an object) is put back as a whole prop and fills an attribute's text as JavaScript writes it into a
47
+ * string (`withNodeParams`). On an element whose opt-out or input type is
48
+ * an expression, which only the page decides, the value is left as written and the `<Translate>`
49
+ * gets `data-ls-unnamed`. A `<Phrase>` keys none of its elements' attributes, so they are left as
50
+ * written there.
51
+ *
52
+ * <Translate><img alt={`Photo of ${user.name}`} /> Hi {user.name}</Translate>
53
+ * → <Translate params={{ name: user.name }}><img alt={"Photo of %name%"} /> Hi %name%</Translate>
54
+ *
55
+ * The core reads `%name%` left to right, so where the text before a value ends in a `%` and a name
56
+ * it has not closed (`%s{x}`), the placeholder's own `%` would close it; there the placeholder is
57
+ * written in the stored form, `{name}`, which the core reads the same way (`%s{x}`). The text is read
58
+ * as the core registers it (C0 control characters dropped) and as it renders it (kept).
59
+ *
60
+ * Only `<Translate>` and `<Phrase>` imported from `langsys-js-react` are rewritten, and only the
61
+ * children a host element, a fragment or `<DontTranslate>` renders as text: an expression inside a
62
+ * nested host element (`<b>{x}</b>`) is included; one that is itself JSX or holds JSX
63
+ * (`items.map((i) => <li>…`), a conditional, already marked (`data-ls-param`), or sits inside
64
+ * another component or a nested `<Translate>` / `<Phrase>` is left alone, since React renders nodes
65
+ * from it rather than a value's text. `{children}` (under any TypeScript cast) that is all the
66
+ * text of its run in translated text (what React renders between two elements) is content to
67
+ * translate and is left alone; beside text, or in a subtree that is not translated, it is a value. A value whose type only the page knows, an element held in a variable, is rewritten like
68
+ * any other, and `<Translate>` / `<Phrase>` render it in its placeholder's place
69
+ * (`withNodeParams`). String, number and plain template literals (`{' '}`, `{3}`) are static text,
70
+ * and a fragment (`<>…</>`, React's `Fragment`) is unwrapped, as React passes its children on. A
71
+ * children list the transform changes reaches React as a JSX compiler hands it over from the
72
+ * source, each run of text one string (JSX's whitespace rule applied, a comment dropped), so the
73
+ * runtime never takes a unit's own text for a value split from it (VAR-7); this holds in the JSX
74
+ * compiler's own Babel pass and when the output is compiled again. `t()` calls are rewritten where
75
+ * `t` is the binding a `useT()` call made (and `<Translate>` / `<Phrase>` matched by their import's
76
+ * binding), so another function named `t` is left alone. A `+` chain joins as JavaScript joins it:
77
+ * a sum before the first string, or in parentheses, is one value. A key built in the call (its own
78
+ * text has no space: `errors.${code}`, `status_${status}`) is left as it is, since legacy-key mode
79
+ * looks it up as built. A name the developer wrote (`%name%` anywhere in a unit, a key of a params
80
+ * literal, `{name}` in a `t()` text whose params the build cannot read into: a variable, a spread)
81
+ * is never given to a value. Names follow VAR-2 (`./naming`); an expression
82
+ * no name can be derived from is named `value`, `value_2`, … and reported in
83
+ * `file.metadata.langsys.warnings`, which the Vite plugin and the Next loader surface as build
84
+ * warnings.
85
+ */
86
+
87
+ interface LangsysTransformMetadata {
88
+ /** One entry per unnameable expression: where it is, and the name it was given. */
89
+ warnings: Array<{
90
+ line: number | null;
91
+ column: number | null;
92
+ source: string;
93
+ name: string;
94
+ }>;
95
+ /** How many units were rewritten. */
96
+ rewritten: number;
97
+ }
98
+ interface State extends PluginPass {
99
+ targets: Set<string>;
100
+ /** Local names of `<Phrase>`, which keys code, notation and an author's exclusion as one empty pair. */
101
+ phrases: Set<string>;
102
+ verbatim: Set<string>;
103
+ /** Local names of React's `Fragment`, and of the `react` module itself (`React.Fragment`). */
104
+ fragments: Set<string>;
105
+ reactModules: Set<string>;
106
+ hooks: Set<string>;
107
+ /**
108
+ * The declaring identifiers of `<Translate>` / `<Phrase>` and `useT` imported from the package, and
109
+ * of each `t` a `useT()` call bound: a binding's identifier is the same node after another plugin
110
+ * in the pass crawls the scope again, where its `Binding` object is not.
111
+ */
112
+ targetBindings: Set<types.Identifier>;
113
+ hookBindings: Set<types.Identifier>;
114
+ tBindings: Set<types.Identifier>;
115
+ meta: LangsysTransformMetadata;
116
+ }
117
+ type Babel = {
118
+ types: typeof types;
119
+ };
120
+ declare function langsysPlaceholders({ types: T }: Babel): PluginObj<State>;
121
+
122
+ export { type LangsysTransformMetadata, langsysPlaceholders as default };
@@ -0,0 +1,122 @@
1
+ import { types, PluginObj, PluginPass } from '@babel/core';
2
+
3
+ /**
4
+ * langsys-js-react/babel — the placeholder transform (spec VAR-6), as a Babel plugin.
5
+ *
6
+ * A value interpolated into translated text becomes a named placeholder, and the value travels as
7
+ * its param, so one sentence registers once however many users it is shown to (VAR-1):
8
+ *
9
+ * <Translate>Hello {user.name}</Translate>
10
+ * → <Translate params={{ name: user.name }}>Hello %name%</Translate>
11
+ *
12
+ * t(`You have ${items.length} items`)
13
+ * → t('You have {items_count} items', undefined, { items_count: items.length })
14
+ *
15
+ * A subtree the core shows as written — `<DontTranslate>`, a host element with `translate="no"` or
16
+ * `data-notrans`, or code and notation (`<template>`, `<noscript>`, `<math>`) — is never translated:
17
+ * a phrase keys it as one opaque element and restores its content, and a block keeps it in its
18
+ * stored content. A value there therefore stays where it is, in both components, and is marked as a
19
+ * value (VAR-3) rather than replaced, so the page shows the value and a block's stored content holds
20
+ * its placeholder; where no marker can stand (below) it is left as written instead. A `translate`
21
+ * or `data-notrans` whose value is an expression is known only at runtime. In a `<Translate>` a
22
+ * value under it is marked and passed as a param too, so excluded it shows as written, and
23
+ * translated the core reads the marker as its placeholder and fills it from the param, which wins
24
+ * over the marked text. In a `<Phrase>` it is a placeholder like any other: translated, the phrase
25
+ * fills it; excluded, the core keys the element as one empty pair and fills the caller's params into
26
+ * an author's exclusion. A `<Phrase>` holding a marker of its own in its text (inside a conditional
27
+ * or a component too) is served as its source until a translation exists, so its other values are
28
+ * marked too, and passed as params; one in its translated text where no marker can stand is left
29
+ * as written, and the phrase gets `data-ls-unnamed`. A name a marker already carries is taken, as an
30
+ * explicit `%name%` is. An opt-out passed through a spread is not seen. Where no marker element
31
+ * can stand — inside MathML and SVG an HTML `<span>` draws nothing, and `<textarea>`, `<title>` and
32
+ * `<option>` hold text only — a value in such a subtree is left as written, and a `<Translate>`,
33
+ * which would store it, gets `data-ls-unnamed` and registers nothing (VAR-7); a `<Phrase>` keys the
34
+ * subtree as one empty pair. `<script>`, `<style>` and `<plaintext>` hold raw text, which a block
35
+ * stores as written and a phrase keys as one empty pair: a value there is left as written, and a `<Translate>`
36
+ * holding one gets `data-ls-unnamed`.
37
+ *
38
+ * <Translate>Signed in as <DontTranslate>{user.email}</DontTranslate></Translate>
39
+ * → <Translate>Signed in as <DontTranslate><span data-ls-param="email">{user.email}</span></DontTranslate></Translate>
40
+ *
41
+ * A block's tokens begin with each element's attributes a translator reads (TOK-3: the core's
42
+ * `TRANSLATABLE_ATTRIBUTES`, and the value of a button or a submit or button input), so in a
43
+ * `<Translate>` a value in one is a placeholder too. A value only in attributes' text travels as the
44
+ * string JavaScript writes there; one that is the whole attribute, or also in the content, travels
45
+ * as given and fills its placeholder as a value fills text, and one React renders as nodes (a list,
46
+ * an object) is put back as a whole prop and fills an attribute's text as JavaScript writes it into a
47
+ * string (`withNodeParams`). On an element whose opt-out or input type is
48
+ * an expression, which only the page decides, the value is left as written and the `<Translate>`
49
+ * gets `data-ls-unnamed`. A `<Phrase>` keys none of its elements' attributes, so they are left as
50
+ * written there.
51
+ *
52
+ * <Translate><img alt={`Photo of ${user.name}`} /> Hi {user.name}</Translate>
53
+ * → <Translate params={{ name: user.name }}><img alt={"Photo of %name%"} /> Hi %name%</Translate>
54
+ *
55
+ * The core reads `%name%` left to right, so where the text before a value ends in a `%` and a name
56
+ * it has not closed (`%s{x}`), the placeholder's own `%` would close it; there the placeholder is
57
+ * written in the stored form, `{name}`, which the core reads the same way (`%s{x}`). The text is read
58
+ * as the core registers it (C0 control characters dropped) and as it renders it (kept).
59
+ *
60
+ * Only `<Translate>` and `<Phrase>` imported from `langsys-js-react` are rewritten, and only the
61
+ * children a host element, a fragment or `<DontTranslate>` renders as text: an expression inside a
62
+ * nested host element (`<b>{x}</b>`) is included; one that is itself JSX or holds JSX
63
+ * (`items.map((i) => <li>…`), a conditional, already marked (`data-ls-param`), or sits inside
64
+ * another component or a nested `<Translate>` / `<Phrase>` is left alone, since React renders nodes
65
+ * from it rather than a value's text. `{children}` (under any TypeScript cast) that is all the
66
+ * text of its run in translated text (what React renders between two elements) is content to
67
+ * translate and is left alone; beside text, or in a subtree that is not translated, it is a value. A value whose type only the page knows, an element held in a variable, is rewritten like
68
+ * any other, and `<Translate>` / `<Phrase>` render it in its placeholder's place
69
+ * (`withNodeParams`). String, number and plain template literals (`{' '}`, `{3}`) are static text,
70
+ * and a fragment (`<>…</>`, React's `Fragment`) is unwrapped, as React passes its children on. A
71
+ * children list the transform changes reaches React as a JSX compiler hands it over from the
72
+ * source, each run of text one string (JSX's whitespace rule applied, a comment dropped), so the
73
+ * runtime never takes a unit's own text for a value split from it (VAR-7); this holds in the JSX
74
+ * compiler's own Babel pass and when the output is compiled again. `t()` calls are rewritten where
75
+ * `t` is the binding a `useT()` call made (and `<Translate>` / `<Phrase>` matched by their import's
76
+ * binding), so another function named `t` is left alone. A `+` chain joins as JavaScript joins it:
77
+ * a sum before the first string, or in parentheses, is one value. A key built in the call (its own
78
+ * text has no space: `errors.${code}`, `status_${status}`) is left as it is, since legacy-key mode
79
+ * looks it up as built. A name the developer wrote (`%name%` anywhere in a unit, a key of a params
80
+ * literal, `{name}` in a `t()` text whose params the build cannot read into: a variable, a spread)
81
+ * is never given to a value. Names follow VAR-2 (`./naming`); an expression
82
+ * no name can be derived from is named `value`, `value_2`, … and reported in
83
+ * `file.metadata.langsys.warnings`, which the Vite plugin and the Next loader surface as build
84
+ * warnings.
85
+ */
86
+
87
+ interface LangsysTransformMetadata {
88
+ /** One entry per unnameable expression: where it is, and the name it was given. */
89
+ warnings: Array<{
90
+ line: number | null;
91
+ column: number | null;
92
+ source: string;
93
+ name: string;
94
+ }>;
95
+ /** How many units were rewritten. */
96
+ rewritten: number;
97
+ }
98
+ interface State extends PluginPass {
99
+ targets: Set<string>;
100
+ /** Local names of `<Phrase>`, which keys code, notation and an author's exclusion as one empty pair. */
101
+ phrases: Set<string>;
102
+ verbatim: Set<string>;
103
+ /** Local names of React's `Fragment`, and of the `react` module itself (`React.Fragment`). */
104
+ fragments: Set<string>;
105
+ reactModules: Set<string>;
106
+ hooks: Set<string>;
107
+ /**
108
+ * The declaring identifiers of `<Translate>` / `<Phrase>` and `useT` imported from the package, and
109
+ * of each `t` a `useT()` call bound: a binding's identifier is the same node after another plugin
110
+ * in the pass crawls the scope again, where its `Binding` object is not.
111
+ */
112
+ targetBindings: Set<types.Identifier>;
113
+ hookBindings: Set<types.Identifier>;
114
+ tBindings: Set<types.Identifier>;
115
+ meta: LangsysTransformMetadata;
116
+ }
117
+ type Babel = {
118
+ types: typeof types;
119
+ };
120
+ declare function langsysPlaceholders({ types: T }: Babel): PluginObj<State>;
121
+
122
+ export { type LangsysTransformMetadata, langsysPlaceholders as default };