@uniweb/core 0.29.8 → 0.30.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniweb/core",
3
- "version": "0.29.8",
3
+ "version": "0.30.0",
4
4
  "description": "Core classes for the Uniweb platform - Uniweb, Website, Page, Block",
5
5
  "type": "module",
6
6
  "exports": {
@@ -17,9 +17,9 @@
17
17
 
18
18
  /**
19
19
  * Extract a locale code from a declared-language entry. The contract is
20
- * strings-only; legacy `{ code, label }` objects are tolerated on read
21
- * (they appeared in older configs and the runtime's buildLocalesList
22
- * accepted them) but are never produced. The `'*'` wildcard marker
20
+ * strings-only; legacy `{ code, label }` objects are tolerated on READ — a
21
+ * payload built from an older config still renders — and refused where a site
22
+ * is built or pushed (`validateLanguageConfig`). The `'*'` wildcard marker
23
23
  * (auto-discover from `locales/`) is not a locale code.
24
24
  *
25
25
  * @param {*} entry - Declared-language entry.
@@ -101,10 +101,22 @@ export const LOCALE_DISPLAY_NAMES = {
101
101
  */
102
102
  export function localeLabel(entry) {
103
103
  if (typeof entry === 'string') {
104
- return LOCALE_DISPLAY_NAMES[entry] || entry.toUpperCase()
104
+ return LOCALE_DISPLAY_NAMES[entry] || nativeName(entry) || entry.toUpperCase()
105
105
  }
106
106
  if (!entry || typeof entry.code !== 'string' || !entry.code) return ''
107
- return entry.label || LOCALE_DISPLAY_NAMES[entry.code] || entry.code.toUpperCase()
107
+ return entry.label || LOCALE_DISPLAY_NAMES[entry.code] || nativeName(entry.code) || entry.code.toUpperCase()
108
+ }
109
+
110
+ // A language's name in itself (`sv` → `Svenska`, `pt-BR` → `Português (Brasil)`), where the
111
+ // platform knows it — so a code the table above does not list still has a name, and a site
112
+ // never needs to write one. Null when it does not know the code.
113
+ function nativeName(code) {
114
+ try {
115
+ const name = new Intl.DisplayNames([code], { type: 'language' }).of(code)
116
+ return name && name !== code ? name.charAt(0).toLocaleUpperCase(code) + name.slice(1) : null
117
+ } catch {
118
+ return null
119
+ }
108
120
  }
109
121
 
110
122
  export function isWildcardLanguages(value) {
@@ -189,9 +201,12 @@ export function resolvePublishableLocales(config = {}) {
189
201
  * - `default-not-publishable` — the effective default is excluded from the
190
202
  * publishable set.
191
203
  *
204
+ * - `object-language-entry` — a `{ code, label }` entry: languages are named by
205
+ * their code (`localeLabel` gives the name). Read tolerantly, never built or pushed.
206
+ *
192
207
  * Warnings:
193
- * - `invalid-language-entry` / `invalid-publish-language-entry` — non-string
194
- * entries (dropped by normalization).
208
+ * - `invalid-language-entry` / `invalid-publish-language-entry` — entries that
209
+ * name no code (dropped by normalization).
195
210
  * - `duplicate-language` — repeated codes (deduped).
196
211
  * - `dangling-publish-language` — listed but not declared (ignored at
197
212
  * publish, preserved in the file/wire).
@@ -225,10 +240,14 @@ export function validateLanguageConfig(config = {}) {
225
240
  })
226
241
  continue
227
242
  }
243
+ // ⛔ An object names a language by `code` and gives it a `label` — a label no sync can
244
+ // carry (a backend's list is of codes), so a site that pushed it lost it, and the push
245
+ // itself was refused (measured 2026-09-26). A language's name comes from its code
246
+ // (`localeLabel`), in every lane alike.
228
247
  if (typeof entry !== 'string') {
229
- warnings.push({
230
- code: entryCode,
231
- message: `${field} entry for '${code}' uses the legacy object form — use the plain string '${code}'`
248
+ errors.push({
249
+ code: 'object-language-entry',
250
+ message: `${field} entry for '${code}' is an object — write the plain string '${code}' (a language's name comes from its code)`
232
251
  })
233
252
  }
234
253
  if (seen.has(code)) {