@yuneta/gobj-ui 7.26.26 → 7.26.27
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 +22 -4
- package/dist/gobj-ui.cjs.js +37 -15
- package/dist/gobj-ui.es.js +37 -15
- package/package.json +1 -1
- package/scripts/validate-locales.mjs +149 -43
- package/src/c_g6_nodes_tree.js +63 -13
- package/src/g6_nodes_tree_i18n_keys.test.js +39 -0
- package/src/yui_dev.js +28 -30
package/README.md
CHANGED
|
@@ -3625,10 +3625,28 @@ Plurals are compared by language (since `7.26.26`): `songs_one` /
|
|
|
3625
3625
|
`songs_few` / `songs_many` / `songs_other` are one key, and each locale must
|
|
3626
3626
|
carry the categories of ITS language (`Intl.PluralRules`: English `one`,
|
|
3627
3627
|
`other`; Russian `one`, `few`, `many`, `other`; Chinese `other`). The key with
|
|
3628
|
-
no suffix serves `other` and `many`,
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3628
|
+
no suffix serves `other` and `many`; `zero`, `one`, `two` and `few` need their
|
|
3629
|
+
own form. That is a policy of GRAMMAR, not a need of i18next, which falls back
|
|
3630
|
+
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 key that only ends
|
|
3632
|
+
like a form (`pick_one` alone) is a plain key; ordinals (`place_ordinal_one`)
|
|
3633
|
+
are checked with the ordinal categories of each language. A value of the shell
|
|
3634
|
+
declaration is a literal when it is the endonym of one of the app's locales
|
|
3635
|
+
(the `name` its catalog exports: `"中文"`, `"español"`); any other lower-case
|
|
3636
|
+
text is a key, `"configuración"` included.
|
|
3637
|
+
|
|
3638
|
+
**A key that reaches `t()` as data is PUBLISHED by its module** (since
|
|
3639
|
+
`7.26.27`): an exported `<NAME>_I18N_KEYS` array of string literals, which
|
|
3640
|
+
`validate-locales` reads in every module the app mounts and asks the locales
|
|
3641
|
+
for. `yui_dev.js` publishes the 51 keys of the Developer window
|
|
3642
|
+
(`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 and
|
|
3644
|
+
popover labels (`G6_NODES_TREE_I18N_KEYS`). A module that hands a key to `t()`
|
|
3645
|
+
by variable adds it to such a list:
|
|
3646
|
+
|
|
3647
|
+
```js
|
|
3648
|
+
export const MY_VIEW_I18N_KEYS = ['shape', 'radius', 'apply to'];
|
|
3649
|
+
```
|
|
3632
3650
|
|
|
3633
3651
|
Up to `7.26.24` each app carried its own copy, and the copies had drifted into
|
|
3634
3652
|
three families: only one read the codes of the schema editor, only the other
|
package/dist/gobj-ui.cjs.js
CHANGED
|
@@ -8988,7 +8988,7 @@ function destroy_widgets(gobj, $container) {
|
|
|
8988
8988
|
* ids would break the association for both. The key is the
|
|
8989
8989
|
* label's own, so a language change reaches it.
|
|
8990
8990
|
************************************************************/
|
|
8991
|
-
function name_form_control($field, conf) {
|
|
8991
|
+
function name_form_control$1($field, conf) {
|
|
8992
8992
|
if (!$field || typeof $field.querySelector !== "function" || !conf) return;
|
|
8993
8993
|
let key = conf.label_i18n || conf.label || "";
|
|
8994
8994
|
if (!key) return;
|
|
@@ -9059,7 +9059,7 @@ function build_html_form(gobj, $form, prefix, template) {
|
|
|
9059
9059
|
if (placeholders[key]) html_field_conf.placeholder = placeholders[key];
|
|
9060
9060
|
if (html_field_conf.tag) {
|
|
9061
9061
|
let $field = create_form_field(gobj, $form, prefix, field_desc, html_field_conf);
|
|
9062
|
-
name_form_control($field, html_field_conf);
|
|
9062
|
+
name_form_control$1($field, html_field_conf);
|
|
9063
9063
|
$form.appendChild($field);
|
|
9064
9064
|
}
|
|
9065
9065
|
});
|
|
@@ -63793,19 +63793,34 @@ function touch_control_css() {
|
|
|
63793
63793
|
if (!coarse_pointer()) return "";
|
|
63794
63794
|
return "min-height:44px;font-size:16px;";
|
|
63795
63795
|
}
|
|
63796
|
+
var form_control_seq = 0;
|
|
63796
63797
|
function create_form_label(parent, text) {
|
|
63797
63798
|
let label = document.createElement("label");
|
|
63798
63799
|
label.textContent = (0, i18next.t)(text);
|
|
63800
|
+
label.setAttribute("data-i18n", text);
|
|
63799
63801
|
label.style.cssText = "display:block;margin-bottom:4px;font-weight:500;";
|
|
63800
63802
|
parent.appendChild(label);
|
|
63801
63803
|
return label;
|
|
63802
63804
|
}
|
|
63805
|
+
function name_form_control(parent, control) {
|
|
63806
|
+
let label = parent.lastElementChild;
|
|
63807
|
+
if (!label || label.tagName !== "LABEL") return;
|
|
63808
|
+
let key = label.getAttribute("data-i18n");
|
|
63809
|
+
if (!key) return;
|
|
63810
|
+
control.id = "g6-form-control-" + ++form_control_seq;
|
|
63811
|
+
label.htmlFor = control.id;
|
|
63812
|
+
control.setAttribute("title", (0, i18next.t)(key));
|
|
63813
|
+
control.setAttribute("data-i18n-title", key);
|
|
63814
|
+
control.setAttribute("aria-label", (0, i18next.t)(key));
|
|
63815
|
+
control.setAttribute("data-i18n-aria-label", key);
|
|
63816
|
+
}
|
|
63803
63817
|
function create_form_color_input(parent, value, onInput) {
|
|
63804
63818
|
let input = document.createElement("input");
|
|
63805
63819
|
input.type = "color";
|
|
63806
63820
|
input.value = value;
|
|
63807
63821
|
input.style.cssText = "width:100%;height:30px;padding:0;border:1px solid var(--bulma-border-weak, #d9d9d9);border-radius:4px;cursor:pointer;margin-bottom:10px;" + touch_control_css();
|
|
63808
63822
|
if (onInput) input.addEventListener("input", onInput);
|
|
63823
|
+
name_form_control(parent, input);
|
|
63809
63824
|
parent.appendChild(input);
|
|
63810
63825
|
return input;
|
|
63811
63826
|
}
|
|
@@ -63817,6 +63832,7 @@ function create_form_number_input(parent, value, min, max, onInput) {
|
|
|
63817
63832
|
input.value = value;
|
|
63818
63833
|
input.style.cssText = "width:100%;padding:4px 6px;background:var(--bulma-scheme-main-bis, #fff);color:var(--bulma-text-strong, #333);border:1px solid var(--bulma-border-weak, #d9d9d9);border-radius:4px;box-sizing:border-box;margin-bottom:10px;" + touch_control_css();
|
|
63819
63834
|
if (onInput) input.addEventListener("input", onInput);
|
|
63835
|
+
name_form_control(parent, input);
|
|
63820
63836
|
parent.appendChild(input);
|
|
63821
63837
|
return input;
|
|
63822
63838
|
}
|
|
@@ -63826,9 +63842,11 @@ function create_form_select(parent, options, marginBottom) {
|
|
|
63826
63842
|
for (let opt of options) {
|
|
63827
63843
|
let o = document.createElement("option");
|
|
63828
63844
|
o.value = opt.value;
|
|
63829
|
-
o.textContent = opt.
|
|
63845
|
+
o.textContent = (0, i18next.t)(opt.i18n);
|
|
63846
|
+
o.setAttribute("data-i18n", opt.i18n);
|
|
63830
63847
|
select.appendChild(o);
|
|
63831
63848
|
}
|
|
63849
|
+
name_form_control(parent, select);
|
|
63832
63850
|
parent.appendChild(select);
|
|
63833
63851
|
return select;
|
|
63834
63852
|
}
|
|
@@ -64587,7 +64605,7 @@ function show_port_popover(gobj) {
|
|
|
64587
64605
|
let shapeSelect = create_form_select(popover, PORT_SHAPES.map((shape) => {
|
|
64588
64606
|
return {
|
|
64589
64607
|
value: shape,
|
|
64590
|
-
|
|
64608
|
+
i18n: shape
|
|
64591
64609
|
};
|
|
64592
64610
|
}));
|
|
64593
64611
|
shapeSelect.value = orig_shape;
|
|
@@ -64598,15 +64616,15 @@ function show_port_popover(gobj) {
|
|
|
64598
64616
|
let scopeSelect = create_form_select(popover, [
|
|
64599
64617
|
{
|
|
64600
64618
|
value: "this",
|
|
64601
|
-
|
|
64619
|
+
i18n: "this port"
|
|
64602
64620
|
},
|
|
64603
64621
|
{
|
|
64604
64622
|
value: "same_topic",
|
|
64605
|
-
|
|
64623
|
+
i18n: "same port in topic"
|
|
64606
64624
|
},
|
|
64607
64625
|
{
|
|
64608
64626
|
value: "all",
|
|
64609
|
-
|
|
64627
|
+
i18n: "all ports"
|
|
64610
64628
|
}
|
|
64611
64629
|
]);
|
|
64612
64630
|
create_form_button_row(popover, [{
|
|
@@ -65294,15 +65312,15 @@ function show_edge_popover(gobj) {
|
|
|
65294
65312
|
let scopeSelect = create_form_select(popover, [
|
|
65295
65313
|
{
|
|
65296
65314
|
value: "this",
|
|
65297
|
-
|
|
65315
|
+
i18n: "this edge"
|
|
65298
65316
|
},
|
|
65299
65317
|
{
|
|
65300
65318
|
value: "same_type",
|
|
65301
|
-
|
|
65319
|
+
i18n: "same type edges"
|
|
65302
65320
|
},
|
|
65303
65321
|
{
|
|
65304
65322
|
value: "all",
|
|
65305
|
-
|
|
65323
|
+
i18n: "all edges"
|
|
65306
65324
|
}
|
|
65307
65325
|
]);
|
|
65308
65326
|
create_form_button_row(popover, [{
|
|
@@ -65446,7 +65464,7 @@ function show_node_popover(gobj) {
|
|
|
65446
65464
|
let shapeSelect = create_form_select(popover, NODE_SHAPES.map((shape) => {
|
|
65447
65465
|
return {
|
|
65448
65466
|
value: shape,
|
|
65449
|
-
|
|
65467
|
+
i18n: shape
|
|
65450
65468
|
};
|
|
65451
65469
|
}));
|
|
65452
65470
|
shapeSelect.value = nodeData.data && nodeData.data.desc ? node_figure_of(gobj, nodeData.data.desc, nodeData.data.graph_props || {}) : DEFAULT_NODE_SHAPE;
|
|
@@ -65455,15 +65473,15 @@ function show_node_popover(gobj) {
|
|
|
65455
65473
|
let scopeSelect = create_form_select(popover, [
|
|
65456
65474
|
{
|
|
65457
65475
|
value: "this",
|
|
65458
|
-
|
|
65476
|
+
i18n: "this node"
|
|
65459
65477
|
},
|
|
65460
65478
|
{
|
|
65461
65479
|
value: "same_topic",
|
|
65462
|
-
|
|
65480
|
+
i18n: "same topic nodes"
|
|
65463
65481
|
},
|
|
65464
65482
|
{
|
|
65465
65483
|
value: "all",
|
|
65466
|
-
|
|
65484
|
+
i18n: "all nodes"
|
|
65467
65485
|
}
|
|
65468
65486
|
]);
|
|
65469
65487
|
create_form_button_row(popover, [{
|
|
@@ -66170,7 +66188,11 @@ function show_create_popover(gobj) {
|
|
|
66170
66188
|
});
|
|
66171
66189
|
let idLabel = document.createElement("div");
|
|
66172
66190
|
idLabel.style.cssText = "margin-bottom:4px;font-weight:500;";
|
|
66173
|
-
|
|
66191
|
+
let idWord = document.createElement("span");
|
|
66192
|
+
idWord.textContent = (0, i18next.t)("node id");
|
|
66193
|
+
idWord.setAttribute("data-i18n", "node id");
|
|
66194
|
+
idLabel.appendChild(idWord);
|
|
66195
|
+
idLabel.appendChild(document.createTextNode(":"));
|
|
66174
66196
|
popover.appendChild(idLabel);
|
|
66175
66197
|
let idInput = document.createElement("input");
|
|
66176
66198
|
idInput.type = "text";
|
package/dist/gobj-ui.es.js
CHANGED
|
@@ -8983,7 +8983,7 @@ function destroy_widgets(gobj, $container) {
|
|
|
8983
8983
|
* ids would break the association for both. The key is the
|
|
8984
8984
|
* label's own, so a language change reaches it.
|
|
8985
8985
|
************************************************************/
|
|
8986
|
-
function name_form_control($field, conf) {
|
|
8986
|
+
function name_form_control$1($field, conf) {
|
|
8987
8987
|
if (!$field || typeof $field.querySelector !== "function" || !conf) return;
|
|
8988
8988
|
let key = conf.label_i18n || conf.label || "";
|
|
8989
8989
|
if (!key) return;
|
|
@@ -9054,7 +9054,7 @@ function build_html_form(gobj, $form, prefix, template) {
|
|
|
9054
9054
|
if (placeholders[key]) html_field_conf.placeholder = placeholders[key];
|
|
9055
9055
|
if (html_field_conf.tag) {
|
|
9056
9056
|
let $field = create_form_field(gobj, $form, prefix, field_desc, html_field_conf);
|
|
9057
|
-
name_form_control($field, html_field_conf);
|
|
9057
|
+
name_form_control$1($field, html_field_conf);
|
|
9058
9058
|
$form.appendChild($field);
|
|
9059
9059
|
}
|
|
9060
9060
|
});
|
|
@@ -63788,19 +63788,34 @@ function touch_control_css() {
|
|
|
63788
63788
|
if (!coarse_pointer()) return "";
|
|
63789
63789
|
return "min-height:44px;font-size:16px;";
|
|
63790
63790
|
}
|
|
63791
|
+
var form_control_seq = 0;
|
|
63791
63792
|
function create_form_label(parent, text) {
|
|
63792
63793
|
let label = document.createElement("label");
|
|
63793
63794
|
label.textContent = t(text);
|
|
63795
|
+
label.setAttribute("data-i18n", text);
|
|
63794
63796
|
label.style.cssText = "display:block;margin-bottom:4px;font-weight:500;";
|
|
63795
63797
|
parent.appendChild(label);
|
|
63796
63798
|
return label;
|
|
63797
63799
|
}
|
|
63800
|
+
function name_form_control(parent, control) {
|
|
63801
|
+
let label = parent.lastElementChild;
|
|
63802
|
+
if (!label || label.tagName !== "LABEL") return;
|
|
63803
|
+
let key = label.getAttribute("data-i18n");
|
|
63804
|
+
if (!key) return;
|
|
63805
|
+
control.id = "g6-form-control-" + ++form_control_seq;
|
|
63806
|
+
label.htmlFor = control.id;
|
|
63807
|
+
control.setAttribute("title", t(key));
|
|
63808
|
+
control.setAttribute("data-i18n-title", key);
|
|
63809
|
+
control.setAttribute("aria-label", t(key));
|
|
63810
|
+
control.setAttribute("data-i18n-aria-label", key);
|
|
63811
|
+
}
|
|
63798
63812
|
function create_form_color_input(parent, value, onInput) {
|
|
63799
63813
|
let input = document.createElement("input");
|
|
63800
63814
|
input.type = "color";
|
|
63801
63815
|
input.value = value;
|
|
63802
63816
|
input.style.cssText = "width:100%;height:30px;padding:0;border:1px solid var(--bulma-border-weak, #d9d9d9);border-radius:4px;cursor:pointer;margin-bottom:10px;" + touch_control_css();
|
|
63803
63817
|
if (onInput) input.addEventListener("input", onInput);
|
|
63818
|
+
name_form_control(parent, input);
|
|
63804
63819
|
parent.appendChild(input);
|
|
63805
63820
|
return input;
|
|
63806
63821
|
}
|
|
@@ -63812,6 +63827,7 @@ function create_form_number_input(parent, value, min, max, onInput) {
|
|
|
63812
63827
|
input.value = value;
|
|
63813
63828
|
input.style.cssText = "width:100%;padding:4px 6px;background:var(--bulma-scheme-main-bis, #fff);color:var(--bulma-text-strong, #333);border:1px solid var(--bulma-border-weak, #d9d9d9);border-radius:4px;box-sizing:border-box;margin-bottom:10px;" + touch_control_css();
|
|
63814
63829
|
if (onInput) input.addEventListener("input", onInput);
|
|
63830
|
+
name_form_control(parent, input);
|
|
63815
63831
|
parent.appendChild(input);
|
|
63816
63832
|
return input;
|
|
63817
63833
|
}
|
|
@@ -63821,9 +63837,11 @@ function create_form_select(parent, options, marginBottom) {
|
|
|
63821
63837
|
for (let opt of options) {
|
|
63822
63838
|
let o = document.createElement("option");
|
|
63823
63839
|
o.value = opt.value;
|
|
63824
|
-
o.textContent = opt.
|
|
63840
|
+
o.textContent = t(opt.i18n);
|
|
63841
|
+
o.setAttribute("data-i18n", opt.i18n);
|
|
63825
63842
|
select.appendChild(o);
|
|
63826
63843
|
}
|
|
63844
|
+
name_form_control(parent, select);
|
|
63827
63845
|
parent.appendChild(select);
|
|
63828
63846
|
return select;
|
|
63829
63847
|
}
|
|
@@ -64582,7 +64600,7 @@ function show_port_popover(gobj) {
|
|
|
64582
64600
|
let shapeSelect = create_form_select(popover, PORT_SHAPES.map((shape) => {
|
|
64583
64601
|
return {
|
|
64584
64602
|
value: shape,
|
|
64585
|
-
|
|
64603
|
+
i18n: shape
|
|
64586
64604
|
};
|
|
64587
64605
|
}));
|
|
64588
64606
|
shapeSelect.value = orig_shape;
|
|
@@ -64593,15 +64611,15 @@ function show_port_popover(gobj) {
|
|
|
64593
64611
|
let scopeSelect = create_form_select(popover, [
|
|
64594
64612
|
{
|
|
64595
64613
|
value: "this",
|
|
64596
|
-
|
|
64614
|
+
i18n: "this port"
|
|
64597
64615
|
},
|
|
64598
64616
|
{
|
|
64599
64617
|
value: "same_topic",
|
|
64600
|
-
|
|
64618
|
+
i18n: "same port in topic"
|
|
64601
64619
|
},
|
|
64602
64620
|
{
|
|
64603
64621
|
value: "all",
|
|
64604
|
-
|
|
64622
|
+
i18n: "all ports"
|
|
64605
64623
|
}
|
|
64606
64624
|
]);
|
|
64607
64625
|
create_form_button_row(popover, [{
|
|
@@ -65289,15 +65307,15 @@ function show_edge_popover(gobj) {
|
|
|
65289
65307
|
let scopeSelect = create_form_select(popover, [
|
|
65290
65308
|
{
|
|
65291
65309
|
value: "this",
|
|
65292
|
-
|
|
65310
|
+
i18n: "this edge"
|
|
65293
65311
|
},
|
|
65294
65312
|
{
|
|
65295
65313
|
value: "same_type",
|
|
65296
|
-
|
|
65314
|
+
i18n: "same type edges"
|
|
65297
65315
|
},
|
|
65298
65316
|
{
|
|
65299
65317
|
value: "all",
|
|
65300
|
-
|
|
65318
|
+
i18n: "all edges"
|
|
65301
65319
|
}
|
|
65302
65320
|
]);
|
|
65303
65321
|
create_form_button_row(popover, [{
|
|
@@ -65441,7 +65459,7 @@ function show_node_popover(gobj) {
|
|
|
65441
65459
|
let shapeSelect = create_form_select(popover, NODE_SHAPES.map((shape) => {
|
|
65442
65460
|
return {
|
|
65443
65461
|
value: shape,
|
|
65444
|
-
|
|
65462
|
+
i18n: shape
|
|
65445
65463
|
};
|
|
65446
65464
|
}));
|
|
65447
65465
|
shapeSelect.value = nodeData.data && nodeData.data.desc ? node_figure_of(gobj, nodeData.data.desc, nodeData.data.graph_props || {}) : DEFAULT_NODE_SHAPE;
|
|
@@ -65450,15 +65468,15 @@ function show_node_popover(gobj) {
|
|
|
65450
65468
|
let scopeSelect = create_form_select(popover, [
|
|
65451
65469
|
{
|
|
65452
65470
|
value: "this",
|
|
65453
|
-
|
|
65471
|
+
i18n: "this node"
|
|
65454
65472
|
},
|
|
65455
65473
|
{
|
|
65456
65474
|
value: "same_topic",
|
|
65457
|
-
|
|
65475
|
+
i18n: "same topic nodes"
|
|
65458
65476
|
},
|
|
65459
65477
|
{
|
|
65460
65478
|
value: "all",
|
|
65461
|
-
|
|
65479
|
+
i18n: "all nodes"
|
|
65462
65480
|
}
|
|
65463
65481
|
]);
|
|
65464
65482
|
create_form_button_row(popover, [{
|
|
@@ -66165,7 +66183,11 @@ function show_create_popover(gobj) {
|
|
|
66165
66183
|
});
|
|
66166
66184
|
let idLabel = document.createElement("div");
|
|
66167
66185
|
idLabel.style.cssText = "margin-bottom:4px;font-weight:500;";
|
|
66168
|
-
|
|
66186
|
+
let idWord = document.createElement("span");
|
|
66187
|
+
idWord.textContent = t("node id");
|
|
66188
|
+
idWord.setAttribute("data-i18n", "node id");
|
|
66189
|
+
idLabel.appendChild(idWord);
|
|
66190
|
+
idLabel.appendChild(document.createTextNode(":"));
|
|
66169
66191
|
popover.appendChild(idLabel);
|
|
66170
66192
|
let idInput = document.createElement("input");
|
|
66171
66193
|
idInput.type = "text";
|
package/package.json
CHANGED
|
@@ -22,7 +22,14 @@
|
|
|
22
22
|
* `zero`, `one`, `two` and `few` change the grammar and need
|
|
23
23
|
* their own form when the language has them. A form the
|
|
24
24
|
* language never uses is a violation; `_zero` is always
|
|
25
|
-
* allowed (i18next uses it for a count of 0).
|
|
25
|
+
* allowed (i18next uses it for a count of 0). i18next itself
|
|
26
|
+
* falls back to the bare key for ANY missing form: asking for
|
|
27
|
+
* `one`/`few` is a policy of grammar, not of i18next. A key
|
|
28
|
+
* that only ENDS like a form (`pick_one` alone) is a plain
|
|
29
|
+
* key: a group is a plural when a locale has its bare base,
|
|
30
|
+
* its `_other` or two of its forms. Ordinals
|
|
31
|
+
* (`place_ordinal_one`) are checked with the ordinal
|
|
32
|
+
* categories of each language.
|
|
26
33
|
* 4. No duplicate key in a locale file (an object literal keeps
|
|
27
34
|
* the LAST one and says nothing -- a stale entry silently
|
|
28
35
|
* overrides a new one; it happened to "last" and "loading").
|
|
@@ -31,7 +38,10 @@
|
|
|
31
38
|
* fine and simply never changes language -- invisible by
|
|
32
39
|
* construction, and it shipped: a Settings `label` column).
|
|
33
40
|
*
|
|
34
|
-
* Rule 5 reads the app's own source (recursively
|
|
41
|
+
* Rule 5 reads the app's own source (recursively, .js and .mjs;
|
|
42
|
+
* not tests, not a locales/ directory), the keys a module
|
|
43
|
+
* PUBLISHES as data (an exported `<NAME>_I18N_KEYS` array: the
|
|
44
|
+
* keys it hands to t() as variables, yui_dev.js's 51), the shell
|
|
35
45
|
* declaration (`src/app_config.json`) and the gobj-ui modules the
|
|
36
46
|
* app MOUNTS, transitively -- deep, through the barrel, or by a
|
|
37
47
|
* dynamic import(): the library translates through the APP's
|
|
@@ -59,7 +69,8 @@
|
|
|
59
69
|
* `attr_keys` names properties whose string value is an i18n key
|
|
60
70
|
* (`app_title: "central"`), `array_keys` properties whose value is
|
|
61
71
|
* an array of keys. A key the app hands over that way reaches no
|
|
62
|
-
* t() the scan can read.
|
|
72
|
+
* t() the scan can read. A name declared that no source uses is a
|
|
73
|
+
* violation (a typo would turn its check off).
|
|
63
74
|
*
|
|
64
75
|
* A locale is a file <code>.js of the locales directory (en.js,
|
|
65
76
|
* es.js, pt-br.js; flags.js and locales.js are not) exporting
|
|
@@ -84,7 +95,7 @@ import {pathToFileURL} from "url";
|
|
|
84
95
|
|
|
85
96
|
const ASCII_RE = /^[\x20-\x7e]+$/;
|
|
86
97
|
const LOCALE_FILE_RE = /^([a-z]{2,3}(?:[-_][a-zA-Z]{2,4})?)\.js$/;
|
|
87
|
-
const PLURAL_RE = /^(
|
|
98
|
+
const PLURAL_RE = /^(.+?)(_ordinal)?_(zero|one|two|few|many|other)$/;
|
|
88
99
|
|
|
89
100
|
/* This package: the library the app builds with. */
|
|
90
101
|
const LIB = new URL("../", import.meta.url);
|
|
@@ -201,47 +212,64 @@ async function load_locales(locales_dir)
|
|
|
201
212
|
codes = ["en", ...codes.filter((c) => c !== "en")];
|
|
202
213
|
}
|
|
203
214
|
let locales = {};
|
|
215
|
+
let names = new Set();
|
|
204
216
|
for(let code of codes) {
|
|
205
|
-
let mod
|
|
206
|
-
|
|
217
|
+
let mod;
|
|
218
|
+
try {
|
|
219
|
+
mod = await import(new URL(`${code}.js`, locales_dir).href);
|
|
220
|
+
} catch(e) {
|
|
221
|
+
/* A helper that happens to have a locale's name (app.js), and
|
|
222
|
+
* needs a browser to load: said and skipped, not a crash */
|
|
223
|
+
note(`${code}.js cannot be loaded here (${e.message}): not a locale, skipped`);
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
let camel = code.replace(/[-_]([a-z])/gi, (m, c) => c.toUpperCase()); // pt-br -> ptBr
|
|
227
|
+
let upper = code.replace(/[-_]([a-z]+)/gi, (m, c) => c.toUpperCase()); // pt-br -> ptBR
|
|
228
|
+
let locale = mod[code] || mod[code.replace(/-/g, "_")] || mod[camel] || mod[upper] || mod.default;
|
|
207
229
|
if(!locale || !locale.translation || typeof locale.translation !== "object") {
|
|
208
230
|
note(`${code}.js exports no {${code}} with a translation dict: not a locale, skipped`);
|
|
209
231
|
continue;
|
|
210
232
|
}
|
|
211
233
|
locales[code] = locale.translation;
|
|
234
|
+
if(typeof locale.name === "string" && locale.name) {
|
|
235
|
+
names.add(locale.name); // its endonym: a literal of a language menu
|
|
236
|
+
}
|
|
212
237
|
}
|
|
213
238
|
if(Object.keys(locales).length === 0) {
|
|
214
239
|
abort(`no locale (<code>.js exporting {<code>: {translation}}) in ${locales_dir.pathname}`);
|
|
215
240
|
}
|
|
216
|
-
return locales;
|
|
241
|
+
return {locales, names};
|
|
217
242
|
}
|
|
218
243
|
|
|
219
244
|
/***************************************************************
|
|
220
245
|
* The plural categories of a language (Intl.PluralRules)
|
|
221
246
|
***************************************************************/
|
|
222
|
-
function plural_categories(code)
|
|
247
|
+
function plural_categories(code, ordinal)
|
|
223
248
|
{
|
|
224
249
|
try {
|
|
225
|
-
return new Set(new Intl.PluralRules(code.replace(/_/g, "-")
|
|
250
|
+
return new Set(new Intl.PluralRules(code.replace(/_/g, "-"),
|
|
251
|
+
{type: ordinal? "ordinal" : "cardinal"}).resolvedOptions().pluralCategories);
|
|
226
252
|
} catch(e) {
|
|
227
253
|
return new Set(["one", "other"]);
|
|
228
254
|
}
|
|
229
255
|
}
|
|
230
256
|
|
|
231
257
|
/***************************************************************
|
|
232
|
-
* A locale's keys split: plain ones, and {
|
|
258
|
+
* A locale's keys split: plain ones, and {group: Set(categories)}, a
|
|
259
|
+
* group being a base, or "<base>#ordinal" for `<base>_ordinal_<cat>`
|
|
233
260
|
***************************************************************/
|
|
234
|
-
function split_plurals(keys)
|
|
261
|
+
function split_plurals(keys, plural_groups)
|
|
235
262
|
{
|
|
236
263
|
let plain = new Set();
|
|
237
264
|
let plurals = new Map();
|
|
238
265
|
for(let k of keys) {
|
|
239
266
|
let m = k.match(PLURAL_RE);
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
267
|
+
let group = m? m[1] + (m[2]? "#ordinal" : "") : null;
|
|
268
|
+
if(m && plural_groups.has(group)) {
|
|
269
|
+
if(!plurals.has(group)) {
|
|
270
|
+
plurals.set(group, new Set());
|
|
243
271
|
}
|
|
244
|
-
plurals.get(
|
|
272
|
+
plurals.get(group).add(m[3]);
|
|
245
273
|
} else {
|
|
246
274
|
plain.add(k);
|
|
247
275
|
}
|
|
@@ -249,20 +277,67 @@ function split_plurals(keys)
|
|
|
249
277
|
return {plain, plurals};
|
|
250
278
|
}
|
|
251
279
|
|
|
280
|
+
/***************************************************************
|
|
281
|
+
* Which `<x>_<category>` keys ARE plurals: a key that only ends like one
|
|
282
|
+
* (`pick_one`, `step_two`) is a key. A group is a plural when some locale
|
|
283
|
+
* has its bare base, or its `_other` form, or two of its forms.
|
|
284
|
+
***************************************************************/
|
|
285
|
+
function find_plural_groups(locales)
|
|
286
|
+
{
|
|
287
|
+
let forms = new Map(); // group -> Set of "code:cat"
|
|
288
|
+
let bare = new Set();
|
|
289
|
+
for(let [code, translation] of Object.entries(locales)) {
|
|
290
|
+
for(let k of Object.keys(translation)) {
|
|
291
|
+
bare.add(k);
|
|
292
|
+
let m = k.match(PLURAL_RE);
|
|
293
|
+
if(m) {
|
|
294
|
+
let group = m[1] + (m[2]? "#ordinal" : "");
|
|
295
|
+
if(!forms.has(group)) {
|
|
296
|
+
forms.set(group, new Map());
|
|
297
|
+
}
|
|
298
|
+
let per = forms.get(group);
|
|
299
|
+
if(!per.has(code)) {
|
|
300
|
+
per.set(code, new Set());
|
|
301
|
+
}
|
|
302
|
+
per.get(code).add(m[3]);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
let groups = new Set();
|
|
307
|
+
for(let [group, per] of forms) {
|
|
308
|
+
let base = group.replace(/#ordinal$/, "");
|
|
309
|
+
let is_plural = bare.has(base) && !group.endsWith("#ordinal");
|
|
310
|
+
for(let cats of per.values()) {
|
|
311
|
+
if(cats.has("other") || cats.size >= 2) {
|
|
312
|
+
is_plural = true;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if(group.endsWith("#ordinal")) {
|
|
316
|
+
is_plural = true; // `_ordinal_` says it
|
|
317
|
+
}
|
|
318
|
+
if(is_plural) {
|
|
319
|
+
groups.add(group);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return groups;
|
|
323
|
+
}
|
|
324
|
+
|
|
252
325
|
/***************************************************************
|
|
253
326
|
*
|
|
254
327
|
***************************************************************/
|
|
255
328
|
async function main()
|
|
256
329
|
{
|
|
257
330
|
let opts = parse_args(process.argv.slice(2));
|
|
258
|
-
let locales = await load_locales(opts.locales);
|
|
331
|
+
let {locales, names} = await load_locales(opts.locales);
|
|
332
|
+
opts.language_names = names;
|
|
259
333
|
let codes = Object.keys(locales);
|
|
260
334
|
|
|
261
335
|
/* Per-locale key checks (ASCII + lower-case) */
|
|
336
|
+
let plural_groups = find_plural_groups(locales);
|
|
262
337
|
let split = {};
|
|
263
338
|
for(let code of codes) {
|
|
264
339
|
let keys = Object.keys(locales[code]);
|
|
265
|
-
split[code] = split_plurals(keys);
|
|
340
|
+
split[code] = split_plurals(keys, plural_groups);
|
|
266
341
|
for(let k of keys) {
|
|
267
342
|
if(!ASCII_RE.test(k)) {
|
|
268
343
|
fail(`[${code}] non-ASCII key: ${JSON.stringify(k)}`);
|
|
@@ -277,11 +352,9 @@ async function main()
|
|
|
277
352
|
* The plural bases, of any locale: a plain key that is one of them
|
|
278
353
|
* belongs to its plural, and is checked with it
|
|
279
354
|
*/
|
|
280
|
-
let bases = new Set();
|
|
281
|
-
for(let
|
|
282
|
-
|
|
283
|
-
bases.add(base);
|
|
284
|
-
}
|
|
355
|
+
let bases = new Set(); // the plain base of each plural group
|
|
356
|
+
for(let group of plural_groups) {
|
|
357
|
+
bases.add(group.replace(/#ordinal$/, ""));
|
|
285
358
|
}
|
|
286
359
|
|
|
287
360
|
/* Symmetry of the plain keys: every locale carries those of the first */
|
|
@@ -309,19 +382,21 @@ async function main()
|
|
|
309
382
|
*/
|
|
310
383
|
const SERVED_BY_THE_BASE = new Set(["other", "many"]);
|
|
311
384
|
for(let code of codes) {
|
|
312
|
-
let
|
|
313
|
-
|
|
314
|
-
let
|
|
385
|
+
for(let group of plural_groups) {
|
|
386
|
+
let ordinal = group.endsWith("#ordinal");
|
|
387
|
+
let base = group.replace(/#ordinal$/, "");
|
|
388
|
+
let wanted = plural_categories(code, ordinal);
|
|
389
|
+
let have = split[code].plurals.get(group) || new Set();
|
|
315
390
|
let has_base = split[code].plain.has(base);
|
|
316
391
|
for(let c of wanted) {
|
|
317
392
|
if(!have.has(c) && !(has_base && SERVED_BY_THE_BASE.has(c))) {
|
|
318
|
-
fail(`[${code}] plural ${JSON.stringify(base)} lacks the form "${c}" of its language` +
|
|
393
|
+
fail(`[${code}] ${ordinal? "ordinal" : "plural"} ${JSON.stringify(base)} lacks the form "${c}" of its language` +
|
|
319
394
|
(SERVED_BY_THE_BASE.has(c)? ` (or the key ${JSON.stringify(base)} itself)` : ""));
|
|
320
395
|
}
|
|
321
396
|
}
|
|
322
397
|
for(let c of have) {
|
|
323
398
|
if(!wanted.has(c) && c !== "zero") {
|
|
324
|
-
fail(`[${code}] plural ${JSON.stringify(base)} has a form "${c}" its language never uses`);
|
|
399
|
+
fail(`[${code}] ${ordinal? "ordinal" : "plural"} ${JSON.stringify(base)} has a form "${c}" its language never uses`);
|
|
325
400
|
}
|
|
326
401
|
}
|
|
327
402
|
}
|
|
@@ -424,6 +499,11 @@ function collect_used_keys(opts)
|
|
|
424
499
|
}
|
|
425
500
|
let array_re = opts.array_keys.length > 0?
|
|
426
501
|
re(String.raw`\b(?:` + opts.array_keys.join("|") + String.raw`)\s*:\s*\[([^\]]*)\]`) : null;
|
|
502
|
+
/* A module publishes the keys it hands to t() as DATA (yui_dev.js:
|
|
503
|
+
* YUI_DEV_I18N_KEYS): every key of such an array is asked for */
|
|
504
|
+
let published_re = /\bexport\s+const\s+[A-Z0-9_]+_I18N_KEYS\s*=\s*\[([^\]]*)\]/g;
|
|
505
|
+
let declared = new Set([...opts.attr_keys, ...opts.array_keys]);
|
|
506
|
+
let declared_seen = new Set();
|
|
427
507
|
|
|
428
508
|
let scan = (url) => {
|
|
429
509
|
/* Comments are not code: the library documents the contract with
|
|
@@ -442,15 +522,29 @@ function collect_used_keys(opts)
|
|
|
442
522
|
keys.add(k);
|
|
443
523
|
}
|
|
444
524
|
}
|
|
525
|
+
let lists = [];
|
|
445
526
|
if(array_re) {
|
|
446
527
|
array_re.lastIndex = 0;
|
|
447
528
|
let f;
|
|
448
529
|
while((f = array_re.exec(code)) !== null) {
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
530
|
+
lists.push(f[1]);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
published_re.lastIndex = 0;
|
|
534
|
+
let pm;
|
|
535
|
+
while((pm = published_re.exec(code)) !== null) {
|
|
536
|
+
lists.push(pm[1]);
|
|
537
|
+
}
|
|
538
|
+
for(let list of lists) {
|
|
539
|
+
let lit_re = re(STR);
|
|
540
|
+
let l;
|
|
541
|
+
while((l = lit_re.exec(list)) !== null) {
|
|
542
|
+
keys.add(literal_of(l));
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
for(let name of declared) {
|
|
546
|
+
if(re(String.raw`\b` + name + String.raw`\s*:`).test(code)) {
|
|
547
|
+
declared_seen.add(name);
|
|
454
548
|
}
|
|
455
549
|
}
|
|
456
550
|
return code;
|
|
@@ -470,7 +564,14 @@ function collect_used_keys(opts)
|
|
|
470
564
|
}
|
|
471
565
|
}
|
|
472
566
|
|
|
473
|
-
|
|
567
|
+
/* A name declared and found in no source is a typo that turns its check off */
|
|
568
|
+
for(let name of declared) {
|
|
569
|
+
if(!declared_seen.has(name)) {
|
|
570
|
+
fail(`package.json "validate-locales" names ${JSON.stringify(name)}, which no source uses: a typo turns its check off`);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
keys_from_shell_config(opts.config, keys, opts.language_names);
|
|
474
575
|
|
|
475
576
|
/*
|
|
476
577
|
* ...and the gobj-ui views it MOUNTS. Only the modules actually
|
|
@@ -504,10 +605,13 @@ function collect_used_keys(opts)
|
|
|
504
605
|
}
|
|
505
606
|
|
|
506
607
|
/***************************************************************
|
|
507
|
-
* The .js files of a source directory, recursively: not the tests
|
|
508
|
-
*
|
|
509
|
-
* or a
|
|
608
|
+
* The .js / .mjs files of a source directory, recursively: not the tests
|
|
609
|
+
* (*.test.js, *.spec.js, __tests__/, test/, tests/), not a locales/
|
|
610
|
+
* directory (its keys are definitions, not uses), not node_modules or a
|
|
611
|
+
* build
|
|
510
612
|
***************************************************************/
|
|
613
|
+
const SKIPPED_DIRS = new Set(["node_modules", "dist", "locales", "__tests__", "test", "tests"]);
|
|
614
|
+
|
|
511
615
|
function source_files(dir, locales_dir)
|
|
512
616
|
{
|
|
513
617
|
let out = [];
|
|
@@ -519,11 +623,11 @@ function source_files(dir, locales_dir)
|
|
|
519
623
|
let url = new URL(f, d);
|
|
520
624
|
let st = statSync(url);
|
|
521
625
|
if(st.isDirectory()) {
|
|
522
|
-
if(f
|
|
626
|
+
if(SKIPPED_DIRS.has(f) || f.startsWith(".")) {
|
|
523
627
|
continue;
|
|
524
628
|
}
|
|
525
629
|
walk(new URL(f + "/", d));
|
|
526
|
-
} else if(f
|
|
630
|
+
} else if(/\.m?js$/.test(f) && !/\.(test|spec)\.m?js$/.test(f)) {
|
|
527
631
|
out.push(url);
|
|
528
632
|
}
|
|
529
633
|
}
|
|
@@ -540,15 +644,17 @@ function source_files(dir, locales_dir)
|
|
|
540
644
|
* ever -- which is how four toolbar buttons announced themselves as
|
|
541
645
|
* "toggle language" in Spanish.
|
|
542
646
|
*
|
|
543
|
-
* A value that is not
|
|
544
|
-
*
|
|
545
|
-
* ("中文", "
|
|
647
|
+
* A value that is not lower-case is a literal the config carries on
|
|
648
|
+
* purpose ("ES/EN" on a language button); so is the endonym of one of the
|
|
649
|
+
* app's locales (its `name`: "中文", "español") in a language menu. Any
|
|
650
|
+
* other value is a key -- "configuración" is a Spanish literal that forgot
|
|
651
|
+
* its key, and it is reported, as an ASCII one would be. `wordmark` and `alt` are
|
|
546
652
|
* deliberately out: a brand name is not translated. A key starting with
|
|
547
653
|
* `_` is a comment.
|
|
548
654
|
***************************************************************/
|
|
549
655
|
const CONFIG_I18N_FIELDS = ["name", "aria_label", "tooltip", "label", "title", "subtitle"];
|
|
550
656
|
|
|
551
|
-
function keys_from_shell_config(url, keys)
|
|
657
|
+
function keys_from_shell_config(url, keys, language_names)
|
|
552
658
|
{
|
|
553
659
|
if(!existsSync(url)) {
|
|
554
660
|
note(`no ${url.pathname}: no shell keys checked`);
|
|
@@ -575,7 +681,7 @@ function keys_from_shell_config(url, keys)
|
|
|
575
681
|
continue;
|
|
576
682
|
}
|
|
577
683
|
if(CONFIG_I18N_FIELDS.includes(k) && typeof v === "string" && v
|
|
578
|
-
&&
|
|
684
|
+
&& v === v.toLowerCase() && !language_names.has(v)) {
|
|
579
685
|
keys.add(v);
|
|
580
686
|
}
|
|
581
687
|
walk(v);
|
package/src/c_g6_nodes_tree.js
CHANGED
|
@@ -340,6 +340,19 @@ const SHAPE_SIZE = {
|
|
|
340
340
|
* default: a figure is a POINT of the topology, and a circle is
|
|
341
341
|
* the one figure that says nothing more than that. */
|
|
342
342
|
const NODE_SHAPES = ['square', 'circle', 'diamond', 'triangle', 'hexagon', 'star'];
|
|
343
|
+
|
|
344
|
+
/* The keys of this view that reach t() as DATA or as an ARGUMENT -- the
|
|
345
|
+
* shape tables, the labels of the property popovers (create_form_label),
|
|
346
|
+
* the confirm button (show_confirm_popover): read by a consumer's
|
|
347
|
+
* validate-locales, which asks the locales for every key of an exported
|
|
348
|
+
* `*_I18N_KEYS` array of a module the app mounts. A shape added to
|
|
349
|
+
* NODE_SHAPES or PORT_SHAPES, or a new label, goes here too (a test checks
|
|
350
|
+
* the shapes). */
|
|
351
|
+
export const G6_NODES_TREE_I18N_KEYS = [
|
|
352
|
+
'square', 'circle', 'diamond', 'triangle', 'hexagon', 'star',
|
|
353
|
+
'shape', 'radius', 'apply to', 'line width', 'color', 'fill color', 'stroke color',
|
|
354
|
+
'delete'
|
|
355
|
+
];
|
|
343
356
|
const DEFAULT_NODE_SHAPE = 'circle';
|
|
344
357
|
|
|
345
358
|
/***************************************************************
|
|
@@ -4931,15 +4944,43 @@ function touch_control_css()
|
|
|
4931
4944
|
* one id.
|
|
4932
4945
|
*/
|
|
4933
4946
|
|
|
4947
|
+
/*
|
|
4948
|
+
* A form of the property popovers is a LABEL followed by its control. The
|
|
4949
|
+
* label carries its key (`text` is one) and the control built next is
|
|
4950
|
+
* NAMED by it: a `<label>` beside a control names nothing, so the control
|
|
4951
|
+
* gets the label's `for`, and its own `title` and `aria-label`, with their
|
|
4952
|
+
* keys (REV-CLOUD V19.4.1: 11 controls without a name).
|
|
4953
|
+
*/
|
|
4954
|
+
let form_control_seq = 0;
|
|
4955
|
+
|
|
4934
4956
|
function create_form_label(parent, text)
|
|
4935
4957
|
{
|
|
4936
4958
|
let label = document.createElement('label');
|
|
4937
4959
|
label.textContent = t(text);
|
|
4960
|
+
label.setAttribute('data-i18n', text);
|
|
4938
4961
|
label.style.cssText = 'display:block;margin-bottom:4px;font-weight:500;';
|
|
4939
4962
|
parent.appendChild(label);
|
|
4940
4963
|
return label;
|
|
4941
4964
|
}
|
|
4942
4965
|
|
|
4966
|
+
function name_form_control(parent, control)
|
|
4967
|
+
{
|
|
4968
|
+
let label = parent.lastElementChild;
|
|
4969
|
+
if(!label || label.tagName !== 'LABEL') {
|
|
4970
|
+
return;
|
|
4971
|
+
}
|
|
4972
|
+
let key = label.getAttribute('data-i18n');
|
|
4973
|
+
if(!key) {
|
|
4974
|
+
return;
|
|
4975
|
+
}
|
|
4976
|
+
control.id = 'g6-form-control-' + (++form_control_seq);
|
|
4977
|
+
label.htmlFor = control.id;
|
|
4978
|
+
control.setAttribute('title', t(key));
|
|
4979
|
+
control.setAttribute('data-i18n-title', key);
|
|
4980
|
+
control.setAttribute('aria-label', t(key));
|
|
4981
|
+
control.setAttribute('data-i18n-aria-label', key);
|
|
4982
|
+
}
|
|
4983
|
+
|
|
4943
4984
|
function create_form_color_input(parent, value, onInput)
|
|
4944
4985
|
{
|
|
4945
4986
|
let input = document.createElement('input');
|
|
@@ -4952,6 +4993,7 @@ function create_form_color_input(parent, value, onInput)
|
|
|
4952
4993
|
if(onInput) {
|
|
4953
4994
|
input.addEventListener('input', onInput);
|
|
4954
4995
|
}
|
|
4996
|
+
name_form_control(parent, input);
|
|
4955
4997
|
parent.appendChild(input);
|
|
4956
4998
|
return input;
|
|
4957
4999
|
}
|
|
@@ -4976,6 +5018,7 @@ function create_form_number_input(parent, value, min, max, onInput)
|
|
|
4976
5018
|
if(onInput) {
|
|
4977
5019
|
input.addEventListener('input', onInput);
|
|
4978
5020
|
}
|
|
5021
|
+
name_form_control(parent, input);
|
|
4979
5022
|
parent.appendChild(input);
|
|
4980
5023
|
return input;
|
|
4981
5024
|
}
|
|
@@ -4991,11 +5034,14 @@ function create_form_select(parent, options, marginBottom)
|
|
|
4991
5034
|
'box-sizing:border-box;margin-bottom:' + (marginBottom || '12px') + ';' +
|
|
4992
5035
|
touch_control_css();
|
|
4993
5036
|
for(let opt of options) {
|
|
5037
|
+
/* `i18n` is the option's key: it carries it, and `value` stays explicit */
|
|
4994
5038
|
let o = document.createElement('option');
|
|
4995
5039
|
o.value = opt.value;
|
|
4996
|
-
o.textContent = opt.
|
|
5040
|
+
o.textContent = t(opt.i18n);
|
|
5041
|
+
o.setAttribute('data-i18n', opt.i18n);
|
|
4997
5042
|
select.appendChild(o);
|
|
4998
5043
|
}
|
|
5044
|
+
name_form_control(parent, select);
|
|
4999
5045
|
parent.appendChild(select);
|
|
5000
5046
|
return select;
|
|
5001
5047
|
}
|
|
@@ -6138,7 +6184,7 @@ function show_port_popover(gobj)
|
|
|
6138
6184
|
|
|
6139
6185
|
create_form_label(popover, 'shape');
|
|
6140
6186
|
let shapeSelect = create_form_select(popover, PORT_SHAPES.map((shape) => {
|
|
6141
|
-
return {value: shape,
|
|
6187
|
+
return {value: shape, i18n: shape};
|
|
6142
6188
|
}));
|
|
6143
6189
|
shapeSelect.value = orig_shape;
|
|
6144
6190
|
shapeSelect.addEventListener('change', preview_port);
|
|
@@ -6148,9 +6194,9 @@ function show_port_popover(gobj)
|
|
|
6148
6194
|
|
|
6149
6195
|
create_form_label(popover, 'apply to');
|
|
6150
6196
|
let scopeSelect = create_form_select(popover, [
|
|
6151
|
-
{value: 'this',
|
|
6152
|
-
{value: 'same_topic',
|
|
6153
|
-
{value: 'all',
|
|
6197
|
+
{value: 'this', i18n: 'this port'},
|
|
6198
|
+
{value: 'same_topic', i18n: 'same port in topic'},
|
|
6199
|
+
{value: 'all', i18n: 'all ports'},
|
|
6154
6200
|
]);
|
|
6155
6201
|
|
|
6156
6202
|
create_form_button_row(popover, [
|
|
@@ -7100,9 +7146,9 @@ function show_edge_popover(gobj)
|
|
|
7100
7146
|
// Apply-to scope
|
|
7101
7147
|
create_form_label(popover, 'apply to');
|
|
7102
7148
|
let scopeSelect = create_form_select(popover, [
|
|
7103
|
-
{ value: 'this',
|
|
7104
|
-
{ value: 'same_type',
|
|
7105
|
-
{ value: 'all',
|
|
7149
|
+
{ value: 'this', i18n: 'this edge' },
|
|
7150
|
+
{ value: 'same_type', i18n: 'same type edges' },
|
|
7151
|
+
{ value: 'all', i18n: 'all edges' },
|
|
7106
7152
|
]);
|
|
7107
7153
|
|
|
7108
7154
|
create_form_button_row(popover, [
|
|
@@ -7339,7 +7385,7 @@ function show_node_popover(gobj)
|
|
|
7339
7385
|
* reshape either way. */
|
|
7340
7386
|
create_form_label(popover, 'shape');
|
|
7341
7387
|
let shapeSelect = create_form_select(popover, NODE_SHAPES.map((shape) => {
|
|
7342
|
-
return {value: shape,
|
|
7388
|
+
return {value: shape, i18n: shape};
|
|
7343
7389
|
}));
|
|
7344
7390
|
shapeSelect.value = nodeData.data && nodeData.data.desc
|
|
7345
7391
|
? node_figure_of(gobj, nodeData.data.desc, nodeData.data.graph_props || {})
|
|
@@ -7349,9 +7395,9 @@ function show_node_popover(gobj)
|
|
|
7349
7395
|
// Apply-to scope
|
|
7350
7396
|
create_form_label(popover, 'apply to');
|
|
7351
7397
|
let scopeSelect = create_form_select(popover, [
|
|
7352
|
-
{ value: 'this',
|
|
7353
|
-
{ value: 'same_topic',
|
|
7354
|
-
{ value: 'all',
|
|
7398
|
+
{ value: 'this', i18n: 'this node' },
|
|
7399
|
+
{ value: 'same_topic', i18n: 'same topic nodes' },
|
|
7400
|
+
{ value: 'all', i18n: 'all nodes' },
|
|
7355
7401
|
]);
|
|
7356
7402
|
|
|
7357
7403
|
create_form_button_row(popover, [
|
|
@@ -8493,7 +8539,11 @@ function show_create_popover(gobj)
|
|
|
8493
8539
|
// Node ID input
|
|
8494
8540
|
let idLabel = document.createElement('div');
|
|
8495
8541
|
idLabel.style.cssText = 'margin-bottom:4px;font-weight:500;';
|
|
8496
|
-
|
|
8542
|
+
let idWord = document.createElement('span');
|
|
8543
|
+
idWord.textContent = t('node id');
|
|
8544
|
+
idWord.setAttribute('data-i18n', 'node id');
|
|
8545
|
+
idLabel.appendChild(idWord);
|
|
8546
|
+
idLabel.appendChild(document.createTextNode(':'));
|
|
8497
8547
|
popover.appendChild(idLabel);
|
|
8498
8548
|
|
|
8499
8549
|
let idInput = document.createElement('input');
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* g6_nodes_tree_i18n_keys.test.js
|
|
3
|
+
*
|
|
4
|
+
* The shape tables of c_g6_nodes_tree.js reach t() as DATA, so the
|
|
5
|
+
* view publishes their keys in G6_NODES_TREE_I18N_KEYS, which a
|
|
6
|
+
* consumer's validate-locales asks the locales for. A shape added to
|
|
7
|
+
* a table and not to the list would render raw in every language.
|
|
8
|
+
*
|
|
9
|
+
* Copyright (c) 2026, ArtGins.
|
|
10
|
+
* All Rights Reserved.
|
|
11
|
+
***********************************************************************/
|
|
12
|
+
import {describe, test, expect} from "vitest";
|
|
13
|
+
import {readFileSync} from "node:fs";
|
|
14
|
+
import {dirname, join} from "node:path";
|
|
15
|
+
import {fileURLToPath} from "node:url";
|
|
16
|
+
|
|
17
|
+
const SRC = readFileSync(join(dirname(fileURLToPath(import.meta.url)), "c_g6_nodes_tree.js"), "utf8");
|
|
18
|
+
|
|
19
|
+
function array_of(name)
|
|
20
|
+
{
|
|
21
|
+
const m = SRC.match(new RegExp(`(?:export\\s+)?const ${name} = \\[([^\\]]*)\\]`));
|
|
22
|
+
expect(m, name).toBeTruthy();
|
|
23
|
+
return [...m[1].matchAll(/'([^']+)'/g)].map((x) => x[1]);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe("c_g6_nodes_tree publishes the keys of its shape tables", () => {
|
|
27
|
+
test("every node and port shape is in G6_NODES_TREE_I18N_KEYS", () => {
|
|
28
|
+
const published = new Set(array_of("G6_NODES_TREE_I18N_KEYS"));
|
|
29
|
+
for(const shape of [...array_of("NODE_SHAPES"), ...array_of("PORT_SHAPES")]) {
|
|
30
|
+
expect(published.has(shape), shape).toBe(true);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("a form control is named by the label before it, and an option carries its key", () => {
|
|
35
|
+
expect(SRC).toMatch(/function name_form_control\(parent, control\)/);
|
|
36
|
+
expect(SRC).toMatch(/o\.setAttribute\('data-i18n', opt\.i18n\)/);
|
|
37
|
+
expect(SRC).not.toMatch(/label: t\('/);
|
|
38
|
+
});
|
|
39
|
+
});
|
package/src/yui_dev.js
CHANGED
|
@@ -71,36 +71,34 @@ const TRAFFIC_TS_FIELDS = {
|
|
|
71
71
|
* repainted from it by `refresh_dev_chrome()` on every toggle, so a
|
|
72
72
|
* label translated once at build time would come back in English at
|
|
73
73
|
* the first click. */
|
|
74
|
-
/*
|
|
75
|
-
* `
|
|
76
|
-
* `
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
* wattyzer and the yunovatios GUIs carry them; check a new consumer
|
|
103
|
-
* against this list, and confirm it by DUMPING the window. */
|
|
74
|
+
/* The keys of this window that reach t() as VARIABLES: the table below,
|
|
75
|
+
* the `grp` / `mk_view` / `mk_expand` / `mk_dir` / `mk_out` helpers and
|
|
76
|
+
* `OUT_TITLES` pass their key by name, so a scan of `t("...")` sees none of
|
|
77
|
+
* them. They are published HERE, as data: a consumer's `validate-locales`
|
|
78
|
+
* (gobj-ui 7.26.27+) reads an exported `*_I18N_KEYS` array of every module
|
|
79
|
+
* the app mounts and asks the locales for each key -- it used to be a
|
|
80
|
+
* comment each consumer copied by hand, and yunomusica lacked 35 of them
|
|
81
|
+
* (REV-CLOUD V19.4.2). A key added to the window goes in this list. */
|
|
82
|
+
export const YUI_DEV_I18N_KEYS = [
|
|
83
|
+
"automata", "both", "browser console only", "clear",
|
|
84
|
+
"clear captured traffic", "collapsed", "console", "copied", "copy",
|
|
85
|
+
"copy visible traffic to clipboard", "creation", "data",
|
|
86
|
+
"dev window and browser console", "dev window only", "developer",
|
|
87
|
+
"clear the filter", "errors", "expand", "expanded",
|
|
88
|
+
"filter events / payload", "find", "i18n", "i18next debug output",
|
|
89
|
+
"incoming", "log", "machine trace shape", "metadata", "outgoing",
|
|
90
|
+
"output", "payload", "periodic", "schema", "show",
|
|
91
|
+
"show the payload of the traces",
|
|
92
|
+
"show this section in the expanded view", "simple mach", "start / stop",
|
|
93
|
+
"subscriptions", "trace every event of every automaton",
|
|
94
|
+
"trace subscriptions and publications",
|
|
95
|
+
"trace the creation and destruction of gobjs",
|
|
96
|
+
"trace the messages to and from the backend",
|
|
97
|
+
"trace the periodic timer event", "trace the start and stop of gobjs",
|
|
98
|
+
"traces", "traffic", "traffic payload folded",
|
|
99
|
+
"traffic payload laid out", "view", "window", "yuno monitor"
|
|
100
|
+
];
|
|
101
|
+
|
|
104
102
|
const TRACE_DEFS = [
|
|
105
103
|
["automata", "automata", "trace every event of every automaton",
|
|
106
104
|
automata_state, toggle_automata],
|