@yuneta/gobj-ui 7.26.27 → 7.26.29
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/README.md +590 -25
- package/dist/gobj-ui.cjs.js +1080 -229
- package/dist/gobj-ui.es.js +1081 -230
- package/package.json +4 -2
- package/scripts/validate-locales.mjs +2656 -315
- package/src/c_g6_nodes_tree.js +1086 -253
- package/src/c_yui_form.js +16 -4
- package/src/c_yui_fsm_graph.js +5 -0
- package/src/c_yui_gclass.js +11 -0
- package/src/c_yui_gobj_tree_js.js +18 -1
- package/src/c_yui_json_graph.js +5 -0
- package/src/c_yui_nav.js +21 -16
- package/src/c_yui_pager.js +1 -1
- package/src/c_yui_period.js +10 -3
- package/src/c_yui_schema_editor.js +34 -18
- package/src/c_yui_shell.js +11 -10
- package/src/c_yui_treedb_graph.js +17 -9
- package/src/c_yui_treedb_schema.js +5 -0
- package/src/c_yui_treedb_topic_with_form.js +9 -9
- package/src/c_yui_treedb_topics.js +13 -13
- package/src/c_yui_window.js +2 -2
- package/src/c_yui_window_manager.js +5 -2
- package/src/c_yui_wizard.js +8 -1
- package/src/form_json_field.js +7 -0
- package/src/g6_canvas_tab_order.js +84 -0
- package/src/g6_canvas_tab_order.wiring.test.js +232 -0
- package/src/g6_nodes_tree_controls.test.js +547 -0
- package/src/g6_nodes_tree_focus.wiring.test.js +1360 -0
- package/src/g6_nodes_tree_i18n_keys.test.js +241 -5
- package/src/i18n_key_functions.js +235 -0
- package/src/i18n_key_functions.test.js +1525 -0
- package/src/lib_graph.js +38 -1
- package/src/nav_backbar_name.wiring.test.js +82 -0
- package/src/schema_flags.js +47 -0
- package/src/schema_import.js +7 -0
- package/src/shell_modals.js +20 -16
- package/src/shell_route_map.js +13 -3
- package/src/wizard_helpers.js +8 -0
- package/src/yui_dev.js +16 -3
- package/src/yui_dev_controls.test.js +174 -0
- package/src/yui_gclass_view.js +1 -1
- package/src/yui_graph_camera.js +1 -1
- package/src/yui_install.js +10 -8
- package/src/yui_table_select.js +2 -2
- package/src/yui_time.js +17 -2
package/README.md
CHANGED
|
@@ -1513,11 +1513,130 @@ clicked":
|
|
|
1513
1513
|
| **Delete** / **Backspace** | deletes the selection, after a confirmation that counts what it takes |
|
|
1514
1514
|
| **Esc**, or a click on the canvas | clears it |
|
|
1515
1515
|
|
|
1516
|
-
The keys reach the graph only while the **graph has focus** — G6
|
|
1517
|
-
|
|
1518
|
-
box a selection of the TEXT and not of
|
|
1519
|
-
|
|
1520
|
-
action decides, so a key is as visible in
|
|
1516
|
+
The keys reach the graph only while the **graph has focus** — G6 hears a key
|
|
1517
|
+
inside its own container, and its main canvas can take the focus — which is
|
|
1518
|
+
what keeps `ctrl+A` inside the find box a selection of the TEXT and not of
|
|
1519
|
+
every node: the focus is in the input, and the input is not inside the graph.
|
|
1520
|
+
They arrive as `EV_KEY_DOWN` and the action decides, so a key is as visible in
|
|
1521
|
+
the `machine` trace as a click.
|
|
1522
|
+
|
|
1523
|
+
**Tab goes from the canvas to the graph's own buttons** (since `7.26.28`). G6
|
|
1524
|
+
gives each of its four canvases `tabIndex = 1`, and a positive tabindex goes
|
|
1525
|
+
before every other control of the page: Tab from the graph went to the other
|
|
1526
|
+
three canvases, then to the top of the page and through every control there,
|
|
1527
|
+
and only then to the buttons beside the selection. Each time G6 writes that
|
|
1528
|
+
`1` — when it creates the canvases (`aftercanvasinit`) and after a renderer
|
|
1529
|
+
change (`afterrendererchange`) — the view gives the main canvas `0` and the
|
|
1530
|
+
other three `-1`. Tab now goes from the canvas to the toolbars and then to the
|
|
1531
|
+
round buttons, in DOM order. The same rule holds in every view of the library
|
|
1532
|
+
that draws a G6 graph (`C_G6_NODES_TREE`, `C_YUI_JSON_GRAPH`,
|
|
1533
|
+
`C_YUI_GOBJ_TREE_JS`, `C_YUI_FSM_GRAPH`, `C_YUI_TREEDB_SCHEMA`): each one calls
|
|
1534
|
+
the shared `keep_canvas_tab_order()` (`src/g6_canvas_tab_order.js`) right after
|
|
1535
|
+
`new Graph()`:
|
|
1536
|
+
|
|
1537
|
+
```js
|
|
1538
|
+
import {keep_canvas_tab_order} from "./g6_canvas_tab_order.js";
|
|
1539
|
+
|
|
1540
|
+
const graph = priv.graph = new Graph({container: priv.$container /* ... */});
|
|
1541
|
+
keep_canvas_tab_order(gobj, graph); // main canvas 0, the other layers -1
|
|
1542
|
+
```
|
|
1543
|
+
|
|
1544
|
+
The buttons of the graph are buttons for the keyboard too: the items of both
|
|
1545
|
+
toolbars, the round buttons beside a selection — the gear and the trash of a
|
|
1546
|
+
node or an edge, the link and the gear of a port — and the ✕ of a node's
|
|
1547
|
+
detail popover. Each one has `role="button"`, `tabindex="0"`, and a `title`
|
|
1548
|
+
and an `aria-label` that carry their key (`data-i18n-title`,
|
|
1549
|
+
`data-i18n-aria-label`). **Enter** or **Space** presses the focused one,
|
|
1550
|
+
exactly as a click does, and that key stops at the button: it is not the
|
|
1551
|
+
graph's. Any other key goes on, so **Esc** and **Delete** still clear or
|
|
1552
|
+
delete the selection while one of those buttons has the focus.
|
|
1553
|
+
|
|
1554
|
+
A toolbar item says its STATE as well as its name:
|
|
1555
|
+
|
|
1556
|
+
- **A disabled item** (save, undo and redo with nothing to do, zoom to
|
|
1557
|
+
selection with nothing selected) has `aria-disabled="true"` and is out of
|
|
1558
|
+
the Tab order (`tabindex="-1"`), from its first render and on every change.
|
|
1559
|
+
`disableElements()` / `enableElements()` (`lib_graph.js`) write the three
|
|
1560
|
+
together, and keep the tabindex the item takes back in
|
|
1561
|
+
`data-enabled-tabindex`. A disabled item that still has the focus takes the
|
|
1562
|
+
key and does nothing.
|
|
1563
|
+
- **A toggle** — the anchor, the selection mode — has `aria-pressed`, `true`
|
|
1564
|
+
only while it is ON, the same state its `pressed_state` look shows.
|
|
1565
|
+
- **A rebuild keeps the state** (since `7.26.28`). G6 rebuilds a toolbar
|
|
1566
|
+
whole — on a fold, a language change, full screen, any plugin added or
|
|
1567
|
+
removed — from its `getItems()`, read at that moment, and the new
|
|
1568
|
+
`innerHTML` lands later, over anything told to the old items. So each
|
|
1569
|
+
state is computed in `getItems()` as well as told to the live item. Before,
|
|
1570
|
+
zoom to selection came back disabled over a selection, and save, undo and
|
|
1571
|
+
redo came back disabled over history still pending.
|
|
1572
|
+
|
|
1573
|
+
```js
|
|
1574
|
+
// An item of a getItems() list, its state computed HERE: a rebuild
|
|
1575
|
+
// draws the item from this list.
|
|
1576
|
+
{id: 'g6-icon-save', value: 'save',
|
|
1577
|
+
className: 'EV_SAVE_GRAPH' + (lit.save ? ' color_submit_state' : ''),
|
|
1578
|
+
title: t('save'), title_key: 'save', disabled: !lit.save},
|
|
1579
|
+
{id: 'g6-icon-select-mode', value: 'selection-mode',
|
|
1580
|
+
className: 'EV_TOGGLE_SELECTION_MODE', pressed: priv.selection_mode,
|
|
1581
|
+
title: t('selection mode'), title_key: 'selection mode'},
|
|
1582
|
+
|
|
1583
|
+
// Between two rebuilds, on the live toolbar:
|
|
1584
|
+
enableElements($container, ".EV_SAVE_GRAPH"); // tabindex 0, no aria-disabled
|
|
1585
|
+
```
|
|
1586
|
+
|
|
1587
|
+
A press, from the pointer or from a key, does one thing: the button SENDS an
|
|
1588
|
+
input event to the view, with the identity of what it belongs to, and the
|
|
1589
|
+
action does the work. So every press is in the `machine` trace:
|
|
1590
|
+
|
|
1591
|
+
| button | event | kw |
|
|
1592
|
+
|---|---|---|
|
|
1593
|
+
| gear of a node | `EV_TOGGLE_NODE_POPOVER` | `{node_id}` |
|
|
1594
|
+
| trash of a node | `EV_REQUEST_DELETE_NODE` | `{node_id}` |
|
|
1595
|
+
| gear of an edge | `EV_TOGGLE_EDGE_POPOVER` | `{edge_id}` |
|
|
1596
|
+
| trash of an edge | `EV_REQUEST_UNLINK_EDGE` | `{edge_id}` |
|
|
1597
|
+
| gear of a port | `EV_TOGGLE_PORT_POPOVER` | `{node_id, port_key}` |
|
|
1598
|
+
| link of an fkey port (a click; a drag starts the link itself) | `EV_ENTER_LINKING_MODE` | `{node_id, port_key}` |
|
|
1599
|
+
| a press on that link, which may be a drag | `EV_START_LINK_DRAG` | `{node_id, port_key, client_x, client_y}` |
|
|
1600
|
+
| ...its release, and a pointer the browser took | `EV_LINK_DRAG_END` / `EV_LINK_DRAG_CANCEL` | `{client_x, client_y, dragged}` / `{}` |
|
|
1601
|
+
| ✕ of the detail popover | `EV_CLOSE_NODE_DETAIL` | `{node_id}` |
|
|
1602
|
+
| create of the edit toolbar | `EV_TOGGLE_CREATE_POPOVER` | `{}` |
|
|
1603
|
+
| cancel of the create popover (or Escape in its id) / create | `EV_CLOSE_CREATE_POPOVER` / `EV_SUBMIT_NEW_NODE` | `{}` / `{topic_name, id}` |
|
|
1604
|
+
| cancel / apply of a node's popover | `EV_CANCEL_NODE_POPOVER` / `EV_APPLY_NODE_POPOVER` | `{node_id}` / `{node_id, shape_before, fill, stroke, line_width, scope, shape}` |
|
|
1605
|
+
| cancel / apply of an edge's popover | `EV_CANCEL_EDGE_POPOVER` / `EV_APPLY_EDGE_POPOVER` | `{edge_id}` / `{edge_id, line_width, color, scope}` |
|
|
1606
|
+
| cancel / apply of a port's popover | `EV_CANCEL_PORT_POPOVER` / `EV_APPLY_PORT_POPOVER` | `{node_id, port_key}` / `{node_id, port_key, r_before, shape, r, scope}` |
|
|
1607
|
+
| cancel / confirm of a delete or an unlink | `EV_CANCEL_CONFIRM` / `EV_CONFIRMED` | `{what, id}` or `{what, ids}` |
|
|
1608
|
+
|
|
1609
|
+
A popover's apply carries what its form held when it was pressed, each value
|
|
1610
|
+
as the control gives it (a string: the action parses it), so the trace says
|
|
1611
|
+
what was applied, not only that something was:
|
|
1612
|
+
|
|
1613
|
+
```
|
|
1614
|
+
EV_APPLY_EDGE_POPOVER {"edge_id": "e1", "line_width": "3", "color": "#52c41a", "scope": "this"}
|
|
1615
|
+
```
|
|
1616
|
+
|
|
1617
|
+
The action refuses, with an error, an identity that is not the current
|
|
1618
|
+
selection (or, for the ✕, the node the popover shows; for a popover's
|
|
1619
|
+
buttons, a popover that is not open; for a cancel of a confirmation, a
|
|
1620
|
+
question that is not the one open). The buttons go away with their selection,
|
|
1621
|
+
so a mismatch is a defect, and to act on it would edit or delete something the
|
|
1622
|
+
reader does not look at. An empty id in the create popover is not a defect:
|
|
1623
|
+
the action says *node id required* in the popover, and puts the focus back in
|
|
1624
|
+
the id.
|
|
1625
|
+
|
|
1626
|
+
Only the MOVES of a link drag are not events: they draw the line and the
|
|
1627
|
+
cursor, which is the gesture's own picture, and one event per move would bury
|
|
1628
|
+
the trace.
|
|
1629
|
+
|
|
1630
|
+
**The keyboard stays with the graph.** A pointer press on one of these buttons
|
|
1631
|
+
does not take the focus: it cancels the `pointerdown`, the click still comes,
|
|
1632
|
+
and the focus stays on the canvas. And before the view removes an element that
|
|
1633
|
+
has the focus — a round button that Escape deselects, a popover that closes
|
|
1634
|
+
with its ✕ or a field in it, a toolbar it rebuilds or takes away — it gives
|
|
1635
|
+
the focus back: after a rebuild to the item of the same CONTROL (its
|
|
1636
|
+
`data-control`: Enter on *enter full screen* lands on *exit full screen*, the
|
|
1637
|
+
same control in its other state), in every other case to the main canvas. Firefox fires no `focusout` when the
|
|
1638
|
+
focused element is removed, so without this the focus fell to `<body>` and the
|
|
1639
|
+
graph heard no key until the next click on it.
|
|
1521
1640
|
|
|
1522
1641
|
Three decisions are worth knowing, because each one is where this could have
|
|
1523
1642
|
gone wrong:
|
|
@@ -2837,8 +2956,9 @@ Consumer i18n keys: `json viewer`, `paste json here`, `invalid json`,
|
|
|
2837
2956
|
`collapsed in the source`, `first json`, `second json`, `compare`, `added`,
|
|
2838
2957
|
`removed`, `changed`, `path`, `no differences`, `compare needs two json`,
|
|
2839
2958
|
`cannot compare`, `json not kept` (plus `view` and `clear`, which every app
|
|
2840
|
-
has). `compare`, `view` and `clear` reach `t()`
|
|
2841
|
-
`
|
|
2959
|
+
has). `compare`, `view` and `clear` reach `t()` through the pad's own button
|
|
2960
|
+
helper, which `I18N_KEY_FUNCTIONS` names, so a consumer's `validate-locales`
|
|
2961
|
+
asks for them (since `7.26.28`; it did not see them before).
|
|
2842
2962
|
|
|
2843
2963
|
### Frontend view — `setup_frontend_view`
|
|
2844
2964
|
|
|
@@ -3356,9 +3476,10 @@ text is in the yunetas `CLAUDE.md` ("JS GUI conventions"); in short:
|
|
|
3356
3476
|
1. Run the static check on the app's sources, from the published package
|
|
3357
3477
|
(7.26.11+ ships the script):
|
|
3358
3478
|
`node node_modules/@yuneta/gobj-ui/scripts/check-markup-strings.mjs src`
|
|
3359
|
-
— usually from the app's `prebuild`. It imports `acorn`,
|
|
3360
|
-
|
|
3361
|
-
|
|
3479
|
+
— usually from the app's `prebuild`. It imports `acorn`, a dependency
|
|
3480
|
+
of this package since `7.26.29`, so npm installs it with the package
|
|
3481
|
+
(up to `7.26.28` it was a devDependency here, and the APP had to
|
|
3482
|
+
declare it). It lists a markup string literal in a description
|
|
3362
3483
|
(`literal`, wrap it in `html_markup()`), markup built with values
|
|
3363
3484
|
(`built`: a template, `String.raw`, a `+` chain, `.concat()`, a
|
|
3364
3485
|
`[...].join()`, or a string method such as `.trim()` over one of those,
|
|
@@ -3505,6 +3626,31 @@ A `<label>` that **wraps** its control is the one shape that needs no
|
|
|
3505
3626
|
`aria-label` next to a visible `i18n` label is worse than none: it OVERRIDES
|
|
3506
3627
|
the translated text for a reader.
|
|
3507
3628
|
|
|
3629
|
+
A control drawn as something other than a `<button>` — a `div` holding an
|
|
3630
|
+
icon or a glyph — is a button for the KEYBOARD too: `role="button"`,
|
|
3631
|
+
`tabindex="0"`, and Enter or Space **click** it, so the key runs the very
|
|
3632
|
+
handler the pointer does:
|
|
3633
|
+
|
|
3634
|
+
```js
|
|
3635
|
+
el.setAttribute('role', 'button');
|
|
3636
|
+
el.setAttribute('tabindex', '0');
|
|
3637
|
+
el.addEventListener('keydown', (e) => {
|
|
3638
|
+
if(e.key === 'Enter' || e.key === ' ') {
|
|
3639
|
+
e.preventDefault(); // Space would scroll the page
|
|
3640
|
+
e.stopPropagation(); // a key that pressed a button is not the host's
|
|
3641
|
+
el.click();
|
|
3642
|
+
}
|
|
3643
|
+
});
|
|
3644
|
+
```
|
|
3645
|
+
|
|
3646
|
+
`C_G6_NODES_TREE` builds its floating buttons and the ✕ of its detail popover
|
|
3647
|
+
this way, and the items of its G6 toolbars too (the listener is on the
|
|
3648
|
+
toolbar element, because G6 rebuilds the items). The `stopPropagation`
|
|
3649
|
+
matters there: the graph's container forwards every keydown to G6, so an
|
|
3650
|
+
Enter that pressed a button would also reach the graph's shortcuts and
|
|
3651
|
+
`EV_KEY_DOWN`. What else a button needs inside a graph is in
|
|
3652
|
+
[Selecting several nodes](#selecting-several-nodes-and-moving-them-together).
|
|
3653
|
+
|
|
3508
3654
|
Two things carry no attribute anyone can set, and are named after the render
|
|
3509
3655
|
instead — and again on every rebuild, because a name written onto a widget's
|
|
3510
3656
|
DOM is a race with the next one:
|
|
@@ -3522,8 +3668,9 @@ DOM is a race with the next one:
|
|
|
3522
3668
|
DEPLOYED DOM, resolve each control's name the way a reader does (`aria-label`
|
|
3523
3669
|
→ `label[for]` → a wrapping `<label>` → the text → `title` → `placeholder`),
|
|
3524
3670
|
then switch language and diff. A key that arrives as a VARIABLE — from a data
|
|
3525
|
-
table, a
|
|
3526
|
-
`validate-locales` and shows up only
|
|
3671
|
+
table, a local alias of `t()`, a wrapper of the app's own — is invisible to
|
|
3672
|
+
`validate-locales` unless its module publishes it (below), and shows up only
|
|
3673
|
+
there.
|
|
3527
3674
|
|
|
3528
3675
|
### i18n: a string must be able to CHANGE language, not just be translated once
|
|
3529
3676
|
|
|
@@ -3628,34 +3775,449 @@ carry the categories of ITS language (`Intl.PluralRules`: English `one`,
|
|
|
3628
3775
|
no suffix serves `other` and `many`; `zero`, `one`, `two` and `few` need their
|
|
3629
3776
|
own form. That is a policy of GRAMMAR, not a need of i18next, which falls back
|
|
3630
3777
|
to the bare key for any form that is missing: a Russian count of 3 rendered
|
|
3631
|
-
with the bare key reads wrong, and the check says so. A
|
|
3632
|
-
|
|
3633
|
-
|
|
3778
|
+
with the bare key reads wrong, and the check says so. A group is a plural only
|
|
3779
|
+
when some locale carries its bare base (`songs`) or its `_other` form — every
|
|
3780
|
+
language has `other`, so a real plural always has one of the two. Keys that
|
|
3781
|
+
only END like forms are plain keys: `pick_one` alone, or `step_one` /
|
|
3782
|
+
`step_two` / `step_three`, an enumeration (up to `7.26.27` two forms were
|
|
3783
|
+
enough to make a plural, and that enumeration gave seven violations). A form
|
|
3784
|
+
asked for by its own name — `t("pick_one")` beside a bare `pick` — is a key
|
|
3785
|
+
like any other. Ordinals (`place_ordinal_one`) are checked with the ordinal
|
|
3786
|
+
categories of each language; the `_ordinal_` infix says they are forms. A value of the shell
|
|
3634
3787
|
declaration is a literal when it is the endonym of one of the app's locales
|
|
3635
3788
|
(the `name` its catalog exports: `"中文"`, `"español"`); any other lower-case
|
|
3636
3789
|
text is a key, `"configuración"` included.
|
|
3637
3790
|
|
|
3638
|
-
**
|
|
3791
|
+
**The source is PARSED, never run** (since `7.26.28`, with `acorn`, a
|
|
3792
|
+
dependency of this package since `7.26.29`: an app need not declare it). A
|
|
3793
|
+
LOCALE is not a source: it is parsed, and then imported, so its top-level code
|
|
3794
|
+
runs -- the app's own code, but code. Comments are not code. A key is read
|
|
3795
|
+
where it is WRITTEN -- given to `t()`, as an `i18n` or `data-i18n*` value of
|
|
3796
|
+
an object literal, by `setAttribute()`, or (since `7.26.29`) written as a
|
|
3797
|
+
member, `attrs.i18n = "save"`, `attrs["data-i18n-title"] = "save"` -- and
|
|
3798
|
+
FOLLOWED where the source writes it out:
|
|
3799
|
+
|
|
3800
|
+
- a string literal, a template with no `${}`, and every branch of `?:`, `||`
|
|
3801
|
+
and `??` (the right one of `&&`), so
|
|
3802
|
+
`"data-i18n": pending ? "loading" : "click to load"` asks for both, and
|
|
3803
|
+
`comment || "print-tranger failed"` for its fallback;
|
|
3804
|
+
- a `const`, to its value, in the scope that declares it (a parameter or a
|
|
3805
|
+
`let` of the same name hides it);
|
|
3806
|
+
- a property of a const object literal: `DEFAULT_LABEL.ok`, and for a
|
|
3807
|
+
computed one, `NOTES[verdict]`, every value the object holds;
|
|
3808
|
+
- the element of a const array literal that a `.map()`, `.forEach()`, ...
|
|
3809
|
+
callback or a `for...of` walks, and what a destructuring reads from it
|
|
3810
|
+
(`for(const {key} of ROWS)`);
|
|
3811
|
+
- `String(x)`, and a template that is only `${x}`, as `x` (since `7.26.29`);
|
|
3812
|
+
- the default of a parameter, `function f(msg = "fallback")`, besides the
|
|
3813
|
+
parameter (since `7.26.29`);
|
|
3814
|
+
- a call of a function of the same module whose every `return` is one of
|
|
3815
|
+
these (since `7.26.29`). One that returns anything else -- a parameter, a
|
|
3816
|
+
variable, a concatenation -- is not followed.
|
|
3817
|
+
|
|
3818
|
+
```js
|
|
3819
|
+
const KEY = "save";
|
|
3820
|
+
t(KEY); // asks for "save"
|
|
3821
|
+
const NOTES = {found: "values found", none: "no values"};
|
|
3822
|
+
t(NOTES[verdict]); // asks for both
|
|
3823
|
+
for(const zone of ZONES) {
|
|
3824
|
+
build_zone(gobj, zone); // ZONES[].key, at build_zone's key position
|
|
3825
|
+
}
|
|
3826
|
+
SHAPES.map((s) => ({value: s, i18n: s})); // every element of SHAPES
|
|
3827
|
+
function pick(ok) {
|
|
3828
|
+
return ok? "accepted" : "refused";
|
|
3829
|
+
}
|
|
3830
|
+
t(pick(answer)); // asks for both
|
|
3831
|
+
```
|
|
3832
|
+
|
|
3833
|
+
What it cannot follow cannot be known: a `let`, a parameter, an import, a
|
|
3834
|
+
call, a concatenation, data from a backend. A key that arrives that way is
|
|
3835
|
+
PUBLISHED by its module (below), and the place it is read is MARKED; a marked
|
|
3836
|
+
`let` -- or a marked member or element of one -- is the one exception to "not
|
|
3837
|
+
followed" (below). A source file that cannot be parsed fails the build, because
|
|
3838
|
+
its keys would go unchecked.
|
|
3839
|
+
|
|
3840
|
+
**A key handed to a FUNCTION is read where it is handed.**
|
|
3841
|
+
`yui_shell_show_error(shell, "some records were gone before the delete")` asks
|
|
3842
|
+
`t()` for its key INSIDE the function, where no scan of `t("...")` sees it, and
|
|
3843
|
+
up to `7.26.27` such keys shipped raw with the validator green (`expand
|
|
3844
|
+
loaded`, `click to load`, `please select some row`, `accept`, ...).
|
|
3845
|
+
`I18N_KEY_FUNCTIONS` (`src/i18n_key_functions.js`) names every gobj-ui function
|
|
3846
|
+
that receives a key, and where it is:
|
|
3847
|
+
|
|
3848
|
+
```js
|
|
3849
|
+
{module: "shell_modals.js", name: "yui_shell_confirm_danger", exported: true,
|
|
3850
|
+
keys: ["1", "2.title", "2.confirm_label", "2.cancel_label"]},
|
|
3851
|
+
{module: "yui_table_select.js", name: "yui_selection_bar", exported: true,
|
|
3852
|
+
keys: ["1.actions[].label"]},
|
|
3853
|
+
{module: "c_yui_json.js", name: "icon_button", exported: false, keys: ["3"]},
|
|
3854
|
+
```
|
|
3855
|
+
|
|
3856
|
+
A position is an argument (`"1"`), a property of the object given there
|
|
3857
|
+
(`"2.title"`, also through `Object.assign()` or a spread of a const object),
|
|
3858
|
+
or a property of each object of an array (`"1.actions[].label"`). What is
|
|
3859
|
+
given at those positions is read like any other key (literals, consts,
|
|
3860
|
+
branches): in the app's own code, on a call bound to an import of that
|
|
3861
|
+
function from gobj-ui (an app function of the same name is not mistaken for
|
|
3862
|
+
it), and in every module the app mounts, where an entry with
|
|
3863
|
+
`exported: false` is a helper of its own module. A position may also hold
|
|
3864
|
+
DOM, which is no key (since `7.26.29`): an array (a `createElement2` spec), a
|
|
3865
|
+
call of `createElement2()`, `createOneHtml()` or `html_markup()` imported
|
|
3866
|
+
from gobj-js, of `text_node()` from gobj-ui, of `document.createElement()`
|
|
3867
|
+
and the like, a `const` bound to one, or a plain `let` that is only ever
|
|
3868
|
+
given one. DOM is known by where it comes from, never by the name of a
|
|
3869
|
+
variable: `$box` says nothing. The exported ones an app calls:
|
|
3870
|
+
`yui_shell_show_info` / `_warning` / `_error` (the message),
|
|
3871
|
+
`yui_shell_show_modal` (a string content, `title`),
|
|
3872
|
+
`yui_shell_confirm_ok` / `_yesno` / `_danger` / `_yesnocancel` (the message,
|
|
3873
|
+
`title`, the `*_label`s), `yui_install_ask_once` (`message`, `yes_label`,
|
|
3874
|
+
`no_label`), `yui_selection_bar` (each action's `label`), `yui_asset_missing`
|
|
3875
|
+
/ `yui_asset_element` (`key`), `hook_cell_spec` (the title key),
|
|
3876
|
+
`period_name` (a granularity id), `card_descriptor` / `cards_grid_descriptor`
|
|
3877
|
+
(a nav item's `name`, `tooltip`, `aria_label`) and `open_gclass_view` (the
|
|
3878
|
+
gclass name, which titles it).
|
|
3879
|
+
|
|
3880
|
+
**Every function that forwards a key is in the table, and a test finds them**
|
|
3881
|
+
(since `7.26.29`: up to `7.26.28` the table lacked 39 of them, and 6 entries
|
|
3882
|
+
lacked positions, so a literal handed to one went unasked -- REV-CLOUD
|
|
3883
|
+
V21.4.1). `src/i18n_key_functions.test.js` reads every module and finds each
|
|
3884
|
+
function that hands one of its PARAMETERS on to a place a key is read --
|
|
3885
|
+
`t()`, an `i18n`, `code` or `data-i18n*` value, a key position of a function
|
|
3886
|
+
of the table -- the parameter itself or what is read from it (`opts.title`, a
|
|
3887
|
+
destructured `{label}`, each `items[].name`), directly or through a `let` it is
|
|
3888
|
+
given to. The search runs to a fixpoint: a function that hands its parameter
|
|
3889
|
+
to one it found is one too.
|
|
3890
|
+
|
|
3891
|
+
- A function called by NAME -- in its module, or exported -- must be in the
|
|
3892
|
+
table with that position, or the test fails, naming the entry to add:
|
|
3893
|
+
|
|
3894
|
+
```
|
|
3895
|
+
c_yui_nav.js:684 item_li() hands [1.name, 1.tooltip, 1.aria_label] to a place a key is read:
|
|
3896
|
+
name it in I18N_KEY_FUNCTIONS -- {module: "c_yui_nav.js", name: "item_li", exported: false, ...}
|
|
3897
|
+
```
|
|
3898
|
+
|
|
3899
|
+
- One that nothing calls by name -- an FSM action (the framework calls it
|
|
3900
|
+
with an event's kw), a callback, a method -- has no call a reader can
|
|
3901
|
+
read, so listing it would read nothing: what it hands on is MARKED where it
|
|
3902
|
+
hands it, or the test fails. `C_YUI_WINDOW_MANAGER`'s `ac_register_window`
|
|
3903
|
+
writes `i18n: /* i18n: data */ title_key`: the window's title is a key its
|
|
3904
|
+
host chose.
|
|
3905
|
+
- What a gobj keeps in its `priv` is its own state, not what a caller hands:
|
|
3906
|
+
a path through the `priv` of a parameter is no position.
|
|
3907
|
+
|
|
3908
|
+
Naming a function moves the marker to where the value ENTERS: its callers give
|
|
3909
|
+
it a literal, a const, a position of their own, or a marker. The nav's items
|
|
3910
|
+
and the shell's toolbar are marked once, where the library reads its config --
|
|
3911
|
+
`render_vertical(gobj, /* i18n: data */ items)`, the app's nav declaration,
|
|
3912
|
+
whose keys `validate-locales` reads in its `app_config.json`.
|
|
3913
|
+
|
|
3914
|
+
**A key the reader cannot follow is PUBLISHED by its module** (since
|
|
3639
3915
|
`7.26.27`): an exported `<NAME>_I18N_KEYS` array of string literals, which
|
|
3640
3916
|
`validate-locales` reads in every module the app mounts and asks the locales
|
|
3641
3917
|
for. `yui_dev.js` publishes the 51 keys of the Developer window
|
|
3642
3918
|
(`YUI_DEV_I18N_KEYS`) -- they used to be a comment each consumer copied, and
|
|
3643
|
-
one consumer lacked 35 --, `c_g6_nodes_tree.js` those of its shape tables
|
|
3644
|
-
popover labels
|
|
3645
|
-
|
|
3919
|
+
one consumer lacked 35 --, `c_g6_nodes_tree.js` those of its shape tables,
|
|
3920
|
+
popover labels, floating buttons and error messages
|
|
3921
|
+
(`G6_NODES_TREE_I18N_KEYS`). Since `7.26.28` more modules publish one:
|
|
3922
|
+
`SCHEMA_FLAGS_I18N_KEYS` (the group and the description of every flag),
|
|
3923
|
+
`YUI_TIME_I18N_KEYS` (every granularity and rolling window of the catalog:
|
|
3924
|
+
which of them a period control offers is the HOST's data),
|
|
3925
|
+
`C_YUI_FORM_I18N_KEYS` (the message of an empty required field, and the
|
|
3926
|
+
warning a form hands its host when it vetoes a close),
|
|
3927
|
+
`C_YUI_SCHEMA_EDITOR_I18N_KEYS` (the severity of a finding, which the checks of
|
|
3928
|
+
`schema_validate.js` write), `SHELL_ROUTE_MAP_I18N_KEYS` (the labels of the
|
|
3929
|
+
route map's own nodes), and so on: `grep -n _I18N_KEYS src/*.js`.
|
|
3930
|
+
|
|
3931
|
+
A list is for what the reader CANNOT follow. A key it can follow needs no list:
|
|
3932
|
+
write it as a literal or a `const` where it is read, and the reader sees it.
|
|
3933
|
+
`const key = maximized ? "restore" : "maximize"; t(key);` asks for both keys,
|
|
3934
|
+
and so do the pager's ✕, the camera's anchor button, the nav's landmark, the
|
|
3935
|
+
selection bar's clear button and the treedb form's title, which are written
|
|
3936
|
+
that way. The same code with a `let` asks for neither.
|
|
3646
3937
|
|
|
3647
3938
|
```js
|
|
3648
3939
|
export const MY_VIEW_I18N_KEYS = ['shape', 'radius', 'apply to'];
|
|
3649
3940
|
```
|
|
3650
3941
|
|
|
3942
|
+
The list is read as DATA, never run: `export const` (or `let`, `var`),
|
|
3943
|
+
`const X = [...]; export {X}` (or `export {x as X_I18N_KEYS}`), wrapped in
|
|
3944
|
+
`Object.freeze()` or not, each element a string literal, a template with no
|
|
3945
|
+
`${}`, or (since `7.26.29`) a SPREAD of another array of the same module read
|
|
3946
|
+
the same way -- a top-level `const`, or a list the module publishes:
|
|
3947
|
+
|
|
3948
|
+
```js
|
|
3949
|
+
const COMMON_KEYS = ['shape', 'radius'];
|
|
3950
|
+
export const MY_VIEW_I18N_KEYS = [...COMMON_KEYS, 'apply to'];
|
|
3951
|
+
export const MY_EDITOR_I18N_KEYS = [...MY_VIEW_I18N_KEYS, 'discard'];
|
|
3952
|
+
```
|
|
3953
|
+
|
|
3954
|
+
Anything named `*_I18N_KEYS` (the name in any letter case) that no consumer
|
|
3955
|
+
can read **fails the build**, naming the file and the list, in the app's own
|
|
3956
|
+
code too: an export that is not such an array (an element that is a variable,
|
|
3957
|
+
a call, a template with `${}`, a spread of an import or of a `let`), a list
|
|
3958
|
+
that is not exported, one declared inside a function or a block, one declared
|
|
3959
|
+
by destructuring (`export const {X_I18N_KEYS} = tables`), one exported under a
|
|
3960
|
+
name that does not end in `_I18N_KEYS`. Up to `7.26.27` the regex that read
|
|
3961
|
+
the lists dropped such a list in silence, and a key holding `]` dropped the
|
|
3962
|
+
whole array; up to `7.26.28` a spread of a const of the module was refused
|
|
3963
|
+
too.
|
|
3964
|
+
|
|
3965
|
+
**A MARKER says where the keys of a place come from.** It is a comment that
|
|
3966
|
+
stands right before the node it marks (only whitespace between): an argument,
|
|
3967
|
+
a branch of one, a property value, or the whole call. Two kinds:
|
|
3968
|
+
|
|
3969
|
+
```js
|
|
3970
|
+
yui_shell_show_error(shell, /* i18n: data */ comment || "print-tranger failed", {t: t});
|
|
3971
|
+
let name = mode_name(/* i18n: C_YUI_PERIOD_I18N_KEYS, YUI_TIME_I18N_KEYS */ mode);
|
|
3972
|
+
confirm_then(gobj, /* i18n: C_YUI_FORM_I18N_KEYS */ kw.warning || "all changes will be lost", ...);
|
|
3973
|
+
```
|
|
3974
|
+
|
|
3975
|
+
- `i18n: data` -- what stands there is DATA, no key a list can hold: a
|
|
3976
|
+
backend's comment, a topic or column name, a gclass name, a host's own key
|
|
3977
|
+
in its config, a DOM node. The literals inside it (`"print-tranger failed"`
|
|
3978
|
+
above) are still read.
|
|
3979
|
+
- `i18n: <NAME>_I18N_KEYS` (several by commas) -- the keys come from those
|
|
3980
|
+
published lists. The keys of the marked expression that the reader CAN
|
|
3981
|
+
follow must be in them: a marker is a claim, and it is checked.
|
|
3982
|
+
|
|
3983
|
+
**A `data` marker is a claim too** (since `7.26.29`). On what the reader
|
|
3984
|
+
follows to keys and nothing else -- a literal, a template with no `${}`, a
|
|
3985
|
+
`const` bound to one, a `?:` / `||` / `??` branch that is one, a call of a
|
|
3986
|
+
function of the module whose returns are literals -- it claims data where the
|
|
3987
|
+
reader reads a key, and it fails. Up to `7.26.28` that is how the key behind
|
|
3988
|
+
a call went unread: the reader stopped at the call, and the marker excused it.
|
|
3989
|
+
|
|
3990
|
+
```js
|
|
3991
|
+
function pick() {
|
|
3992
|
+
return "accepted";
|
|
3993
|
+
}
|
|
3994
|
+
yui_shell_show_info(shell, /* i18n: data */ pick());
|
|
3995
|
+
// main.js:4: marked as data, but what it marks is the key "accepted", which the
|
|
3996
|
+
// reader follows: drop the marker (it is no data)
|
|
3997
|
+
```
|
|
3998
|
+
|
|
3999
|
+
`/* i18n: data */ comment || "print-tranger failed"` stays legal: `comment` is
|
|
4000
|
+
data, and the fallback is read anyway.
|
|
4001
|
+
|
|
4002
|
+
**A marked `let` (or `var`) is held to its marker.** A marker that names a list
|
|
4003
|
+
used to let a `let` pass unread, whatever it held: a new literal branch on it
|
|
4004
|
+
was neither asked for nor checked (REV-CLOUD V20). Now, when what a marker
|
|
4005
|
+
marks is a plain `let` / `var`, the reader collects everything the variable is
|
|
4006
|
+
GIVEN, in the scope that declares it -- its initializer and every `=` (`||=`,
|
|
4007
|
+
`??=`, `&&=` too) -- and reads each value like any other. Each literal it finds
|
|
4008
|
+
is a key the locales are asked for and, under a list marker, must be in the
|
|
4009
|
+
lists it names. A write it cannot read (`+=`, `++`, a destructuring, a
|
|
4010
|
+
`for...of` over the variable) keeps the variable unknown; a parameter or
|
|
4011
|
+
another variable of the same name is not a write to it. And a variable that is
|
|
4012
|
+
given nothing but literals is a const that was not written as one, so it
|
|
4013
|
+
fails:
|
|
4014
|
+
|
|
4015
|
+
```js
|
|
4016
|
+
let key = maximized ? "restore" : "maximize";
|
|
4017
|
+
t(/* i18n: C_YUI_WINDOW_I18N_KEYS */ key);
|
|
4018
|
+
// c_yui_window.js:920: `key` is a let (line 918) that only ever holds literals
|
|
4019
|
+
// ("restore", "maximize"): make it a const, which the reader follows, and drop the marker
|
|
4020
|
+
```
|
|
4021
|
+
|
|
4022
|
+
The rule is the same under `i18n: data`: the literals of a marked variable are
|
|
4023
|
+
read, and one that holds nothing else fails.
|
|
4024
|
+
|
|
4025
|
+
**A `let` that holds a TABLE is held the same way** (since the same round:
|
|
4026
|
+
up to then a marked member of one passed unread). When what is marked is read
|
|
4027
|
+
FROM a `let` -- a property of it (`LABELS[k]`, `opts.title` at a key
|
|
4028
|
+
position), the element of a `.map()` / `.forEach()` / `for...of` over it, or
|
|
4029
|
+
another `let` given one of those (`let picked = LIST[0]`) -- the reader follows
|
|
4030
|
+
that `let` too: everything it is given, and along the same steps (a property,
|
|
4031
|
+
an element) inside each value. For such a member, what is WRITTEN into the
|
|
4032
|
+
table counts as given: `x.a = v`, `x[k] = v`, `x.push(v)` (also `unshift`,
|
|
4033
|
+
`splice`, `fill`, a spread), `Object.assign(x, v)`. A `+=` or `++` on that
|
|
4034
|
+
member keeps it unknown.
|
|
4035
|
+
|
|
4036
|
+
```js
|
|
4037
|
+
let LABELS = {ok: "accept", no: "refuse"};
|
|
4038
|
+
t(/* i18n: MY_VIEW_I18N_KEYS */ LABELS[answer]);
|
|
4039
|
+
|
|
4040
|
+
let LIST = ["accept"];
|
|
4041
|
+
let picked = LIST[0];
|
|
4042
|
+
t(/* i18n: MY_VIEW_I18N_KEYS */ picked);
|
|
4043
|
+
|
|
4044
|
+
let EXTRA = [];
|
|
4045
|
+
EXTRA.push("accept");
|
|
4046
|
+
EXTRA.forEach((x) => t(/* i18n: MY_VIEW_I18N_KEYS */ x));
|
|
4047
|
+
```
|
|
4048
|
+
|
|
4049
|
+
```
|
|
4050
|
+
main.js:2: `LABELS` is a let (line 1) that only ever holds literals ("accept", "refuse"):
|
|
4051
|
+
make it a const, which the reader follows, and drop the marker
|
|
4052
|
+
main.js:6: `picked` is a let (line 5) that only ever holds literals ("accept"),
|
|
4053
|
+
through the let `LIST` (line 4): make them consts, which the reader follows, and drop the marker
|
|
4054
|
+
```
|
|
4055
|
+
|
|
4056
|
+
Line 10 is no violation: "accept" is asked for, and must be in
|
|
4057
|
+
`MY_VIEW_I18N_KEYS`. It does not fail with *"make it a const"*, because a const
|
|
4058
|
+
would not carry what is pushed into it to the reader.
|
|
4059
|
+
|
|
4060
|
+
`validate-locales` checks every marker in what it scans, in an app's code too:
|
|
4061
|
+
a comment that starts with `i18n:` and is no marker, a marker that stands
|
|
4062
|
+
before no place a key is read (a key position, `t()`, an `i18n` or
|
|
4063
|
+
`data-i18n*` value), a marker that names a list no module the app mounts
|
|
4064
|
+
publishes, a marked key that is not in the lists it names, a marked variable
|
|
4065
|
+
that only ever holds literals -- each one fails, with the file and the line.
|
|
4066
|
+
|
|
4067
|
+
**The rule is per CALL, in the library and in the app.**
|
|
4068
|
+
`src/i18n_key_functions.test.js` reads every module of the library with the
|
|
4069
|
+
validator's own reader, and `validate-locales` reads the app's own code the
|
|
4070
|
+
same way (since `7.26.29`; up to `7.26.28` only the library was held to it, and
|
|
4071
|
+
a `let`, a wrapper or a concatenation in an app passed unread). Every value a
|
|
4072
|
+
key position of a key function can take must be:
|
|
4073
|
+
|
|
4074
|
+
1. a string literal, a `?:` / `||` / `??` of them, nothing, or DOM;
|
|
4075
|
+
2. a const the reader follows to one of those, or a plain `let` that is only
|
|
4076
|
+
ever given DOM;
|
|
4077
|
+
3. a key position of the enclosing table function, handed on (`message`
|
|
4078
|
+
where `message` is a key parameter of the function the call is in) -- in
|
|
4079
|
+
the library only: a function of an app is in no table; or
|
|
4080
|
+
4. MARKED, `/* i18n: data */` or `/* i18n: <NAME>_I18N_KEYS */`.
|
|
4081
|
+
|
|
4082
|
+
Anything else fails, naming the file, the line and the call:
|
|
4083
|
+
|
|
4084
|
+
```
|
|
4085
|
+
c_yui_treedb_topics.js:860 (call at 859) card_action_anchor() [2]: table_key
|
|
4086
|
+
src/main.js:12: yui_shell_confirm_yesno() [2.title] is given opts, which the reader cannot follow:
|
|
4087
|
+
hand it a literal or a const, or mark it /* i18n: data */ or /* i18n: <NAME>_I18N_KEYS */
|
|
4088
|
+
```
|
|
4089
|
+
|
|
4090
|
+
A TRANSLATED text handed to a key position fails even when it is marked: the
|
|
4091
|
+
function translates it again and marks it `data-i18n` with the translation,
|
|
4092
|
+
so it never changes language. Hand it the key:
|
|
4093
|
+
|
|
4094
|
+
```js
|
|
4095
|
+
yui_install_ask_once(shell, {t: t, message: t("install this app")}); // fails
|
|
4096
|
+
yui_install_ask_once(shell, {t: t, message: "install this app"}); // the key
|
|
4097
|
+
```
|
|
4098
|
+
|
|
4099
|
+
```
|
|
4100
|
+
src/c_app.js:734 (call at line 732): yui_install_ask_once() [1.message] is given t("install this app"):
|
|
4101
|
+
a TRANSLATED text where a KEY goes (translated again and marked data-i18n with the translation,
|
|
4102
|
+
it never changes language): hand it the key
|
|
4103
|
+
```
|
|
4104
|
+
|
|
4105
|
+
In an app, what a wrapper of its own hands on is marked where it hands it:
|
|
4106
|
+
`/* i18n: data */` when it is data (a backend's comment, a key built from a
|
|
4107
|
+
name, the DOM its callers built). When its callers give it the module's own
|
|
4108
|
+
literal keys, the module PUBLISHES them and the marker names the list --
|
|
4109
|
+
wattyzer's `open_route_modal()` hands on the titles of `C_WZ_APP_I18N_KEYS`:
|
|
4110
|
+
|
|
4111
|
+
```js
|
|
4112
|
+
export const C_WZ_APP_I18N_KEYS = ["backend connection", "about wattyzer", "preferences"];
|
|
4113
|
+
...
|
|
4114
|
+
let handle = yui_shell_show_modal(priv.shell, /* i18n: data */ $content,
|
|
4115
|
+
/* i18n: C_WZ_APP_I18N_KEYS */ modal_opts);
|
|
4116
|
+
```
|
|
4117
|
+
|
|
4118
|
+
Publishing a list does not exempt a module: the rule is per call, not per
|
|
4119
|
+
module. The same test fails when a marker is not one
|
|
4120
|
+
or stands nowhere; when a marker names a list that is not published by its
|
|
4121
|
+
module or by one it imports (a consumer would not read it); when a marked key
|
|
4122
|
+
the reader can follow is not in the list; when a marked variable only ever
|
|
4123
|
+
holds literals; when a list is not readable, holds a key that is not
|
|
4124
|
+
lower-case ASCII, holds one twice, or holds one written nowhere else in its
|
|
4125
|
+
module (stale); when an entry of the table names a function its module does
|
|
4126
|
+
not declare.
|
|
4127
|
+
|
|
4128
|
+
**Every key of every list is PINNED**: dropping it from its list fails a test,
|
|
4129
|
+
or loses nothing. For each key, one of these holds, or the test fails with
|
|
4130
|
+
*"reaches a consumer only through this list, and dropping it from the list
|
|
4131
|
+
fails no check"*:
|
|
4132
|
+
|
|
4133
|
+
- **its list is derived from the module's SOURCE** (`COVERAGE`): the test reads
|
|
4134
|
+
that source from the syntax tree and fails on a key the list lacks -- a
|
|
4135
|
+
table (`LAYOUTS` for `C_YUI_GOBJ_TREE_JS_I18N_KEYS`, `VIEWS` /
|
|
4136
|
+
`MACHINE_VIEWS` / `ZONES` for `C_YUI_GCLASS_I18N_KEYS`, the period catalog,
|
|
4137
|
+
the flag catalog, the graph's node views, the default labels of the dialogs
|
|
4138
|
+
and of the install prompt), a default (the wizard's label attrs), a value
|
|
4139
|
+
the module returns or assigns (`field_validation_key()` and `kw.warning` for
|
|
4140
|
+
`C_YUI_FORM_I18N_KEYS`, every `primary_label` of `wizard_helpers.js`), a
|
|
4141
|
+
property it writes (the `error` of `form_json_field.js`, the `label` of the
|
|
4142
|
+
route map's own nodes, the `op` of a schema import), or the severities the
|
|
4143
|
+
checks of `schema_validate.js` write (`C_YUI_SCHEMA_EDITOR_I18N_KEYS`).
|
|
4144
|
+
The source is read with the validator's own reader, in the scope each value
|
|
4145
|
+
is written in: a literal, every branch of `?:` / `||` / `??`, a `const` to
|
|
4146
|
+
its value, a `let` into everything it is given. So a key added there through
|
|
4147
|
+
a const or a let is seen, and fails without the list. A value it cannot
|
|
4148
|
+
follow there (a parameter, a call, a `let` that holds one) FAILS the
|
|
4149
|
+
coverage, naming the file and the line, unless the entry names it as data,
|
|
4150
|
+
with why: the form's `$input.yui_json_error` (the json field's own error,
|
|
4151
|
+
`FORM_JSON_FIELD_I18N_KEYS`), the route map's `map.brand.label` (the app's
|
|
4152
|
+
shell declaration, which `validate-locales` reads), the wizard's
|
|
4153
|
+
`opts.confirm_label` / `opts.next_label` (the host's own keys). An entry
|
|
4154
|
+
named as data that the source no longer reads fails too;
|
|
4155
|
+
- **a marker follows it** -- a `const`, or a marked `let` -- and no other list
|
|
4156
|
+
that marker names holds it, so the marker check fails without it;
|
|
4157
|
+
- **the module reads it anyway** (a literal or a `const` it follows): the list
|
|
4158
|
+
entry is redundant, and dropping it loses no key for a consumer.
|
|
4159
|
+
|
|
4160
|
+
The test proves itself: with the `COVERAGE` entries that pin them taken
|
|
4161
|
+
away, it reports each of the keys REV-CLOUD V20 found pinned by nothing.
|
|
4162
|
+
|
|
4163
|
+
A list is also named by a marker or by `COVERAGE`: one that is neither is an
|
|
4164
|
+
ORPHAN, and fails. Two lists have a test of their own instead, and the pin
|
|
4165
|
+
check leaves them to it: `g6_nodes_tree_i18n_keys.test.js` reads every call of
|
|
4166
|
+
the node graph's key-taking helpers from its syntax tree, and
|
|
4167
|
+
`yui_dev_controls.test.js` builds the Developer window and compares every key
|
|
4168
|
+
it asks for or carries with the list.
|
|
4169
|
+
|
|
4170
|
+
**What is NOT pinned.** `t()` is not a key function, and only the positions of
|
|
4171
|
+
`I18N_KEY_FUNCTIONS` are held to the per-call rule. So a key handed to `t()`,
|
|
4172
|
+
`i18n` or a `data-i18n*` value by an UNMARKED `let`, a call the reader does not
|
|
4173
|
+
follow or a concatenation is invisible to the validator and fails no test --
|
|
4174
|
+
in the library and in an app alike. (A PARAMETER is not, in the library: the
|
|
4175
|
+
forwarder test puts its function in the table, or demands a marker.) So is
|
|
4176
|
+
what is written INTO a `const` table after its declaration (`LIST.push("k")`,
|
|
4177
|
+
`TABLE.k = "k"`): the reader follows a const to its value, and only a marked
|
|
4178
|
+
`let` is followed into what is written into it. It is found only by reading
|
|
4179
|
+
the page in another language. In an app, a key its callers hand a wrapper of
|
|
4180
|
+
its own reaches the locales only through a list the module publishes: the
|
|
4181
|
+
wrapper's hand-off must be marked (the rule per call), and nothing ties its
|
|
4182
|
+
callers' literals to the list.
|
|
4183
|
+
|
|
4184
|
+
**What fails the build, besides a key no locale defines:**
|
|
4185
|
+
|
|
4186
|
+
- a locale that cannot be parsed;
|
|
4187
|
+
- a locale that cannot be loaded when it LOOKS like one: it holds a
|
|
4188
|
+
`translation` dict, it exports its own name (`es.js` exporting `es`), or it
|
|
4189
|
+
has a default export -- however the `translation` is built;
|
|
4190
|
+
- a locale that looks like one and exports no `{<code>}` with a
|
|
4191
|
+
`translation` dict;
|
|
4192
|
+
- a source file that cannot be parsed;
|
|
4193
|
+
- a published list no consumer can read (above);
|
|
4194
|
+
- a marker that is wrong (above), a `data` marker on what the reader follows
|
|
4195
|
+
to keys included;
|
|
4196
|
+
- a key position of a key function, in the app's own code, given what the
|
|
4197
|
+
reader cannot follow and unmarked, or given a translated text (the rule per
|
|
4198
|
+
call, above).
|
|
4199
|
+
|
|
4200
|
+
Up to `7.26.27` the locale cases were "skipped" with an OK: one language less
|
|
4201
|
+
validated, and nobody told. A file of the locales directory with a locale's
|
|
4202
|
+
name that does not look like one (`app.js`, a helper that needs a browser to
|
|
4203
|
+
load) is said and skipped, as before. The script runs the same when it is
|
|
4204
|
+
reached through a symlinked package with `--preserve-symlinks-main` (also from
|
|
4205
|
+
`NODE_OPTIONS`): both sides of its "am I the script?" test are resolved.
|
|
4206
|
+
|
|
3651
4207
|
Up to `7.26.24` each app carried its own copy, and the copies had drifted into
|
|
3652
4208
|
three families: only one read the codes of the schema editor, only the other
|
|
3653
4209
|
two the barrel imports, and two read `t('topic') + ':'` as a key built by
|
|
3654
|
-
concatenation and skipped it -- so the
|
|
3655
|
-
in wattyzer and yunovatios. No app keeps a copy
|
|
3656
|
-
library's own demo,
|
|
3657
|
-
English
|
|
3658
|
-
`test-app/scripts/validate-locales.mjs
|
|
4210
|
+
concatenation and skipped it -- so the treedb node graph's (`C_G6_NODES_TREE`)
|
|
4211
|
+
*"topic:"* went untranslated in wattyzer and yunovatios. No app keeps a copy
|
|
4212
|
+
now. `test-app`, this library's own demo, has a catalog of another shape (one
|
|
4213
|
+
`src/locales.js`, English as the source: a key IS the English text, Title Case
|
|
4214
|
+
included), so its `test-app/scripts/validate-locales.mjs` loads that catalog
|
|
4215
|
+
itself and takes everything else from the published reader
|
|
4216
|
+
(`collect_used_keys()`, exported with the rest of the reader): it sees the key
|
|
4217
|
+
functions, the consts, the lists and the markers like every app. Its rules: no
|
|
4218
|
+
duplicate key in a bundle, every key the source asks for in `es`, and every key
|
|
4219
|
+
of the library in `en` as well (they are lower-case keys, not English display
|
|
4220
|
+
text).
|
|
3659
4221
|
|
|
3660
4222
|
### Dates: never hand-roll them again
|
|
3661
4223
|
|
|
@@ -3712,7 +4274,10 @@ The library asks the APP's i18next for its keys, so a consumer must define them
|
|
|
3712
4274
|
`previous period`, …) — copy the block from `test-app/src/locales.js`, which is
|
|
3713
4275
|
the complete one: it is the only consumer that declares every mode, `rolling`
|
|
3714
4276
|
included (`last 24h`, `last 7 days`), and a missing key is **invisible** —
|
|
3715
|
-
i18next answers it with the key itself.
|
|
4277
|
+
i18next answers it with the key itself. Since `7.26.28` an app's
|
|
4278
|
+
`validate-locales` asks for the whole catalog, from `YUI_TIME_I18N_KEYS` and
|
|
4279
|
+
`C_YUI_PERIOD_I18N_KEYS`: which of it a control offers is the host's data, out
|
|
4280
|
+
of reach of a scan.
|
|
3716
4281
|
The picker subscribes ITSELF to the shell's `EV_LANGUAGE_CHANGED` (its labels
|
|
3717
4282
|
are composed at render time), so a host has nothing to forward — a host that
|
|
3718
4283
|
forwards the event anyway just repaints it twice, harmlessly. All Intl
|