@yuneta/gobj-ui 7.26.33 → 7.26.35

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 (45) hide show
  1. package/README.md +223 -53
  2. package/dist/gobj-ui.cjs.js +1053 -144
  3. package/dist/gobj-ui.es.js +1053 -144
  4. package/package.json +2 -2
  5. package/scripts/validate-locales.mjs +672 -96
  6. package/src/c_yui_form.js +229 -11
  7. package/src/c_yui_gclass.js +2 -0
  8. package/src/c_yui_gobj_tree_js.js +2 -0
  9. package/src/c_yui_json.js +2 -0
  10. package/src/c_yui_json_graph.js +2 -0
  11. package/src/c_yui_nav.js +23 -5
  12. package/src/c_yui_shell.css +16 -0
  13. package/src/c_yui_shell.js +282 -26
  14. package/src/c_yui_treedb_graph.js +26 -8
  15. package/src/c_yui_treedb_topic_with_form.js +40 -18
  16. package/src/c_yui_treedb_topics.js +34 -11
  17. package/src/c_yui_window.js +132 -32
  18. package/src/c_yui_window_manager.js +156 -44
  19. package/src/form_json_editor_names.js +448 -0
  20. package/src/form_json_editor_names.wiring.test.js +595 -0
  21. package/src/form_label_i18n.wiring.test.js +169 -0
  22. package/src/form_table_validator.wiring.test.js +192 -0
  23. package/src/form_template_field.wiring.test.js +21 -0
  24. package/src/i18n_key_functions.js +14 -6
  25. package/src/i18n_key_functions.test.js +124 -2
  26. package/src/lib_maplibre.js +26 -2
  27. package/src/lib_maplibre_relocalize.test.js +78 -0
  28. package/src/named_controls.wiring.test.js +468 -0
  29. package/src/nav_cards_helpers.js +9 -1
  30. package/src/nav_item_badge.wiring.test.js +182 -1
  31. package/src/shell_route_map.js +25 -8
  32. package/src/table_editor_name.wiring.test.js +201 -0
  33. package/src/treedb_column_title.wiring.test.js +151 -0
  34. package/src/treedb_json_drill.wiring.test.js +41 -1
  35. package/src/window_layer.wiring.test.js +308 -43
  36. package/src/window_show.wiring.test.js +6 -2
  37. package/src/window_stacking.wiring.test.js +334 -0
  38. package/src/yui_badge.js +58 -13
  39. package/src/yui_badge.test.js +40 -3
  40. package/src/yui_dev.js +2 -1
  41. package/src/yui_file_field.js +2 -0
  42. package/src/yui_frontend_view.js +2 -1
  43. package/src/yui_gclass_view.js +9 -6
  44. package/src/yui_tabulator_i18n.js +42 -0
  45. package/src/yui_tabulator_i18n.test.js +48 -1
package/README.md CHANGED
@@ -79,14 +79,50 @@ Published as `@yuneta/gobj-ui`. Built on top of [`@yuneta/gobj-js`](https://gith
79
79
  > the title bar in place) and its `is-shaded` CSS are **removed**; an app that
80
80
  > relied on shading needs to register a manager.
81
81
  >
