@uniweb/core 0.11.1 → 0.11.3
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 +1 -1
- package/src/locale-config.js +66 -0
- package/src/tracker.js +50 -1
- package/src/website.js +18 -6
package/package.json
CHANGED
package/src/locale-config.js
CHANGED
|
@@ -41,6 +41,72 @@ function codeOf(entry) {
|
|
|
41
41
|
* @param {*} value - The authored `languages` value.
|
|
42
42
|
* @returns {boolean}
|
|
43
43
|
*/
|
|
44
|
+
/**
|
|
45
|
+
* Human-readable display names for the locales a site is likely to declare.
|
|
46
|
+
*
|
|
47
|
+
* This lives in core, not kit, because core is what BUILDS the locale objects a
|
|
48
|
+
* foundation reads (`website.getLocales()`, `website.langs`). It used to live
|
|
49
|
+
* only in kit, so core could not resolve a label and left the field absent for
|
|
50
|
+
* a plain-string `languages:` entry — which meant the very form this module
|
|
51
|
+
* tells authors to migrate TO produced worse labels than the legacy object form
|
|
52
|
+
* it warns about. Measured 2026-08-23 on a site with 11 locales: dropping the
|
|
53
|
+
* `label:` keys silently turned "Français" into "fr" in the switcher, because
|
|
54
|
+
* the common foundation idiom is `locale.label || locale.code`.
|
|
55
|
+
*
|
|
56
|
+
* Kit re-exports this so `@uniweb/kit`'s `LOCALE_DISPLAY_NAMES` keeps working.
|
|
57
|
+
*/
|
|
58
|
+
export const LOCALE_DISPLAY_NAMES = {
|
|
59
|
+
en: 'English',
|
|
60
|
+
es: 'Español',
|
|
61
|
+
fr: 'Français',
|
|
62
|
+
de: 'Deutsch',
|
|
63
|
+
it: 'Italiano',
|
|
64
|
+
pt: 'Português',
|
|
65
|
+
nl: 'Nederlands',
|
|
66
|
+
pl: 'Polski',
|
|
67
|
+
ru: 'Русский',
|
|
68
|
+
ja: '日本語',
|
|
69
|
+
ko: '한국어',
|
|
70
|
+
zh: '中文',
|
|
71
|
+
'zh-CN': '简体中文',
|
|
72
|
+
'zh-TW': '繁體中文',
|
|
73
|
+
ar: 'العربية',
|
|
74
|
+
he: 'עברית',
|
|
75
|
+
hi: 'हिन्दी',
|
|
76
|
+
th: 'ไทย',
|
|
77
|
+
vi: 'Tiếng Việt',
|
|
78
|
+
tr: 'Türkçe',
|
|
79
|
+
uk: 'Українська',
|
|
80
|
+
cs: 'Čeština',
|
|
81
|
+
el: 'Ελληνικά',
|
|
82
|
+
hu: 'Magyar',
|
|
83
|
+
ro: 'Română',
|
|
84
|
+
sv: 'Svenska',
|
|
85
|
+
da: 'Dansk',
|
|
86
|
+
fi: 'Suomi',
|
|
87
|
+
no: 'Norsk',
|
|
88
|
+
id: 'Bahasa Indonesia',
|
|
89
|
+
ms: 'Bahasa Melayu'
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Resolve a locale's display label.
|
|
94
|
+
*
|
|
95
|
+
* Priority: an explicitly configured `label` -> the display-name table -> the
|
|
96
|
+
* code uppercased. Accepts either a bare code string or a `{ code, label? }`
|
|
97
|
+
* object, so callers do not have to branch on which form the config used.
|
|
98
|
+
*
|
|
99
|
+
* @param {string|{code: string, label?: string}} entry
|
|
100
|
+
* @returns {string} Display label, or '' when the entry carries no code.
|
|
101
|
+
*/
|
|
102
|
+
export function localeLabel(entry) {
|
|
103
|
+
if (typeof entry === 'string') {
|
|
104
|
+
return LOCALE_DISPLAY_NAMES[entry] || entry.toUpperCase()
|
|
105
|
+
}
|
|
106
|
+
if (!entry || typeof entry.code !== 'string' || !entry.code) return ''
|
|
107
|
+
return entry.label || LOCALE_DISPLAY_NAMES[entry.code] || entry.code.toUpperCase()
|
|
108
|
+
}
|
|
109
|
+
|
|
44
110
|
export function isWildcardLanguages(value) {
|
|
45
111
|
return value === '*' || (Array.isArray(value) && value.includes('*'))
|
|
46
112
|
}
|
package/src/tracker.js
CHANGED
|
@@ -414,15 +414,64 @@ export default class Tracker {
|
|
|
414
414
|
* Report a page view. Framework-owned: it carries the acquisition context and
|
|
415
415
|
* dedupes consecutive reports of the same path.
|
|
416
416
|
*
|
|
417
|
+
* ⭐ **`first_of_load` marks the one view that opened this document**, and it
|
|
418
|
+
* is computed here rather than living in `acquisition` — precisely because
|
|
419
|
+
* everything in `acquisition` is *replayed* onto every view, and this must
|
|
420
|
+
* not be. Exactly one emission per `Tracker` can carry it: `currentPath` is
|
|
421
|
+
* `null` only before the first report and is never reset.
|
|
422
|
+
*
|
|
423
|
+
* ⛔ **Why it exists at all, since a consumer holding the events can derive
|
|
424
|
+
* it from `visit`.** Not every consumer holds the events. A counter store
|
|
425
|
+
* that keeps no per-event rows cannot ask *"was this the first `page_view`
|
|
426
|
+
* with this `visit`?"* without remembering which `visit` values it has seen —
|
|
427
|
+
* which is retaining `visit`, arrived at by the back door. This bit makes the
|
|
428
|
+
* document-scoped question answerable with **no collector state at all**, and
|
|
429
|
+
* a store that deliberately retains nothing is a supported consumer, not an
|
|
430
|
+
* unusual one.
|
|
431
|
+
*
|
|
432
|
+
* ⛔ **The name carries the unit on purpose.** Bare `first` reads as *the
|
|
433
|
+
* visitor's first ever view*, which is a claim this file refuses to make and
|
|
434
|
+
* could not make — nothing here survives the document. The unit is **one
|
|
435
|
+
* document load**, so a panel built on it counts loads, never people and
|
|
436
|
+
* never sessions. *(`lane-chain.md` §4a: when a misread name is paid by
|
|
437
|
+
* another lane, put the constraint in the name.)*
|
|
438
|
+
*
|
|
439
|
+
* ⛔ **IT MUST NEVER SHIP IN A RELEASE THAT LACKS `continues`, and that is a
|
|
440
|
+
* standing contract rather than an accident of ordering.** A consumer uses
|
|
441
|
+
* its *presence* as proof that this emitter is new enough to have sent
|
|
442
|
+
* `continues` had the referrer been same-origin — which is what lets an
|
|
443
|
+
* entry-page metric drop the "runtime too old to say" case entirely instead
|
|
444
|
+
* of caveating it. Backport `first_of_load` to a line without `continues`
|
|
445
|
+
* and every such consumer starts counting continuations as arrivals, with
|
|
446
|
+
* nothing anywhere reporting an error. *(The dependency is one-way:
|
|
447
|
+
* `continues` without `first_of_load` is fine and shipped that way.)*
|
|
448
|
+
*
|
|
449
|
+
* ⚖️ **It states a FACT, not a metric** — the same discipline as `continues`.
|
|
450
|
+
* Whether a first-of-load view is an "entry page" also depends on
|
|
451
|
+
* `continues`, and that combination is the consumer's to make; a field named
|
|
452
|
+
* `entry` would age badly the moment a second metric wanted this bit.
|
|
453
|
+
*
|
|
417
454
|
* @param {string} path
|
|
418
455
|
*/
|
|
419
456
|
trackPageView(path) {
|
|
420
457
|
if (!this.arms('page_view') || !path) return
|
|
421
458
|
if (path === this.currentPath) return
|
|
459
|
+
const firstOfLoad = this.currentPath === null
|
|
422
460
|
this.currentPath = path
|
|
423
461
|
// Promptly, rather than waiting out the batch window: a page view is the
|
|
424
462
|
// event most likely to be the only one of a short visit.
|
|
425
|
-
|
|
463
|
+
//
|
|
464
|
+
// Emitted only when true, like `continues` — absent is the negative, and it
|
|
465
|
+
// keeps every later view the size it already was.
|
|
466
|
+
this.enqueue(
|
|
467
|
+
{
|
|
468
|
+
event: 'page_view',
|
|
469
|
+
path,
|
|
470
|
+
...(this.acquisition || {}),
|
|
471
|
+
...(firstOfLoad ? { first_of_load: true } : {})
|
|
472
|
+
},
|
|
473
|
+
true
|
|
474
|
+
)
|
|
426
475
|
}
|
|
427
476
|
|
|
428
477
|
/**
|
package/src/website.js
CHANGED
|
@@ -10,7 +10,7 @@ import EntityStore from './entity-store.js'
|
|
|
10
10
|
import FetcherDispatcher from './fetcher-dispatcher.js'
|
|
11
11
|
import ObservableState from './observable-state.js'
|
|
12
12
|
import { normalizeSeo } from './seo.js'
|
|
13
|
-
import { resolveDefaultLocale } from './locale-config.js'
|
|
13
|
+
import { resolveDefaultLocale, localeLabel } from './locale-config.js'
|
|
14
14
|
import { matchDynamicRoute, decodeRouteValue } from './route-match.js'
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -226,8 +226,12 @@ export default class Website {
|
|
|
226
226
|
/**
|
|
227
227
|
* Build locales list from config
|
|
228
228
|
* Supports both string codes and objects: ['es', 'fr'] or [{code: 'es', label: 'Español'}]
|
|
229
|
-
*
|
|
230
|
-
*
|
|
229
|
+
* Every returned locale carries a resolved `label`: an explicitly configured
|
|
230
|
+
* one wins, otherwise it comes from LOCALE_DISPLAY_NAMES, otherwise the code
|
|
231
|
+
* uppercased. It used to be passed through only when configured, which made
|
|
232
|
+
* the common foundation idiom `locale.label || locale.code` render "fr"
|
|
233
|
+
* instead of "Français" for a plain-string `languages:` entry — i.e. the form
|
|
234
|
+
* locale-config tells authors to migrate to was the one that read worse.
|
|
231
235
|
* @private
|
|
232
236
|
*/
|
|
233
237
|
buildLocalesList(config) {
|
|
@@ -262,6 +266,7 @@ export default class Website {
|
|
|
262
266
|
// Build final array with isDefault flag
|
|
263
267
|
return Array.from(localeMap.values()).map(locale => ({
|
|
264
268
|
...locale,
|
|
269
|
+
label: localeLabel(locale),
|
|
265
270
|
isDefault: locale.code === defaultLocale
|
|
266
271
|
}))
|
|
267
272
|
}
|
|
@@ -857,9 +862,16 @@ export default class Website {
|
|
|
857
862
|
// ─────────────────────────────────────────────────────────────────
|
|
858
863
|
|
|
859
864
|
/**
|
|
860
|
-
* Get all available locales
|
|
861
|
-
*
|
|
862
|
-
*
|
|
865
|
+
* Get all available locales.
|
|
866
|
+
*
|
|
867
|
+
* `label` is always present and always displayable: an explicitly configured
|
|
868
|
+
* label wins, otherwise it is resolved from LOCALE_DISPLAY_NAMES, otherwise it
|
|
869
|
+
* is the code uppercased. So `locale.label` needs no fallback at the call site
|
|
870
|
+
* — the idiom `locale.label || locale.code` is now redundant rather than
|
|
871
|
+
* load-bearing, and kit's `getLocaleLabel()` is only needed for a code that
|
|
872
|
+
* did not come from here.
|
|
873
|
+
*
|
|
874
|
+
* @returns {Array<{code: string, label: string, isDefault: boolean}>}
|
|
863
875
|
*/
|
|
864
876
|
getLocales() {
|
|
865
877
|
return this.locales
|