uniweb 1.22.0 → 1.22.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uniweb",
3
- "version": "1.22.0",
3
+ "version": "1.22.2",
4
4
  "description": "Create structured Vite + React sites with content/code separation",
5
5
  "type": "module",
6
6
  "bin": {
@@ -41,15 +41,15 @@
41
41
  "js-yaml": "^4.3.2",
42
42
  "prompts": "^2.4.2",
43
43
  "tar": "^7.5.21",
44
- "@uniweb/schemas": "~1.6.5",
45
- "@uniweb/content-writer": "~1.3.1",
46
44
  "@uniweb/semantic-parser": "~1.13.0",
47
- "@uniweb/core": "~1.4.12"
45
+ "@uniweb/core": "~1.4.12",
46
+ "@uniweb/content-writer": "~1.3.1",
47
+ "@uniweb/schemas": "~1.6.6"
48
48
  },
49
49
  "peerDependencies": {
50
50
  "@uniweb/semantic-parser": "~1.13.0",
51
- "@uniweb/content-reader": "~1.5.7",
52
- "@uniweb/build": "~1.11.0"
51
+ "@uniweb/build": "~1.11.2",
52
+ "@uniweb/content-reader": "~1.5.7"
53
53
  },
54
54
  "peerDependenciesMeta": {
55
55
  "@uniweb/build": {
@@ -546,7 +546,7 @@ Optional attributes: `{size=20}`, `{color=red}`. Custom SVGs: `![Logo](./logo.sv
546
546
 
547
547
  Sizing and loading ride on the image: `{width=800 height=600 loading=lazy fit=cover position=center}`, and its credit as `{author="Jane Doe"}`. A video adds `{poster=./thumb.jpg autoplay muted loop controls}`; a `{role=pdf}` document adds `{preview=./cover.jpg author=… description=…}` and arrives in `content.documents` — `{ url, name, mime, size?, alt, caption, … }` — declare it in `content:` as `documents`. Name a video's poster: a static build makes one from the video only when none is named and ffmpeg is installed, and `uniweb publish` sends only the poster you name.
548
548
 
549
- **Where media lives.** `./x` names a file beside the page, `/x` a file in `public/`, and both reach a static build and a site published with Uniweb. Keep a page's media beside it, and keep in `public/` what needs a fixed address: the favicon, fonts, a file other sites link to. `uniweb push` uploads a file beside a page wherever the page is, in a folder `paths:` mounts too; a file outside the site and the folders it mounts is not uploaded, so put that in `public/`. A site has no `assets/` folder for media: a build stops on a `/x` only `assets/` holds.
549
+ **Where media lives.** `./x` names a file beside the page, `/x` a file in `public/`, and both reach a static build and a site published with Uniweb. Keep a page's media beside it, and keep in `public/` what needs a fixed address: the favicon, fonts, a file other sites link to. A static build copies `public/` whole and adds its own copy of each file a page names there, so a video only a page shows is in `dist/` twice from `public/` and once from beside the page. `uniweb push` uploads a file beside a page wherever the page is, in a folder `paths:` mounts too; a file outside the site and the folders it mounts is not uploaded, so put that in `public/`. A site has no `assets/` folder for media: a build stops on a `/x` only `assets/` holds.
550
550
 
551
551
  **Placement — where a block sits in a section rendered in order.** `{placement=left}` (or `right`, `wide`, `full`) on an image or a video; a table takes it on its **caption line**, the paragraph after the table, after a blank line — `: Plans and prices {placement=wide #tbl-plans}`, which also gives the table a caption and a cross-reference id. It is the author's intent, not a size: kit's `<Render>`, `<Prose>` and `<Article>` lay all four out (`data-placement`, styled by `prose-tokens.css`), and any other component ignores it. A section type says which it honours in `meta.js` — `sequence: { label, placement: true }` — and the build warns when a file asks for one its type does not offer.
552
552
 
@@ -952,7 +952,7 @@ The body — the value of the schema's content field, `body.content`.
952
952
 
953
953
  A path under a folder is relative to `records/`, naming one file or matching many. A record's entry can instead be `{ path, tags, label }` — what the folder says about the record: a component receives them beside it as `$tags` (in the order written) and `$label`, and a query reads them like any key (`where: { $tags: featured }`); such an entry may sit at the top level, where a bare path is refused. A `label` — a record entry's or a folder's — is one text in the site's language, or one per language (`label: { en: Launch video, fr: Vidéo de lancement }`); `$label` is the page's language, else its base language, else the site's. A folder takes `tags:` too. A query reads one folder with `scope: archive` — the folder and everything inside it. A record sits in one folder; if you want a computed subset, that is a query, not a second placement. Structure is for querying, not for navigation — and `folder.yml` never lists a record at the top level (the build refuses it: every file in `records/` is a record already).
954
954
 
955
- ⚠️ **With a backend, `records/` is what `uniweb push` sends** — every record in it. A site with no `records/` at all leaves the backend's records alone; an *empty* `records/` removes them, and the CLI asks before it does. Records pushed to a backend are served once the site is published.
955
+ ⚠️ **With a backend, `records/` is what `uniweb push` sends** — every record in it. A site with no `records/` at all leaves the backend's records alone; an *empty* `records/` removes them, and the CLI asks before it does. Records pushed to a backend are served once the site is published: a record with a data schema reaches the published pages only when `services:` asks for `records: true`, and one with none travels as the published site's files.
956
956
 
957
957
  **`queries.yml` — how content is reached.** A bare map of name → query. A query names a schema and the site's records of that schema are its rows:
958
958
 
@@ -2948,8 +2948,9 @@ YAML, not switches — write `true` or `false`. On a site you push or publish it
2948
2948
  its service, `false` asks it to turn its service off, and an address asks it to leave its own off so
2949
2949
  yours answers. **On a site your host publishes, a service is off unless `services:` asks for it** —
2950
2950
  list the ones the site uses (the templates list theirs). `records: true` asks for your records to be
2951
- delivered live to the published pages: a query whose records have a data schema shows nothing there
2952
- without it, and `publish` says so; a static build reads records from files and ignores it. An entry
2951
+ delivered live to the published pages. Turn it on before the first publish of a page that shows
2952
+ records with a data schema: without it those records never reach the published page, and `publish`
2953
+ warns. A static build reads records from files and ignores it. An entry
2953
2954
  says everything about its service: a setting you remove from it is removed on the next push, and
2954
2955
  removing the entry switches the service off. `uniweb push` and `uniweb publish` send each service
2955
2956
  `site.yml` lists; a service the site has that this copy has never seen (turned on in the app since
@@ -3076,10 +3077,12 @@ These are **body only — no frontmatter**; params and config still come from th
3076
3077
 
3077
3078
  **A tagged data block translates by the shape its key declares** in the component's `meta.js` `data:` — a data schema or an inline field map: its text fields, never an enum, a URL or a `translatable: false` field. A key declared `{}` leaves its blocks to a heuristic that offers any string whose field name and value don't look structural, so a `style: display-black` naming one of your component's looks gets translated too. Declare the shape of every block a translator will see — `{ name: 'string', style: { type: 'string', enum: [...] } }`.
3078
3079
 
3079
- **An image translates by its alt text.** Words alone give it its alt text in that language and keep its file. A translation that is an image also names the file that language shows: `"2d093636": "![La superposition](/docs-media/fr/overview.png)"`. Name it with a `/` path, a file in `public/`. A caption or a video's `poster` can be given the same way. An image with no alt text has nothing to translate, so give every image one.
3080
+ **An image translates by its alt text.** Words alone give it its alt text in that language and keep its file. A translation that is an image also names the file that language shows: `"2d093636": "![La superposition](/docs-media/fr/overview.png)"`. Name it with a `/` path, a file in `public/`, and a build processes it as it does the page's own images; a `./` path has no page's folder to start from, and the build reports it. A caption or a video's `poster` can be given the same way. An image with no alt text has nothing to translate, so give every image one.
3080
3081
 
3081
3082
  **Records translate in the same `extract` run**, into their own manifest at `locales/records/manifest.json` — keyed by the RECORD, so a record translated once is found by every query that returns it. A record whose query has a data schema translates exactly the fields the schema says are text — never an enum, a URL, a date, a number, a file, a reference or a `translatable: false` field — which are the fields a push carries in each language; mark a string that is a code or a key `translatable: false` and no translator is asked for it. An excerpt the build derives is re-derived from each language's body.
3082
3083
 
3084
+ **A data schema's own labels are not in `locales/`.** The schema's `label`, `plural`, `description` and its sections' and fields' labels are written once, in its `source_locale`; their other languages go in the schema's `i18n`, keyed by path: `i18n: { fr: { model.label: Séance, section.brief.field.title.label: Titre } }`. A section is named by its full path (`contributions/publications`), and a `fields:` schema's one section is `brief`. See `development/data-schemas.md`.
3085
+
3083
3086
  **Component side.** Nothing to do: `content.title` arrives in the active language. The one thing a foundation builds is a switcher.
3084
3087
 
3085
3088
  ```jsx
@@ -69,10 +69,6 @@ import {
69
69
  isExtensionPackage,
70
70
  detectFoundationType
71
71
  } from '@uniweb/build'
72
- import {
73
- resolveDefaultLocale,
74
- normalizeLanguageList
75
- } from '@uniweb/core/locale-config'
76
72
  import { readSiteConfig } from '@uniweb/build/site'
77
73
  import { workspacePackages } from '../utils/workspace-packages.js'
78
74
 
@@ -424,34 +420,25 @@ async function loadI18nConfig(projectDir, siteConfig = null) {
424
420
  const localesDir = config.i18n?.localesDir || 'locales'
425
421
  const localesPath = join(projectDir, localesDir)
426
422
 
427
- // Resolve locales (undefined/'*' → all available, array → specific)
428
- const { resolveLocales } = await import('@uniweb/build/i18n')
429
- let locales = await resolveLocales(config.languages, localesPath)
430
-
431
- // Publish filter: build outputs are visitor artifacts. With an explicit
432
- // `publishLanguages:`, only listed locales get dist/{locale}/ content,
433
- // per-locale HTML, hreflang, and translated collections. Draft locales
434
- // stay previewable in dev, which doesn't run this path. Intersecting the
435
- // DISK-resolved list here also covers wildcard (`languages: '*'`) sites.
436
- if (config.publishLanguages != null) {
437
- const publishSet = new Set(normalizeLanguageList(config.publishLanguages))
438
- const dropped = locales.filter((l) => !publishSet.has(l))
439
- locales = locales.filter((l) => publishSet.has(l))
440
- if (dropped.length > 0) {
441
- info(
442
- `Skipping non-publishable locale(s): ${dropped.join(', ')} (not in publishLanguages)`
443
- )
444
- }
423
+ // The site's locales (undefined/'*' → all available, array → specific), with
424
+ // two filters, by the rule the bundle uses to decide whose files to process
425
+ // (`localesToBuild`):
426
+ // - Publish: build outputs are visitor artifacts. With an explicit
427
+ // `publishLanguages:`, only listed locales get dist/{locale}/ content,
428
+ // per-locale HTML, hreflang, and translated collections. Draft locales
429
+ // stay previewable in dev, which doesn't run this path. Intersecting the
430
+ // DISK-resolved list also covers wildcard (`languages: '*'`) sites.
431
+ // - ⛔ The default locale is the root tree, not `dist/<default>/`. An explicit
432
+ // `languages: [en, fr]` resolves verbatim — default included — so the DECLARED
433
+ // form (the one the contract tells authors to use) minted a duplicate copy of
434
+ // every page, while the wildcard form did not. Dropping it keeps content,
435
+ // per-locale HTML and translated records agreeing on one set.
436
+ const { localesToBuild } = await import('@uniweb/build/i18n')
437
+ const { locales, skipped, defaultLocale } = localesToBuild(config, localesPath)
438
+ if (skipped.length > 0) {
439
+ info(`Skipping non-publishable locale(s): ${skipped.join(', ')} (not in publishLanguages)`)
445
440
  }
446
441
 
447
- // ⛔ The default locale is the root tree, not `dist/<default>/`. An explicit
448
- // `languages: [en, fr]` resolves verbatim — default included — so the DECLARED
449
- // form (the one the contract tells authors to use) minted a duplicate copy of
450
- // every page, while the wildcard form did not. Dropping it here keeps content,
451
- // per-locale HTML and translated records agreeing on one set.
452
- const defaultLocale = resolveDefaultLocale(config)
453
- locales = locales.filter((l) => l !== defaultLocale)
454
-
455
442
  if (locales.length === 0) return null
456
443
 
457
444
  return {
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-10-29T01:09:12.877Z",
3
+ "generatedAt": "2026-10-29T14:12:43.968Z",
4
4
  "packages": {
5
5
  "@uniweb/api": {
6
6
  "version": "1.6.5"
7
7
  },
8
8
  "@uniweb/build": {
9
- "version": "1.11.0"
9
+ "version": "1.11.2"
10
10
  },
11
11
  "@uniweb/content-reader": {
12
12
  "version": "1.5.7"
@@ -42,7 +42,7 @@
42
42
  "version": "1.0.1"
43
43
  },
44
44
  "@uniweb/schemas": {
45
- "version": "1.6.5"
45
+ "version": "1.6.6"
46
46
  },
47
47
  "@uniweb/scholar": {
48
48
  "version": "1.1.0"