@feeef.dev/cli 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.
Files changed (61) hide show
  1. package/LICENSE +6 -0
  2. package/README.md +94 -0
  3. package/dist/bin.js +2752 -0
  4. package/dist/bin.js.map +1 -0
  5. package/dist/index.d.ts +6 -0
  6. package/dist/index.js +2730 -0
  7. package/dist/index.js.map +1 -0
  8. package/package.json +67 -0
  9. package/scaffolds/blank/.cursor/rules/feeef-theme.mdc +34 -0
  10. package/scaffolds/blank/.cursor/rules/filterator.mdc +13 -0
  11. package/scaffolds/blank/.cursor/rules/order-form.mdc +17 -0
  12. package/scaffolds/blank/.cursor/rules/theme-source.mdc +32 -0
  13. package/scaffolds/blank/.cursor/skills/feeef-filterator/SKILL.md +15 -0
  14. package/scaffolds/blank/.cursor/skills/feeef-theme/SKILL.md +56 -0
  15. package/scaffolds/blank/.cursor/skills/feeef-theme/reference.md +73 -0
  16. package/scaffolds/blank/.vscode/extensions.json +3 -0
  17. package/scaffolds/blank/.vscode/settings.json +13 -0
  18. package/scaffolds/blank/AGENTS.md +104 -0
  19. package/scaffolds/blank/README.md +21 -0
  20. package/scaffolds/blank/docs/00-INDEX.md +33 -0
  21. package/scaffolds/blank/docs/03-CUSTOM-COMPONENTS.md +116 -0
  22. package/scaffolds/blank/docs/04-WORKFLOW.md +129 -0
  23. package/scaffolds/blank/docs/05-ANTI-PATTERNS.md +80 -0
  24. package/scaffolds/blank/docs/07-SHARED-COMPONENTS.md +144 -0
  25. package/scaffolds/blank/docs/08-ORDER-FORM.md +376 -0
  26. package/scaffolds/blank/docs/09-THEME-PORTING.md +211 -0
  27. package/scaffolds/blank/docs/10-SCHEMA-LIBRARY.md +127 -0
  28. package/scaffolds/blank/docs/11-PAGES-CHECKOUT-THANKS-PRODUCT.md +81 -0
  29. package/scaffolds/blank/docs/12-DARK-LIGHT-THEME.md +164 -0
  30. package/scaffolds/blank/docs/13-TEMPLATE-I18N.md +320 -0
  31. package/scaffolds/blank/docs/14-FILTERATOR.md +433 -0
  32. package/scaffolds/blank/docs/16-AUTHORING-TS-IMPORTS.md +73 -0
  33. package/scaffolds/blank/feeef.template.json +7 -0
  34. package/scaffolds/blank/locales/README.md +4 -0
  35. package/scaffolds/blank/locales/ar.json +10 -0
  36. package/scaffolds/blank/locales/en.json +10 -0
  37. package/scaffolds/blank/package-lock.json +1975 -0
  38. package/scaffolds/blank/package.json +23 -0
  39. package/scaffolds/blank/pages/home/components/hero.tsx +34 -0
  40. package/scaffolds/blank/pages/home/page.json +3 -0
  41. package/scaffolds/blank/props.json +6 -0
  42. package/scaffolds/blank/schema.ts +49 -0
  43. package/scaffolds/blank/tsconfig.json +24 -0
  44. package/scaffolds/blank/types/feeef-live-scope.d.ts +162 -0
  45. package/scaffolds/blank/types/feeef-template-schema.d.ts +84 -0
  46. package/vendor/template-kit/README.md +64 -0
  47. package/vendor/template-kit/bin/dev.mjs +61 -0
  48. package/vendor/template-kit/bin/feeef-template.mjs +212 -0
  49. package/vendor/template-kit/data/lithium-schema.json +4733 -0
  50. package/vendor/template-kit/lib/build.mjs +377 -0
  51. package/vendor/template-kit/lib/compile-component.mjs +188 -0
  52. package/vendor/template-kit/lib/component-meta.mjs +481 -0
  53. package/vendor/template-kit/lib/library.mjs +168 -0
  54. package/vendor/template-kit/lib/locales.mjs +68 -0
  55. package/vendor/template-kit/lib/paths.mjs +84 -0
  56. package/vendor/template-kit/lib/shared.mjs +268 -0
  57. package/vendor/template-kit/lib/theme-schema.mjs +300 -0
  58. package/vendor/template-kit/lib/unpack.mjs +324 -0
  59. package/vendor/template-kit/lib/validate.mjs +207 -0
  60. package/vendor/template-kit/schemas/component.schema.json +55 -0
  61. package/vendor/template-kit/schemas/template.schema.json +18 -0