82
- > **Where a window lives (7.26.31):** a `C_YUI_WINDOW` whose host names no
83
- > `$parent` mounts in the **overlay layer of the shell** -- above the content,
84
- > below the toolbar's dropdowns, the dialogs and the toasts -- and on
85
- > `document.body` only when there is no shell. It used to be the body always,
86
- > and since `.yui-shell` is `position: fixed` (a stacking context of its own)
87
- > a window at z-index 11 covered the WHOLE shell, modal layer included: the
88
- > confirmation of a window's own close opened under it, out of reach. A
89
- > `$parent` the host names still wins.
82
+ > **Where a window lives (7.26.31, 7.26.35):** a `C_YUI_WINDOW` whose host
83
+ > names no `$parent` mounts in the **window layer of the shell**
84
+ > (`yui_shell_window_layer()`), a stacking context of its own inside the
85
+ > overlay layer -- above the content, below the drawer, the toolbar's
86
+ > dropdowns, the dialogs and the toasts -- and on `document.body` only while
87
+ > there is no shell. It used to be the body always, and since `.yui-shell` is
88
+ > `position: fixed` (a stacking context of its own) a window at z-index 11
89
+ > covered the WHOLE shell, modal layer included: the confirmation of a
90
+ > window's own close opened under it, out of reach. A `$parent` the host names
91
+ > still wins.
92
+ >
93
+ > **Every window of the library lives there, in ONE z-order** (7.26.35). The
94
+ > site map, the raw json of the treedb graph and topics, the gclass viewer and
95
+ > the json of a treedb topic form used to name the shell's POPUP layer: above
96
+ > the drawer, in the way of the toolbar's dropdowns (which opened behind them
97
+ > where they met). And there were two orders: the dock raised its windows to a
98
+ > counter of its own, a window without a dock sat at 3 -- so a window opened
99
+ > later without a dock opened UNDER any docked one, and no click brought it up.
100
+ > Now one number orders every window (`yui_shell_raise_window()`): a window
101
+ > opens on top, a press brings it up (a docked one through the dock's
102
+ > `EV_FOCUS_WINDOW`, one without a dock through its own `EV_SHOW`), and nothing
103
+ > a window takes can reach the drawer or the layers above.
104
+ >
105
+ > **The place follows the shell.** A window parented to the app (the
106
+ > developer window, a device window) outlives a logout that destroys the
107
+ > shell: it goes to the body with it, and the next shell takes it in again.
108
+ > Each shell built or destroyed sends `EV_SHELL_CHANGED` to every gobj that
109
+ > asked with `yui_shell_watch_changes()` -- every `C_YUI_WINDOW` and the dock
110
+ > do -- and each places itself again in its own action. A window its host
111
+ > placed stays where it was put, unless that element was inside the shell
112
+ > that went. From 7.26.31 to 7.26.34 such a window stayed in the destroyed
113
+ > shell's layer: alive, detached, invisible, and the first click on its menu
114
+ > entry closed the invisible one. Its Back entry goes with the shell that held
115
+ > it: after a re-login Back no longer closes it, its ✕ does.
116
+ >
117
+ > **The dock** (`C_YUI_WINDOW_MANAGER`, floating) lives in the shell's popup
118
+ > layer, at the top of it: above every window, below the dialogs and the
119
+ > toasts. It was `document.body` at z-index 1000000, above the whole shell --
120
+ > clickable over a dialog's backdrop. Inline, it moves to the next shell's zone
121
+ > after a logout, and floats in the body between them. A **chip** of the dock
122
+ > is no control itself (7.26.35): it holds two sibling buttons, the toggle
123
+ > (minimise / bring back, named by the window's title, through the FSM:
124
+ > `EV_TOGGLE_WINDOW`) and its ✕ (`close`). It was a `role="button"` holding
125
+ > the ✕ button -- one control inside another.
90
126
 
91
127
  ## Two maintained lines
92
128
 
@@ -3207,14 +3243,35 @@ all for the same reason (a badge that lies costs more than no badge):
3207
3243
  the default look, so a call with three arguments works as it always did.
3208
3244
  - **Unknown item id is a silent no-op**, so an app whose toolbar or menu has no
3209
3245
  such item does not log an error on every tick of whatever feeds the number.
