stitch2 0.10.0 → 0.11.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 +6 -1
- package/dist/elements.js +2 -1
- package/dist/i18n-report.js +6 -2
- package/dist/i18n.js +53 -11
- package/package.json +1 -1
- package/skills/stitch2-i18n/SKILL.md +11 -0
package/README.md
CHANGED
|
@@ -197,6 +197,9 @@ shows every locale and the screens use the same strings as the product:
|
|
|
197
197
|
<ex-header title="Settings" data-t-title="settings.title"></ex-header>
|
|
198
198
|
<p data-t="workout.sets" data-t-args="count=16">16 sets</p> <!-- plural form picked by count -->
|
|
199
199
|
<p translate="no">Dumbbell Bench Press</p> <!-- sample content stays as written -->
|
|
200
|
+
<ex-row meta="5 exercises · Linear" data-t-meta="{plan.exercises|count=5} · {plan.linear}"></ex-row> <!-- a template -->
|
|
201
|
+
<ex-row meta="Sun, 4 Oct · 7,920 kg"
|
|
202
|
+
data-t-meta="{@date|value=2026-10-04;weekday=short;day=numeric;month=short} · {@number|value=7920} {unit.kg}"></ex-row>
|
|
200
203
|
```
|
|
201
204
|
|
|
202
205
|
```json
|
|
@@ -209,7 +212,9 @@ shows every locale and the screens use the same strings as the product:
|
|
|
209
212
|
```
|
|
210
213
|
|
|
211
214
|
Messages are JSON or a JS/TS module (its default export, else its first exported object); nested objects become
|
|
212
|
-
dotted keys, and plurals are objects keyed by CLDR category (`one`, `few`, `other`…).
|
|
215
|
+
dotted keys, and plurals are objects keyed by CLDR category (`one`, `few`, `other`…). A value with braces is a
|
|
216
|
+
template: each `{key|name=value;…}` becomes its message and the rest is kept, and `{@date|…}` and `{@number|…}`
|
|
217
|
+
format with `Intl` in the screen's language. Two pseudo-languages need
|
|
213
218
|
no translations: **pseudo** makes every text about 40% longer, accented and bracketed, so cut text shows, and
|
|
214
219
|
**pseudo-rtl** lays the page out right to left.
|
|
215
220
|
|
package/dist/elements.js
CHANGED
|
@@ -42,7 +42,8 @@ export function defineElements(components, options) {
|
|
|
42
42
|
connectedCallback() {
|
|
43
43
|
const props = {};
|
|
44
44
|
for (const a of Array.from(this.attributes)) {
|
|
45
|
-
|
|
45
|
+
// stitch2's own markers and the message keys (i18n.ts translated them already) are not props.
|
|
46
|
+
if (a.name.startsWith('data-s2-') || a.name === 'data-t' || a.name.startsWith('data-t-'))
|
|
46
47
|
continue;
|
|
47
48
|
props[camel(a.name)] = a.value === '' ? true : a.value;
|
|
48
49
|
}
|
package/dist/i18n-report.js
CHANGED
|
@@ -61,9 +61,13 @@ export function scan(html) {
|
|
|
61
61
|
const plural = 'data-t-args' in a && /(^|,)\s*count\s*=/.test(a['data-t-args']);
|
|
62
62
|
const skip = !!top?.skip || 'data-t-skip' in a || a.translate === 'no';
|
|
63
63
|
for (const [name, value] of Object.entries(a)) {
|
|
64
|
-
|
|
64
|
+
const keyed = name === 'data-t' || (name.startsWith('data-t-') && name !== 'data-t-args' && name !== 'data-t-skip');
|
|
65
|
+
if (keyed && value.includes('{'))
|
|
66
|
+
for (const m of value.matchAll(/\{([^{}|@][^{}|]*)(?:\|([^}]*))?\}/g))
|
|
67
|
+
uses.push({ key: m[1], line, plural: plural || /(^|;)\s*count\s*=/.test(m[2] ?? '') });
|
|
68
|
+
else if (name === 'data-t')
|
|
65
69
|
uses.push({ key: value, line, plural });
|
|
66
|
-
else if (
|
|
70
|
+
else if (keyed)
|
|
67
71
|
for (const k of value.split(','))
|
|
68
72
|
uses.push({ key: k.trim(), line, plural });
|
|
69
73
|
else if (!skip && !name.startsWith('data-') && !NOT_TEXT.test(name) && !('data-t-' + name in a)) {
|
package/dist/i18n.js
CHANGED
|
@@ -13,6 +13,10 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
13
13
|
* <span data-t="nav.library">Exercises</span> the element's text
|
|
14
14
|
* <gg-header title="Settings" data-t-title="settings.title"> an attribute (also comma lists: data-t-options)
|
|
15
15
|
* <p data-t="count.sets" data-t-args="count=16">16 sets</p> {count} filled in, plural form picked by count
|
|
16
|
+
* <x-row meta="5 exercises · Linear" data-t-meta="{plan.count|count=5} · {plan.linear}">
|
|
17
|
+
* a template: messages in braces, the rest kept
|
|
18
|
+
* <x-row meta="Sun, 4 Oct" data-t-meta="{@date|value=2026-10-04;weekday=short;day=numeric;month=short}">
|
|
19
|
+
* dates and numbers ({@number|value=7920}) by Intl
|
|
16
20
|
*
|
|
17
21
|
* Opened with ?lang=<locale> (the canvas's Language switch, and check), the page's text is replaced from that
|
|
18
22
|
* locale's messages before its components render, and the page gets lang and dir. Two pseudo-languages need no
|
|
@@ -105,7 +109,25 @@ export async function pagePayload(locale) {
|
|
|
105
109
|
function runtime(p) {
|
|
106
110
|
const missing = new Set(p.missing);
|
|
107
111
|
const plural = new Intl.PluralRules(p.lang);
|
|
112
|
+
// {@date|value=…;weekday=short…} and {@number|value=…;maximumFractionDigits=1…}: Intl's options, in the locale.
|
|
113
|
+
const intl = (key, args) => {
|
|
114
|
+
const opts = {};
|
|
115
|
+
for (const [k, v] of Object.entries(args))
|
|
116
|
+
if (k !== 'value')
|
|
117
|
+
opts[k] = v === 'true' ? true : v === 'false' ? false : /^\d+$/.test(v) ? Number(v) : v;
|
|
118
|
+
try {
|
|
119
|
+
return key === '@date'
|
|
120
|
+
? // As written, whatever the viewer's time zone: the value is read and shown in UTC.
|
|
121
|
+
new Intl.DateTimeFormat(p.lang, { timeZone: 'UTC', ...opts }).format(new Date(/T.*(Z|[+-]\d\d:?\d\d)$/.test(args.value ?? '') || !/T/.test(args.value ?? '') ? (args.value ?? '') : args.value + 'Z'))
|
|
122
|
+
: new Intl.NumberFormat(p.lang, opts).format(Number(args.value));
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
108
128
|
const t = (key, args) => {
|
|
129
|
+
if (key === '@date' || key === '@number')
|
|
130
|
+
return intl(key, args ?? {});
|
|
109
131
|
let msg;
|
|
110
132
|
if (args && args.count !== undefined) {
|
|
111
133
|
const n = Number(args.count);
|
|
@@ -116,11 +138,11 @@ function runtime(p) {
|
|
|
116
138
|
return undefined;
|
|
117
139
|
return msg.replace(/\{(\w+)\}/g, (m, k) => (args && args[k] !== undefined ? args[k] : m));
|
|
118
140
|
};
|
|
119
|
-
const known = (key) => key in p.messages || key + '.other' in p.messages;
|
|
141
|
+
const known = (key) => key.startsWith('@') || key in p.messages || key + '.other' in p.messages;
|
|
120
142
|
const lacks = (key) => missing.has(key) || missing.has(key + '.other') || !known(key);
|
|
121
|
-
const parseArgs = (s) => {
|
|
143
|
+
const parseArgs = (s, sep = ',') => {
|
|
122
144
|
const out = {};
|
|
123
|
-
for (const part of (s ?? '').split(
|
|
145
|
+
for (const part of (s ?? '').split(sep)) {
|
|
124
146
|
const i = part.indexOf('=');
|
|
125
147
|
if (i > 0)
|
|
126
148
|
out[part.slice(0, i).trim()] = part.slice(i + 1).trim();
|
|
@@ -141,21 +163,41 @@ function runtime(p) {
|
|
|
141
163
|
for (const a of Array.from(el.attributes)) {
|
|
142
164
|
if (a.name !== 'data-t' && !a.name.startsWith('data-t-'))
|
|
143
165
|
continue;
|
|
166
|
+
if (a.name === 'data-t-args' || a.name === 'data-t-skip')
|
|
167
|
+
continue;
|
|
144
168
|
const args = parseArgs(el.getAttribute('data-t-args'));
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
169
|
+
let v;
|
|
170
|
+
if (a.value.includes('{')) {
|
|
171
|
+
// A template: each {key} or {key|name=value;name=value} becomes its message, the rest stays as written.
|
|
172
|
+
let whole = true;
|
|
173
|
+
v = a.value.replace(/\{([^{}|]+)(?:\|([^}]*))?\}/g, (_m, key, inline) => {
|
|
174
|
+
keys.push(key);
|
|
175
|
+
const r = t(key, { ...args, ...parseArgs(inline, ';') });
|
|
176
|
+
if (r === undefined)
|
|
177
|
+
whole = false;
|
|
178
|
+
return r ?? '';
|
|
179
|
+
});
|
|
180
|
+
if (!whole)
|
|
181
|
+
v = undefined;
|
|
182
|
+
}
|
|
183
|
+
else if (a.name === 'data-t') {
|
|
184
|
+
v = t(a.value, args);
|
|
149
185
|
keys.push(a.value);
|
|
150
186
|
}
|
|
151
|
-
else
|
|
152
|
-
|
|
187
|
+
else {
|
|
188
|
+
// A list attribute (options="A,B"): one key per item.
|
|
153
189
|
const parts = a.value.split(',').map((k) => k.trim());
|
|
154
190
|
const vals = parts.map((k) => t(k, args));
|
|
155
|
-
if (vals.every((
|
|
156
|
-
|
|
191
|
+
if (vals.every((x) => x !== undefined))
|
|
192
|
+
v = vals.join(',');
|
|
157
193
|
keys.push(...parts);
|
|
158
194
|
}
|
|
195
|
+
if (v === undefined)
|
|
196
|
+
continue;
|
|
197
|
+
if (a.name === 'data-t')
|
|
198
|
+
el.textContent = v;
|
|
199
|
+
else
|
|
200
|
+
el.setAttribute(a.name.slice(7), v);
|
|
159
201
|
}
|
|
160
202
|
if (!keys.length)
|
|
161
203
|
continue;
|
package/package.json
CHANGED
|
@@ -40,8 +40,19 @@ Use the app's own files. Never copy messages into the design folder: two copies
|
|
|
40
40
|
<input placeholder="Search" data-t-placeholder="exercises.search" />
|
|
41
41
|
<p data-t="workout.sets" data-t-args="count=16">16 sets</p> <!-- {count} filled in, plural form by count -->
|
|
42
42
|
<p translate="no">Dumbbell Bench Press</p> <!-- sample content: names, user input, numbers -->
|
|
43
|
+
<s2-row meta="5 exercises · Linear" data-t-meta="{plan.exercises|count=5} · {plan.linear}">
|
|
43
44
|
```
|
|
44
45
|
|
|
46
|
+
A value with braces is a **template**: each `{key}` or `{key|name=value;name=value}` becomes its message and
|
|
47
|
+
the rest stays as written. Use it for lines the app builds from several messages and data (a meta line, a
|
|
48
|
+
list of `label=url` links: `data-t-links="{site.features}=#features,{site.docs}=/docs"`); sample data inside
|
|
49
|
+
it stays literal. Without braces, `data-t-<attr>` on a comma list takes one key per item.
|
|
50
|
+
|
|
51
|
+
Dates and numbers are written by the locale too: `{@date|value=2026-10-04;weekday=short;day=numeric;month=short}`
|
|
52
|
+
("Sun, 4 Oct", "вс, 4 окт.") and `{@number|value=7920}` ("7,920", "7 920"); the options are `Intl.DateTimeFormat`'s and
|
|
53
|
+
`Intl.NumberFormat`'s, and dates are read and shown in UTC. Pair a number with the unit's message:
|
|
54
|
+
`{@number|value=82.5} {unit.kg}`.
|
|
55
|
+
|
|
45
56
|
- Keep the source text in the markup: the screen reads correctly without stitch2, and a missing key shows it.
|
|
46
57
|
- Key every string the product owns: labels, buttons, headings, empty states, hints, units, tab names, the
|
|
47
58
|
placeholder and aria-label too. Sample data (an exercise name, a user's note, a number) gets `translate="no"`.
|