@@ -0,0 +1,320 @@
1
+ # Template Multilang (JSON per language)
2
+
3
+ > Theme package copy — paths assume this theme folder is the project root.
4
+
5
+ Plan + contract for **theme-authored** translations: one JSON file per locale under the template kit, compiled into `data.i18n`, consumed by custom JSX via `t()`.
6
+
7
+ This is **not** the same as Lithium chrome i18n (`platform chrome messages (not theme locales)` + next-intl) or Flutter slang. Those stay for app UI. Theme locales cover Dawn / custom copy that lives with the theme.
8
+
9
+ ---
10
+
11
+ ## Goals
12
+
13
+ 1. Theme authors maintain `locales/ar.json`, `locales/en.json`, `locales/fr.json` (extensible).
14
+ 2. Build embeds them into `TemplateData.i18n` (and writes `dist/locales/*.json`).
15
+ 3. Custom / Dawn JSX calls `t('checkout.title')` — no hardcoded English/Arabic fallbacks.
16
+ 4. Active locale = `store.configs.defaultLanguage` → `mapStoreLanguageToLocale()` (same as next-intl).
17
+ 5. Later: merchant per-locale prop overrides + language switcher (phased).
18
+
19
+ ## Non-goals (for now)
20
+
21
+ - Translating product `name` / `description` in the DB (separate catalog i18n).
22
+ - URL prefixes (`/en/…`) — storefront stays locale-without-path (see `docs/I18N_ROADMAP.md`).
23
+ - Replacing next-intl for native registry components (order form chrome already uses next-intl).
24
+
25
+ ---
26
+
27
+ ## Architecture
28
+
29
+ ```text
30
+ locales/{ar,en,fr}.json
31
+
32
+ ▼ npm run build
33
+ TemplateData.i18n = {
34
+ defaultLocale: "en",
35
+ locales: ["ar","en","fr"],
36
+ messages: { ar: {...}, en: {...}, fr: {...} }
37
+ }
38
+
39
+ ▼ getTemplate(store)
40
+ CustomLive scope: t('key') / useFeeefT()
41
+
42
+
43
+ Active locale from store.configs.defaultLanguage
44
+ ```
45
+
46
+ ### Two message planes
47
+
48
+ | Plane | Where | Who | Used by |
49
+ |---|---|---|---|
50
+ | **Lithium chrome** | `platform chrome messages (not theme locales)` | Platform | Native components, `useTranslations()` |
51
+ | **Theme locales** | `locales/*.json` → `data.i18n` | Theme author | Custom JSX `t()`, theme defaults |
52
+
53
+ Do **not** dump theme marketing copy into `(platform)` — keep themes portable.
54
+
55
+ ### Store language config (source of locale)
56
+
57
+ Merchant settings live on **`store.configs`** (`feeef.js` / Flutter languages settings UI):
58
+
59
+ ```ts
60
+ interface StoreConfigs {
61
+ /** Active storefront language (what Lithium + theme `t()` use today). */
62
+ defaultLanguage?: string; // e.g. "ar", "en", "fr", "ar-DZ", "arabic"
63
+
64
+ /** Enabled languages for the store (metadata + future switcher). */
65
+ languages?: StoreLanguageConfig[];
66
+ }
67
+
68
+ interface StoreLanguageConfig {
69
+ code: string; // "ar" | "en" | "fr" | …
70
+ name: string; // "Arabic"
71
+ nativeName: string; // "العربية"
72
+ rtl?: boolean; // hint; Lithium also derives RTL from mapped locale
73
+ }
74
+ ```
75
+
76
+ **How the storefront picks a locale today**
77
+
78
+ ```text
79
+ store.configs.defaultLanguage
80
+ → mapStoreLanguageToLocale()
81
+ → Locale "ar" | "en" | "fr" // DEFAULT_LOCALE = "ar" if missing/unknown
82
+ → next-intl messages + <html lang/dir>
83
+ → theme t() / useFeeefLocale() // same mapping
84
+ ```
85
+
86
+ | Field | Used today? | Notes |
87
+ |---|---|---|
88
+ | `defaultLanguage` | **Yes** | Only field that drives Lithium + theme `t()` |
89
+ | `languages[]` | **Not yet** on storefront | Editable in merchant app; reserved for Phase 4 language switcher |
90
+ | `languages[].rtl` | Informational | HTML `dir` comes from mapped locale (`ar` → rtl) |
91
+
92
+ **Mapping rules** (`mapStoreLanguageToLocale`):
93
+
94
+ - Direct: `"ar"` / `"en"` / `"fr"`
95
+ - Region prefix: `"ar-DZ"` → `"ar"`, `"en-US"` → `"en"`
96
+ - Aliases: `"arabic"` / `"العربية"` → `"ar"`, etc.
97
+ - Else → `"ar"` (platform default)
98
+
99
+ **Theme vs store defaults**
100
+
101
+ | Concept | Field | Role |
102
+ |---|---|---|
103
+ | Theme fallback locale | `data.i18n.defaultLocale` | Fallback JSON when a key is missing in the active locale |
104
+ | Store active language | `store.configs.defaultLanguage` | Which locale the visitor sees |
105
+
106
+ Example: theme `defaultLocale: "en"`, store `defaultLanguage: "ar"` → `t()` reads `messages.ar` first, then falls back to `messages.en`.
107
+
108
+ ### Resolution order for `t(key)`
109
+
110
+ 1. `data.i18n.messages[activeLocale][key…]`
111
+ 2. `data.i18n.messages[defaultLocale][key…]`
112
+ 3. Return the key string (fail soft — never throw)
113
+
114
+ Nested keys use dots: `t('checkout.empty')` → `messages.en.checkout.empty`.
115
+
116
+ ---
117
+
118
+ ## Source layout (kit)
119
+
120
+ ```text
121
+
122
+ locales/
123
+ README.md
124
+ ar.json
125
+ en.json
126
+ fr.json
127
+ feeef.template.json # optional "defaultLocale": "en"
128
+ pages/…
129
+ ```
130
+
131
+ Example `en.json`:
132
+
133
+ ```json
134
+ {
135
+ "checkout": {
136
+ "title": "Checkout",
137
+ "empty": "Your cart is empty",
138
+ "continueShopping": "Continue shopping",
139
+ "itemsInCart": "{count} items in cart"
140
+ },
141
+ "thanks": {
142
+ "title": "Thank you!",
143
+ "subtitle": "Your order was received"
144
+ },
145
+ "common": {
146
+ "addToCart": "Add to cart",
147
+ "buyNow": "Buy it now"
148
+ }
149
+ }
150
+ ```
151
+
152
+ ICU / `{count}` interpolation is supported by `t(key, { count: n })` (simple replace, not full ICU).
153
+
154
+ ---
155
+
156
+ ## Compiled shape
157
+
158
+ ```ts
159
+ // TemplateData
160
+ {
161
+ props: { theme: { mode: "light" }, … },
162
+ i18n: {
163
+ defaultLocale: "en",
164
+ locales: ["ar", "en", "fr"],
165
+ messages: {
166
+ ar: { checkout: { title: "إتمام الشراء", … }, … },
167
+ en: { … },
168
+ fr: { … }
169
+ }
170
+ },
171
+ pages: { … }
172
+ }
173
+ ```
174
+
175
+ Also written: `dist/locales/{locale}.json` (same message objects) for tooling / CLI upload.
176
+
177
+ ### Upload note (`store_template_locales`)
178
+
179
+ Canonical storage is the **`store_template_locales`** table (one row per language), not a column on `store_templates`.
180
+
181
+ - Local / `template:dev`: keep using embedded `data.i18n` from the kit build.
182
+ - Publish (`feeef template publish`): strips `data.i18n` from the blob and `PUT`s `dist/locales/*.json` into `store_templates/:id/locales`.
183
+ - Storefront: when `store.templateId` is set, SSR-fetches the locales bundle into effective `data.i18n`; falls back to embedded `data.i18n` for legacy themes.
184
+
185
+ See [04-WORKFLOW.md](./04-WORKFLOW.md) → **Upload / publish** and `feeef --help`.
186
+
187
+ ### Marketplace components (no per-component locales)
188
+
189
+ `template_components` rows carry **code + schemas only** — not their own language packs.
190
+
191
+ When another merchant uses a **public** component from a different theme:
192
+
193
+ - JSX resolves from the library row (`refId` / fork).
194
+ - `t('some.key')` inside that JSX reads the **host store’s** theme i18n (`store_template_locales` / `metadata.templateData.i18n`), not the author’s pack.
195
+ - Missing keys fall back to the key string or `defaultLocale` messages on the host theme.
196
+
197
+ Future option: `template_component_locales` or bundled default messages on fork — **not implemented**; leave as-is for v1.
198
+
199
+ ```jsx
200
+ function App() {
201
+ const title = t('checkout.title');
202
+ const empty = t('checkout.empty');
203
+ // with params:
204
+ const meta = t('checkout.itemsInCart', { count: 3 });
205
+
206
+ // optional hook form (same resolver):
207
+ const translate = useFeeefT();
208
+ const locale = useFeeefLocale(); // "ar" | "en" | "fr"
209
+
210
+ return <h1>{title}</h1>;
211
+ }
212
+ ```
213
+
214
+ | Symbol | Meaning |
215
+ |---|---|
216
+ | `t(key, params?)` | Theme locale lookup |
217
+ | `useFeeefT()` | Same as `t`, hook form |
218
+ | `useFeeefLocale()` | Active locale code |
219
+
220
+ Prefer `t()` for theme strings. Prefer next-intl `useTranslations` only inside **native** TSX modules (not available in react-live).
221
+
222
+ ---
223
+
224
+ ## Prop references (Phase 2)
225
+
226
+ Merchant/theme props may store a key instead of a literal:
227
+
228
+ ```json
229
+ { "title": { "$t": "checkout.title" } }
230
+ ```
231
+
232
+ or compact:
233
+
234
+ ```json
235
+ { "title": "@i18n:checkout.title" }
236
+ ```
237
+
238
+ Runtime (registry / CustomLive props merge) resolves to the active locale string. Plain strings stay as-is (merchant override wins).
239
+
240
+ ---
241
+
242
+ ## Phased delivery
243
+
244
+ | Phase | Deliverable | Status |
245
+ |---|---|---|
246
+ | **0** | Fix `resolveTranslationKey` to honor locale (Lithium `messages/`) | Required |
247
+ | **1** | Kit `locales/*.json` → `data.i18n` + `t()` / `useFeeefT` / `useFeeefLocale` in CustomLive | **PoC** |
248
+ | **2** | Resolve `$t` / `@i18n:` in props at render | Planned |
249
+ | **3** | Flutter editor: locale tabs → `propsLocales` on components | Planned |
250
+ | **4** | Storefront language switcher from `store.configs.languages` | Planned |
251
+ | **5** | Product/catalog content translations (DB) | Separate project |
252
+
253
+ ### Phase 3 sketch (`propsLocales`)
254
+
255
+ ```ts
256
+ {
257
+ type: "custom",
258
+ props: { title: "Default / fallback" },
259
+ propsLocales: {
260
+ ar: { title: "مرحبا" },
261
+ en: { title: "Hello" },
262
+ fr: { title: "Bonjour" }
263
+ }
264
+ }
265
+ ```
266
+
267
+ Merge: `{ ...props, ...propsLocales[activeLocale] }` then resolve `$t`.
268
+
269
+ ---
270
+
271
+ ## RTL / LTR
272
+
273
+ - Active direction: derive from `useStore().store.configs.defaultLanguage` (Dawn components do this locally — do **not** depend on `getFeeefDir` / `useFeeefLocale` scope bindings; react-live may not retranspile when those are added).
274
+ - Prefer: `const dir = locale === 'ar' ? 'rtl' : 'ltr'` after mapping the store language.
275
+ - Local `function t(key, params)` reading `useTemplate().template.data.i18n` is the reliable pattern for theme strings (same data as scope `t()`).
276
+ - Put `dir={dir}` on each theme chrome root (header, footer, PLP, PDP, shells).
277
+ - CSS must use **logical** properties: `margin-inline-start`, `padding-inline`, `inset-inline-start/end`, `border-inline-*`, `text-align: start|end`.
278
+ - Drawers / off-canvas: flip hide transform with `--dn-flip: 1` (LTR) / `-1` (RTL).
279
+ - Do **not** hardcode `dir="ltr"` or `direction: ltr` in multilang themes.
280
+
281
+ ---
282
+
283
+ ## Proof (PoC checklist)
284
+
285
+ - [x] `locales/{ar,en,fr}.json` exist
286
+ - [x] `npm run build` embeds `data.i18n` and writes `dist/locales/`
287
+ - [x] CustomLive scope exposes `t` / `useFeeefT` / `useFeeefLocale`
288
+ - [x] Dawn checkout shell uses `t('checkout.*')`
289
+ - [x] `resolveTranslationKey` caches **per locale**
290
+
291
+ Verified example:
292
+
293
+ ```bash
294
+ npm run build
295
+ # → … / 3 locales → dist/data.json
296
+ # → locales → dist/locales
297
+ node -e 'const d=require("./dist/data.json"); console.log(d.i18n.messages.ar.checkout.title)'
298
+ # → إتمام الشراء
299
+ ```
300
+
301
+ ---
302
+
303
+ ## Anti-patterns
304
+
305
+ | Don’t | Do |
306
+ |---|---|
307
+ | Hardcode `'Checkout'` / `'إتمام الشراء'` in JSX fallbacks | `t('checkout.title')` |
308
+ | Put theme strings only in `(platform)` | Theme `locales/*.json` |
309
+ | Nest translations under `props.i18n` ad hoc | Top-level `data.i18n` |
310
+ | Assume English is always default | `i18n.defaultLocale` + store `defaultLanguage` |
311
+ | Full ICU in Phase 1 | Simple `{param}` replace; upgrade later if needed |
312
+
313
+ ---
314
+
315
+ ## Related
316
+
317
+ - Platform chrome i18n is separate — do not put theme copy there.
318
+ - Custom scope: `docs/03-CUSTOM-COMPONENTS.md`
319
+ - Dark/light: [12-DARK-LIGHT-THEME.md](./12-DARK-LIGHT-THEME.md)
320
+ - Kit: [04-WORKFLOW.md](./04-WORKFLOW.md)