3210
- - Writing the **same** value again touches no DOM: the badge is a
3211
- `role="status"` live region, and rewriting it would have a screen reader
3212
- announce the same number on every tick.
3213
-
3214
- `role="status"` and not `aria-hidden`, deliberately: the button or link
3215
- carries an explicit `aria-label`, and an explicit label **replaces** an
3216
- element's content for a screen reader — a badge inside it would otherwise be
3217
- silent. As its own live region it is both read and announced when it changes.
3246
+ - Writing the **same** value again touches no DOM.
3247
+
3248
+ **What a screen reader gets** (since `7.26.35`). The button or link carries an
3249
+ explicit `aria-label`, and an explicit label **replaces** an element's content
3250
+ for a reader -- inside a `<button>` (the toolbar, an accordion head) the badge
3251
+ is not even in the tree. So:
3252
+
3253
+ - **The count is part of the control's NAME.** The control is labelled by
3254
+ itself and by its badge (`aria-labelledby="<control> <badge>"`,
3255
+ `name_control_with_badge()` in `yui_badge.js`): the self-reference reads its
3256
+ `aria-label`, which `refresh_language()` re-translates, and the badge adds its
3257
+ text -- *"Alarms 3"*, or *"Alarms"* while there is none. Nothing is composed
3258
+ in javascript, so a language switch keeps it right.
3259
+ - **A change is SAID, with the name of what it counts**: *"Alarms, 3"*, in one
3260
+ live region of the shell (`.SHELL_BADGE_STATUS`, `role="status"`,
3261
+ visually hidden), which is always in the tree -- never hidden, never inside a
3262
+ control. `yui_shell_set_nav_item_badge()` and
3263
+ `yui_shell_set_toolbar_item_badge()` say it when the text a reader would see
3264
+ CHANGES and is not empty: the same count is no news, a count going away is
3265
+ not either, one coming back is. A menu entry drawn by three navs is said
3266
+ once. The phrase is a key of its own, `"{{name}}, {{n}}"`, so a language
3267
+ orders and punctuates it its way; it goes through the translator the app gave
3268
+ the shell (`yui_shell_set_translator`).
3269
+
3270
+ Up to `7.26.34` the badge was its own `role="status"`: it said *"3"* with no
3271
+ word of what has three, it was `hidden` while empty -- so the first count was
3272
+ most likely never said -- and inside a `<button>` it was not there at all. A
3273
+ `C_YUI_NAV` used without a shell names its counts the same way, but says
3274
+ nothing when one changes: the live region is the shell's.
3218
3275
 
3219
3276
  **The menu entry's badge reaches every instance of the id.** A menu hosted by
3220
3277
  two zones (a rail on desktop, an icon bar on a phone) draws the same entry
@@ -3599,7 +3656,8 @@ export default { plugins: [ yunetaHtmlPlugin({ defaultTitle: "My App" }) ] };
3599
3656
 
3600
3657
  `npm run build && npm test`, then `npm publish`. A published version reaches
3601
3658
  an app only through **`npm run deploy-round`**: it waits for the registry to
3602
- serve the version, then per app raises the range, installs, builds, deploys
3659
+ serve the version, then per app raises the range (and the app's own version,
3660
+ its third number, in the same edit), installs, builds, deploys
3603
3661
  and reads back the bundle each host serves (`--check` only reads back,
3604
3662
  `--only <name>` does one app, `--no-wait` skips the registry). The private
3605
3663
  consumers are listed in `~/.yuneta/gobj-ui-consumers.json` (see
@@ -3654,7 +3712,7 @@ one. All four attributes are written where the control is built:
3654
3712
  ```
3655
3713
 
3656
3714
  It is a floor, not a preference. The ecosystem was swept against it — the
3657
- deployed page of six SPAs, read control by control — and the target is **zero
3715
+ deployed page of every SPA, read control by control — and the target is **zero
3658
3716
  controls without a name**. Three things it exists to stop, each of which
3659
3717
  shipped:
3660
3718
 
@@ -3703,7 +3761,30 @@ DOM is a race with the next one:
3703
3761
  row-selection checkbox (`yui_tabulator_name_filters` /
3704
3762
  `yui_tabulator_name_row_selects`, hung off `columnsLoaded` and
3705
3763
  `renderComplete`), Tom Select's box in front of the `<select>` it hides
3706
- (`name_form_control`).
3764
+ (`name_form_control`). Since `7.26.35` also:
3765
+ - the box Tabulator draws in a cell being **edited**, named after its
3766
+ column by `yui_tabulator_name_editor(column_def, name_of)` -- asked at
3767
+ the moment of editing (Tabulator takes `editorParams` as a function of
3768
+ the cell, and its built-in editors write the `elementAttributes` it
3769
+ answers), so the name is in the language of that moment with nothing to
3770
+ re-apply;
3771
+ - the ✕ of a selected option of a Tom Select (its `remove_button`
3772
+ plugin's `html`): `remove this option`, reachable and pressed by a key;
3773
+ - the menu of the json editor of a form (vanilla-jsoneditor), known by
3774
+ the class it gives each button (`form_json_editor_names.js`): its titles
3775
+ and the text of its mode buttons through `onRenderMenu`, its aria-labels
3776
+ and `aria-pressed` after each render (`EV_NAME_JSON_EDITORS`), and drawn
3777
+ again on the shell's `EV_LANGUAGE_CHANGED`. Its CONTEXT menu too
3778
+ (`onRenderContextMenu`): its items carry no class, so each is known by
3779
+ the icon it is drawn with, the column it stands in, its place in a
3780
+ dropdown and the editor's mode -- never by its words -- and an item the
3781
+ module does not know stays in English rather than mislabelled; the arrow
3782
+ that opens a dropdown, which the editor names not at all, is `more
3783
+ options`. **A documented limit:** what the editor draws with no hook --
3784
+ its search box, its repair messages, the tip at the foot of the context
3785
+ menu, its sort and transform modals -- stays in its English;
3786
+ - maplibre's canvas and default markers, whose `aria-label` maplibre writes
3787
+ once from its locale: `yui_maplibre_relocalize()` rewrites them.
3707
3788
  - **An `<option>`**, which is text like any other: it carries `data-i18n`,
3708
3789
  with `value` kept explicit — a translated option with no value tells the FSM
3709
3790
  to enter a mode that does not exist.
@@ -3846,14 +3927,30 @@ call of t()?" (since `7.26.32`; up to `7.26.31` it was a call NAMED `t` or
3846
3927
  `tr`): a name `t` or `tr` (whatever binds it), a member `.t` / `.tr`
3847
3928
  (`i18next.t`), i18next's `t` imported under any name (`import {t as tt} from
3848
3929
  "i18next"`), or an alias of one -- a const (`const translate = t`), a
3849
- `.bind()` (`i18next.t.bind(i18next)`), a re-export of a module the reader
3850
- reads --, called as `t(k)`, `t?.(k)`, `t.call(x, k)` or `t.apply(x, [k])`. An
3851
- ARRAY given to it is i18next's list of fallback keys, and each one is a key:
3852
- `t([specific, "generic"])` asks for "generic". The places, since `7.26.32`, also
3853
- include `el.dataset.i18nTitle = k` (`dataset.i18nX` IS the attribute
3854
- `data-i18n-x`), and an attribute or property NAMED by a const:
3930
+ `.bind()` (`i18next.t.bind(i18next)`), a destructuring (since `7.26.35`:
3931
+ `const {t: tt} = i18next`, a parameter `{t: tt}`), a re-export of a module the
3932
+ reader reads --, called as `t(k)`, `t?.(k)`, `t.call(x, k)` or
3933
+ `t.apply(x, [k])`. An ARRAY given to it is i18next's list of fallback keys, and
3934
+ each one is a key: `t([specific, "generic"])` asks for "generic". The places,
3935
+ since `7.26.32`, also include `el.dataset.i18nTitle = k` (`dataset.i18nX` IS
3936
+ the attribute `data-i18n-x`), and an attribute or property NAMED by a const:
3855
3937
  `el.setAttribute(ATTR, k)`, `{[ATTR]: k}`, `x[ATTR] = k`, with `const ATTR =
3856
- "data-i18n-title"`. What is followed:
3938
+ "data-i18n-title"`; and since `7.26.35` `el.setAttributeNS(ns, ATTR, k)`, and a
3939
+ translator handed as a CALLBACK:
3940
+
3941
+ - `keys.map(t)`, `.forEach(t)` (and `flatMap`, `filter`, `find`, `findLast`,
3942
+ `some`, `every`), `Array.from(keys, t)`: each ELEMENT is a key, read like any
3943
+ other -- a const array, a published list, an import of one are followed;
3944
+ - `promise.then(t)`, `.catch(t)`: what the promise settles with is a key no
3945
+ source writes.
3946
+
3947
+ Both are held to the rule per call (below): an array the reader cannot follow,
3948
+ and any promise, fails unless the call is marked
3949
+ (`/* i18n: data */ rows.map(t)`; or before the translator,
3950
+ `load().then(/* i18n: data */ t)`). A translator handed to a FUNCTION is not
3951
+ one of these: `{t: t}`, `yui_tabulator_lang(t)`, `refresh_language($el, t)` --
3952
+ it is how gobj-ui receives one, and the function calls it with keys of its
3953
+ own. What is followed:
3857
3954
 
3858
3955
  - a string literal, a template with no `${}`, and every branch of `?:`, `||`
3859
3956
  and `??` (the right one of `&&`), so
@@ -3903,13 +4000,39 @@ function pick(ok) {
3903
4000
  t(pick(answer)); // asks for both
3904
4001
  ```
3905
4002
 
3906
- What it cannot follow cannot be known: a `let`, a parameter of an app's
3907
- function, a call it does not read, a concatenation, data from a backend. A key
3908
- that arrives that way is PUBLISHED by its module (below), and the place it is
3909
- read is MARKED; a marked `let` -- or a marked member or element of one -- is the
3910
- one exception to "not followed" (below). A source file that cannot be parsed
4003
+ What it cannot follow cannot be known: a `let` given anything but literals, a
4004
+ parameter of an app's function, a call it does not read, a concatenation, data
4005
+ from a backend. A key that arrives that way is PUBLISHED by its module (below),
4006
+ and the place it is read is MARKED; a marked `let` -- or a marked member or
4007
+ element of one -- is followed (below). A source file that cannot be parsed
3911
4008
  fails the build, because its keys would go unchecked.
3912
4009
 
4010
+ **An UNMARKED `let` that is only ever given literals IS followed** (since
4011
+ `7.26.35`), where `t()`, an `i18n` / `code` value or a `data-i18n*` attribute
4012
+ reads a key: when every value it is given in its scope -- its initializer, every
4013
+ `=`, `||=`, `??=`, `&&=` -- is WRITTEN as a string literal, a const bound to one
4014
+ (of the module, or imported), `null` / `undefined`, another such `let`, or a
4015
+ `?:` / `||` / `??` of those (the right one of `&&`), the variable can only ever
4016
+ hold those literals, and each is a key asked for. It is exact, not a guess, so
4017
+ it fails nothing new: a key missing from the locales fails as any key does.
4018
+
4019
+ ```js
4020
+ let state_key = (st === "ST_MONITORING")? "monitor monitoring" : "monitor disconnected";
4021
+ if(connecting) {
4022
+ state_key = "monitor connecting";
4023
+ }
4024
+ $state.setAttribute("data-i18n", state_key); // asks for all three
4025
+ $state.textContent = t(state_key);
4026
+ ```
4027
+
4028
+ A `let` given anything else is not followed and stays invisible: a property of
4029
+ a table (`let m = MONTHS[i]`: the reader would read every value of the table,
4030
+ which is enough for a marker that names lists, and too much to ask the locales
4031
+ for), a call (`let k = busy_key(gobj)`), a parameter, data, or a write it cannot
4032
+ read (`k += "!"`, a `for...of` over it, a destructuring). At a key position of a
4033
+ key function nothing changes: a `let` there is still a violation of the rule per
4034
+ call (below), which wants a const or a marker.
4035
+
3913
4036
  **A key handed to a FUNCTION is read where it is handed.**
3914
4037
  `yui_shell_show_error(shell, "some records were gone before the delete")` asks
3915
4038
  `t()` for its key INSIDE the function, where no scan of `t("...")` sees it, and
@@ -4054,7 +4177,9 @@ write it as a literal or a `const` where it is read, and the reader sees it.
4054
4177
  `const key = maximized ? "restore" : "maximize"; t(key);` asks for both keys,
4055
4178
  and so do the pager's ✕, the camera's anchor button, the nav's landmark, the
4056
4179
  selection bar's clear button and the treedb form's title, which are written
4057
- that way. The same code with a `let` asks for neither.
4180
+ that way. The same code with a `let` asks for both too at `t()` (since
4181
+ `7.26.35`: a `let` only ever given literals is followed), but at a key position
4182
+ of a key function it fails the rule per call, which wants the const.
4058
4183
 
4059
4184
  ```js
4060
4185
  export const MY_VIEW_I18N_KEYS = ['shape', 'radius', 'apply to'];
@@ -4088,9 +4213,11 @@ list in silence, and a key holding `]` dropped the whole array; up to
4088
4213
  WRITTEN, so a key it is given later is asked for by nobody. `X.push(k)`,
4089
4214
  `X.splice(i, 0, k)`, `X.unshift(k)`, `X.fill(k)`, `X[i] = k`, `X.length = n`,
4090
4215
  `Object.assign(X, ...)` and `delete X[i]` fail the build, in the module that
4091
- publishes the list and in every module that imports it (under any name). Up
4092
- to `7.26.31` an `export let` list was read and could be pushed into, and the
4093
- pushed keys went unasked:
4216
+ publishes the list and in every module that imports it (under any name; since
4217
+ `7.26.35` also as a member of a namespace import, `M.X_I18N_KEYS.push(k)`, and
4218
+ through an ALIAS: `const a = X_I18N_KEYS; a.push(k)`, or a `let` given nothing
4219
+ else). Up to `7.26.31` an `export let` list was read and could be pushed into,
4220
+ and the pushed keys went unasked:
4094
4221
 
4095
4222
  ```js
4096
4223
  export let MY_VIEW_I18N_KEYS = ["shape"]; // fails: a published list is an export const
@@ -4117,12 +4244,24 @@ confirm_then(gobj, /* i18n: C_YUI_FORM_I18N_KEYS */ kw.warning || "all changes w
4117
4244
  follow must be in them: a marker is a claim, and it is checked. A key BUILT
4118
4245
  at run time is published that way and marked with its list -- gui_agent's
4119
4246
  monitor names a control's label `monitor ${control}`, and publishes the six
4120
- keys that template builds in `C_AGENT_MONITOR_I18N_KEYS`. A template with
4121
- `${}` under a list marker must have the SHAPE of some key of the lists
4122
- (since `7.26.32`): `monitor ${control}` is checked against `^monitor .*$`,
4123
- so a marker naming the wrong list fails. Every key of a published list is
4124
- asked for, so a control whose key the list lacks is the one thing no check
4125
- sees: the list sits next to the template, and a comment says so.
4247
+ keys that template builds in `C_AGENT_MONITOR_I18N_KEYS` (and the questions,
4248
+ `monitor confirm ${control}`, in `C_AGENT_MONITOR_CONFIRM_I18N_KEYS`). A
4249
+ template with `${}` under a list marker must have the SHAPE of some key of
4250
+ the lists (since `7.26.32`): `monitor ${control}` is checked against
4251
+ `^monitor .*$`, so a marker naming the wrong list fails. And (since
4252
+ `7.26.35`) a key of that shape that a NARROWER marked template builds -- one
4253
+ whose shape lies inside it, `monitor confirm ${control}` -- is that
4254
+ template's: up to `7.26.34` `monitor ${control}` marked with the list of the
4255
+ questions passed, because "monitor confirm start" matches `^monitor .*$`;
4256
+ now it fails, naming the narrower template. (The wider template builds such
4257
+ a key only when what fills its `${}` carries the narrower one's literal --
4258
+ "confirm start" for a control --, which code that writes both does not do.)
4259
+ Every key of a published list is asked for, so a control whose key the list
4260
+ LACKS is the one thing the validator cannot see: what a template builds is
4261
+ data. The app holds such a list to its source with a test of its own --
4262
+ gui_agent's `monitor_i18n_keys.test.js` derives every control from
4263
+ `SCENARIO_ACTIONS` and `scenario_controls()`, and fails until both lists
4264
+ hold exactly their keys.
4126
4265
 
4127
4266
  **A `data` marker is a claim too** (since `7.26.29`). On what the reader
4128
4267
  follows to keys and nothing else -- a literal, a template with no `${}`, a
@@ -4189,7 +4328,9 @@ t(/* i18n: C_YUI_WINDOW_I18N_KEYS */ key);
4189
4328
  ```
4190
4329
 
4191
4330
  The rule is the same under `i18n: data`: the literals of a marked variable are
4192
- read, and one that holds nothing else fails.
4331
+ read, and one that holds nothing else fails. (Unmarked, such a variable fails
4332
+ nothing: since `7.26.35` its literals are simply asked for, above. The marker
4333
+ is what makes it a claim to check.)
4193
4334
 
4194
4335
  **A `let` that holds a TABLE is held the same way** (since the same round:
4195
4336
  up to then a marked member of one passed unread). When what is marked is read
@@ -4271,7 +4412,13 @@ so it never changes language. Hand it the key. A translated text is the value
4271
4412
  of any TRANSLATOR (since `7.26.32`; up to `7.26.31` only a call named `t`, so
4272
4413
  `tr("save")`, an alias of t, `t?.()` or `t.call()` passed marked -- and an
4273
4414
  unmarked `tr()` failed with *"cannot follow"*, whose advice was the marker that
4274
- then let it pass):
4415
+ then let it pass), and (since `7.26.35`) a string BUILT from one: a
4416
+ concatenation or a template holding one (`t("k") + ":"`, `` `${t("k")}:` ``),
4417
+ a method called on one (`t("k").toUpperCase()`, `[t("a"), name].join(" ")`);
4418
+ DOM built with one (`createElement2(["p", {}, t("k")])`) is DOM. No marker
4419
+ excuses such a text anywhere a key is read -- `t(/* i18n: data */ t("k") +
4420
+ "!")` and `{i18n: /* i18n: data */ t("k").trim()}` fail too, with *"hand the
4421
+ key, not t(key)"*:
4275
4422
 
4276
4423
  ```js
4277
4424
  yui_install_ask_once(shell, {t: t, message: t("install this app")}); // fails
@@ -4354,8 +4501,10 @@ it asks for or carries with the list.
4354
4501
  reading the page in another language:
4355
4502
 
4356
4503
  - a key handed to a translator, an `i18n` or a `data-i18n*` value by an
4357
- UNMARKED `let`, a call the reader does not follow (a recursion, one whose
4358
- returns mix keys and data, a function of a package) or a computed string:
4504
+ UNMARKED `let` given anything but literals (a property of a table, a call, a
4505
+ parameter, data, a `+=`: one only ever given literals is followed, above), a
4506
+ call the reader does not follow (a recursion, one whose returns mix keys and
4507
+ data, a function of a package) or a computed string:
4359
4508
  a translator is not a key function, and only the positions of
4360
4509
  `I18N_KEY_FUNCTIONS` are held to the per-call rule -- in the library and in
4361
4510
  an app alike. (A PARAMETER is not, in the library: the forwarder test puts
@@ -4363,7 +4512,18 @@ reading the page in another language:
4363
4512
  name it.);
4364
4513
  - a translator the reader does not know as one: a name other than `t` / `tr`
4365
4514
  that no const or import makes an alias of -- a parameter `translate`, a
4366
- `let` alias, `opts.translate(k)`, `i18next.getFixedT()`;
4515
+ `let` alias, `opts.translate(k)`, `i18next.getFixedT()` --, or one called
4516
+ through what is no name of it: `(0, t)(k)`, `[t][0](k)`,
4517
+ `Reflect.apply(t, x, [k])`, `Function.prototype.call.call(t, x, k)`;
4518
+ - a key BOUND with the translator, `t.bind(null, k)` (`.bind()` is read as an
4519
+ alias of the translator, and the key it binds is not read);
4520
+ - a translator handed to a FUNCTION (`{t: t}`, `yui_tabulator_lang(t)`,
4521
+ `run(t)`): only the iteration methods, `Array.from()` and `.then()` /
4522
+ `.catch()` are read as calling it with what they walk or settle with;
4523
+ - a translated text held in a marked `let` (`let label = t(k);` ...
4524
+ `/* i18n: data */ label`): the marker reads the literals of the `let`, not
4525
+ its translations -- only a translated text written right under the marker
4526
+ fails;
4367
4527
  - a key function reached through a parameter (`apply(fn)` calling
4368
4528
  `fn(shell, "k")`), a `let` given more than one function, a map in a `let`,
4369
4529
  a computed member (`API[name](...)`), or `.apply()` of an array the source
@@ -4378,13 +4538,20 @@ reading the page in another language:
4378
4538
  - what a module of a package exports (gobj-js, i18next), and an exported
4379
4539
  `let`;
4380
4540
  - in the library's forwarder search, a key stored in a heap object or a DOM
4381
- node and read back somewhere else (`$b._yui_label` was one), and what a
4382
- caller puts in a gobj's attrs (a window's `title` given at `gobj_create()`);
4541
+ node and read back somewhere else (`$b._yui_label` was one), a parameter a
4542
+ CLOSURE returns (`const inner = () => key; t(inner())`), and what a caller
4543
+ puts in a gobj's attrs (a window's `title` given at `gobj_create()`);
4544
+ - a published list mutated through a function it is handed to
4545
+ (`function add(list) { list.push(k); } add(X_I18N_KEYS)`), or by
4546
+ `Array.prototype.push.call(X_I18N_KEYS, k)`;
4383
4547
  - in an app, a key its callers hand a wrapper of its own reaches the locales
4384
4548
  only through a list the module publishes: the wrapper's hand-off must be
4385
4549
  marked (the rule per call), and nothing ties its callers' literals to the
4386
4550
  list -- nor a key built at run time (a template) to the list its marker
4387
- names, beyond the shape of the template.
4551
+ names, beyond the shape of the template (the app's own test does:
4552
+ gui_agent's `monitor_i18n_keys.test.js`); a marker over a parameter of such
4553
+ a wrapper (`title: /* i18n: L_I18N_KEYS */ title_key`) checks nothing at
4554
+ all, since what its callers hand it is not read.
4388
4555
 
4389
4556
  **What fails the build, besides a key no locale defines:**
4390
4557
 
@@ -4396,14 +4563,17 @@ reading the page in another language:
4396
4563
  `translation` dict;
4397
4564
  - a source file that cannot be parsed;
4398
4565
  - a published list no consumer can read (above), declared with `let` or
4399
- `var`, or mutated;
4566
+ `var`, or mutated (also through an alias or a namespace member);
4400
4567
  - a marker that is wrong (above): a `data` marker on what the reader follows
4401
4568
  to keys, or on what would hide a key (a call mixing keys and data, an array
4402
- of string literals), a list marker on a template whose shape no key of the
4403
- list has;
4569
+ of string literals), any marker on a translated text, a list marker on a
4570
+ template whose shape no key of the list has -- or whose every key of that
4571
+ shape a narrower marked template builds;
4404
4572
  - a key position of a key function, in the app's own code, given what the
4405
4573
  reader cannot follow and unmarked, or given a translated text (the rule per
4406
- call, above).
4574
+ call, above); and so a translator handed as a callback to `.map()`,
4575
+ `.forEach()`, ... over an array the reader cannot follow, or to `.then()` /
4576
+ `.catch()`, unmarked.
4407
4577
 
4408
4578
  Up to `7.26.27` the locale cases were "skipped" with an OK: one language less
4409
4579
  validated, and nobody told. A file of the locales directory with a locale's