gulp-mu-gulp-api 0.2.1 → 0.3.1
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 +78 -8
- package/package.json +5 -2
- package/src/i18x.mjs +306 -0
- package/src/index.mjs +215 -19
package/README.md
CHANGED
|
@@ -20,10 +20,19 @@ The module is deliberately **dependency-free** and knows nothing about µGulp™
|
|
|
20
20
|
npm install gulp-mu-gulp-api
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
+
### 0.3.0
|
|
24
|
+
|
|
25
|
+
- **Added** integrated i18x console layer: `Log`, `Warn`, `LogError`, `Translate`, `GetLid`, `SetLid`, `GetTimeZone`, `DateInTimeZone`, `InstallStringExtensions` — translations live in the consumer project's `i18x/gulp/<lid>.json`; language follows `MICROGULP_LANG` (set by the µGulp dashboard) with locale fallback.
|
|
26
|
+
- **Added** µLib-compatible `String.prototype.i18xTrans()` / `.i18xRegister()` (installed automatically on import).
|
|
27
|
+
- **Removed** `µMeta()` / `MicroGulpMeta` — embed the context tag in the source phrase and use `.i18xRegister()` instead (see below).
|
|
28
|
+
|
|
23
29
|
## Usage
|
|
24
30
|
|
|
25
31
|
```javascript
|
|
26
|
-
import { ReportProgress, CreateProgress, PlaySignal, Speak, RequestTextInput, RequestColorInput, RequestFontInput, RequestSelectInput, RequestMultiSelectInput, RequestForm, IsMicroGulp, IsµGulp,
|
|
32
|
+
import { ReportProgress, CreateProgress, PlaySignal, Speak, RequestTextInput, RequestColorInput, RequestFontInput, RequestSelectInput, RequestMultiSelectInput, RequestForm, IsMicroGulp, IsµGulp, InstallStringExtensions } from 'gulp-mu-gulp-api';
|
|
33
|
+
|
|
34
|
+
// Enables "phrase<context=\"…\"/>".i18xRegister()/.i18xTrans() on strings.
|
|
35
|
+
InstallStringExtensions();
|
|
27
36
|
|
|
28
37
|
export async function BUILD_THEME() {
|
|
29
38
|
// single inputs
|
|
@@ -50,8 +59,8 @@ export async function BUILD_THEME() {
|
|
|
50
59
|
PlaySignal('success');
|
|
51
60
|
Speak('Theme build finished.');
|
|
52
61
|
}
|
|
53
|
-
BUILD_THEME.µDisplayName =
|
|
54
|
-
BUILD_THEME.µDescription =
|
|
62
|
+
BUILD_THEME.µDisplayName = 'Build Theme<context="µDisplayName"/>'.i18xRegister();
|
|
63
|
+
BUILD_THEME.µDescription = 'Compiles the Carbon/Neon dashboard skin.<context="µDescription"/>'.i18xRegister();
|
|
55
64
|
```
|
|
56
65
|
|
|
57
66
|
Complete forms in one go:
|
|
@@ -75,7 +84,10 @@ let values = await RequestForm({
|
|
|
75
84
|
| Function | Purpose |
|
|
76
85
|
| :--- | :--- |
|
|
77
86
|
| `IsMicroGulp()` / `IsµGulp()` | `true` when the task runs under µGulp™ with an attached dashboard (`IsUGulp` is a deprecated alias) |
|
|
78
|
-
|
|
|
87
|
+
| `InstallStringExtensions()` | Installs µLib-compatible `String.prototype.i18xTrans()`/`i18xRegister()` (auto-run on import) |
|
|
88
|
+
| `Log(text, values?)` / `Warn` / `LogError` | Localized `console.*` via i18x (`<context="task log"/>` etc.) |
|
|
89
|
+
| `Translate(text, values?)` | Returns the translated string without logging |
|
|
90
|
+
| `GetLid()` / `SetLid(lid?)` | Active language id; `SetLid` clears to automatic when omitted |
|
|
79
91
|
| `ReportProgress(value, label?)` | Report progress `0..1` to the task progress bar |
|
|
80
92
|
| `CreateProgress(label?)` | Reporter object with `Update(value, stepLabel?)` and `Done()` |
|
|
81
93
|
| `PlaySignal(signal?)` | Acoustic signal in the dashboard: `'success'` \| `'error'` \| `'attention'` (default) |
|
|
@@ -87,6 +99,29 @@ let values = await RequestForm({
|
|
|
87
99
|
| `RequestSelectInput(options)` | Single selection from a fixed option list |
|
|
88
100
|
| `RequestMultiSelectInput(options)` | Multi selection (checkbox group), always resolves to a `string[]` |
|
|
89
101
|
|
|
102
|
+
### Localized task metadata (`.i18xRegister()` + placeholders)
|
|
103
|
+
|
|
104
|
+
Write the i18xe context tag **into** the source phrase and mark it with `.i18xRegister()` — that marker is what the `i18xe-sync` tooling scans for, and at runtime it returns the phrase unchanged (the classic gulp CLI keeps seeing a plain string). Use i18xe placeholder tags in the source text. The dashboard substitutes a standard property set when translating:
|
|
105
|
+
|
|
106
|
+
| Placeholder | Source |
|
|
107
|
+
| :--- | :--- |
|
|
108
|
+
| `version` | `µI18nContext.version` export, else `package.json` |
|
|
109
|
+
| `project` | `µI18nContext.project` export, else `package.json` `name` |
|
|
110
|
+
| `workspace` | workspace folder name |
|
|
111
|
+
| `source` | gulpfile path relative to the workspace |
|
|
112
|
+
| `gulpfile` | gulpfile file name |
|
|
113
|
+
| `task` | technical export name |
|
|
114
|
+
| `taskId` | composite id `source::task` |
|
|
115
|
+
|
|
116
|
+
Export defaults from the gulpfile (overrides `package.json`):
|
|
117
|
+
|
|
118
|
+
```javascript
|
|
119
|
+
export const µI18nContext = { version: '1.13', project: 'eMP' };
|
|
120
|
+
|
|
121
|
+
MAKE_BUILDS.µDisplayName = 'Make Build V<version/><context="µDisplayName"/>'.i18xRegister();
|
|
122
|
+
// => "Make Build V1.13" in the dashboard (translatable per language)
|
|
123
|
+
```
|
|
124
|
+
|
|
90
125
|
### Field types (for `RequestForm`)
|
|
91
126
|
|
|
92
127
|
A field descriptor is `{ id, type, label, default?, options?, validate?, min?, max?, step?, rows?, placeholder? }`.
|
|
@@ -143,10 +178,19 @@ Das Modul ist bewusst **abhängigkeitsfrei** und kennt keine µGulp™-Interna:
|
|
|
143
178
|
npm install gulp-mu-gulp-api
|
|
144
179
|
```
|
|
145
180
|
|
|
181
|
+
### 0.3.0
|
|
182
|
+
|
|
183
|
+
- **Neu** integrierte i18x-Konsolenschicht: `Log`, `Warn`, `LogError`, `Translate`, `GetLid`, `SetLid`, `GetTimeZone`, `DateInTimeZone`, `InstallStringExtensions` — Übersetzungen liegen im Verbraucherprojekt unter `i18x/gulp/<lid>.json`; die Sprache folgt `MICROGULP_LANG` (vom µGulp-Dashboard gesetzt) mit Locale-Fallback.
|
|
184
|
+
- **Neu** µLib-kompatible `String.prototype.i18xTrans()` / `.i18xRegister()` (werden beim Import automatisch installiert).
|
|
185
|
+
- **Entfernt** `µMeta()` / `MicroGulpMeta` — Kontext-Tag in die Textphrase schreiben und `.i18xRegister()` verwenden (siehe unten).
|
|
186
|
+
|
|
146
187
|
## Verwendung
|
|
147
188
|
|
|
148
189
|
```javascript
|
|
149
|
-
import { ReportProgress, CreateProgress, PlaySignal, Speak, RequestTextInput, RequestColorInput, RequestFontInput, RequestSelectInput, RequestMultiSelectInput, RequestForm, IsMicroGulp, IsµGulp,
|
|
190
|
+
import { ReportProgress, CreateProgress, PlaySignal, Speak, RequestTextInput, RequestColorInput, RequestFontInput, RequestSelectInput, RequestMultiSelectInput, RequestForm, IsMicroGulp, IsµGulp, InstallStringExtensions } from 'gulp-mu-gulp-api';
|
|
191
|
+
|
|
192
|
+
// Enables "phrase<context=\"…\"/>".i18xRegister()/.i18xTrans() on strings.
|
|
193
|
+
InstallStringExtensions();
|
|
150
194
|
|
|
151
195
|
export async function BUILD_THEME() {
|
|
152
196
|
// Einzeleingaben
|
|
@@ -173,8 +217,8 @@ export async function BUILD_THEME() {
|
|
|
173
217
|
PlaySignal('success');
|
|
174
218
|
Speak('Theme build finished.');
|
|
175
219
|
}
|
|
176
|
-
BUILD_THEME.µDisplayName =
|
|
177
|
-
BUILD_THEME.µDescription =
|
|
220
|
+
BUILD_THEME.µDisplayName = 'Build Theme<context="µDisplayName"/>'.i18xRegister();
|
|
221
|
+
BUILD_THEME.µDescription = 'Kompiliert den Carbon/Neon-Dashboard-Skin.<context="µDescription"/>'.i18xRegister();
|
|
178
222
|
```
|
|
179
223
|
|
|
180
224
|
Komplette Formulare in einem Rutsch:
|
|
@@ -198,7 +242,10 @@ let values = await RequestForm({
|
|
|
198
242
|
| Funktion | Zweck |
|
|
199
243
|
| :--- | :--- |
|
|
200
244
|
| `IsMicroGulp()` / `IsµGulp()` | `true`, wenn der Task unter µGulp™ mit angebundenem Dashboard läuft (`IsUGulp` ist ein veralteter Alias) |
|
|
201
|
-
|
|
|
245
|
+
| `InstallStringExtensions()` | Installiert µLib-kompatible `String.prototype.i18xTrans()`/`i18xRegister()` (läuft beim Import automatisch) |
|
|
246
|
+
| `Log(text, values?)` / `Warn` / `LogError` | Lokalisierte `console.*`-Ausgabe über i18x (`<context="task log"/>` usw.) |
|
|
247
|
+
| `Translate(text, values?)` | Gibt den übersetzten String zurück, ohne zu loggen |
|
|
248
|
+
| `GetLid()` / `SetLid(lid?)` | Aktive Sprach-ID; `SetLid` ohne Argument = automatische Auflösung |
|
|
202
249
|
| `ReportProgress(value, label?)` | Fortschritt `0..1` an die Task-Progressbar melden |
|
|
203
250
|
| `CreateProgress(label?)` | Reporter-Objekt mit `Update(value, stepLabel?)` und `Done()` |
|
|
204
251
|
| `PlaySignal(signal?)` | Akustisches Signal im Dashboard: `'success'` \| `'error'` \| `'attention'` (Default) |
|
|
@@ -210,6 +257,29 @@ let values = await RequestForm({
|
|
|
210
257
|
| `RequestSelectInput(options)` | Einzelauswahl aus fester Optionsliste |
|
|
211
258
|
| `RequestMultiSelectInput(options)` | Mehrfachauswahl (Checkbox-Gruppe), löst immer mit `string[]` auf |
|
|
212
259
|
|
|
260
|
+
### Lokalisierte Task-Metadaten (`.i18xRegister()` + Platzhalter)
|
|
261
|
+
|
|
262
|
+
Den i18xe-Kontext-Tag **in** die Textphrase schreiben und mit `.i18xRegister()` markieren — genau diese Markierung scannt das `i18xe-sync`-Tooling, zur Laufzeit gibt sie den Text unverändert zurück (die klassische gulp-CLI sieht weiterhin einen einfachen String). i18xe-Platzhalter im Quelltext verwenden. Beim Übersetzen setzt das Dashboard einen Standardsatz ein:
|
|
263
|
+
|
|
264
|
+
| Platzhalter | Quelle |
|
|
265
|
+
| :--- | :--- |
|
|
266
|
+
| `version` | `µI18nContext.version`-Export, sonst `package.json` |
|
|
267
|
+
| `project` | `µI18nContext.project`-Export, sonst `package.json` `name` |
|
|
268
|
+
| `workspace` | Workspace-Ordnername |
|
|
269
|
+
| `source` | Gulpfile-Pfad relativ zum Workspace |
|
|
270
|
+
| `gulpfile` | Gulpfile-Dateiname |
|
|
271
|
+
| `task` | technischer Exportname |
|
|
272
|
+
| `taskId` | zusammengesetzte Id `source::task` |
|
|
273
|
+
|
|
274
|
+
Defaults im Gulpfile exportieren (`package.json` wird überschrieben):
|
|
275
|
+
|
|
276
|
+
```javascript
|
|
277
|
+
export const µI18nContext = { version: '1.13', project: 'eMP' };
|
|
278
|
+
|
|
279
|
+
MAKE_BUILDS.µDisplayName = 'Make Build V<version/><context="µDisplayName"/>'.i18xRegister();
|
|
280
|
+
// => „Make Build V1.13“ im Dashboard (pro Sprache übersetzbar)
|
|
281
|
+
```
|
|
282
|
+
|
|
213
283
|
### Feldtypen (für `RequestForm`)
|
|
214
284
|
|
|
215
285
|
Ein Feld-Deskriptor ist `{ id, type, label, default?, options?, validate?, min?, max?, step?, rows?, placeholder? }`.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gulp-mu-gulp-api",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Public task API for the µGulp orchestrator: progress reporting, sound signals, speech output and interactive UI inputs (text, password, number, textarea, color, font, select, radio, multi-select checkbox, range slider, date/time, file) from within gulp tasks — with graceful CLI fallbacks when running without µGulp.",
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "Public task API for the µGulp orchestrator: progress reporting, sound signals, speech output, localized console output (i18x) and interactive UI inputs (text, password, number, textarea, color, font, select, radio, multi-select checkbox, range slider, date/time, file) from within gulp tasks — with graceful CLI fallbacks when running without µGulp.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.mjs",
|
|
7
7
|
"exports": {
|
|
@@ -22,6 +22,9 @@
|
|
|
22
22
|
"interactive",
|
|
23
23
|
"sound",
|
|
24
24
|
"speech",
|
|
25
|
+
"i18n",
|
|
26
|
+
"i18x",
|
|
27
|
+
"localization",
|
|
25
28
|
"prompt",
|
|
26
29
|
"multiselect",
|
|
27
30
|
"form",
|
package/src/i18x.mjs
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
// ===========================================
|
|
2
|
+
// gulp-mu-gulp-api — i18x console output for gulp tasks
|
|
3
|
+
// © 2026 Meinolf Amekudzi
|
|
4
|
+
// (published under MIT license)
|
|
5
|
+
// ===========================================
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Localized console output for gulp tasks, following the i18xe notation:
|
|
9
|
+
* source strings are American English and carry a context tag
|
|
10
|
+
* (`<context="task log"/>` …); translations live in the *consumer project's*
|
|
11
|
+
* own `i18x/gulp/<lid>.json` files and `<name/>` placeholders are filled at
|
|
12
|
+
* render time. Any project using µGulp drops an `i18x/gulp` directory next to
|
|
13
|
+
* its gulpfile and its build output is localized — no coupling to µGulp
|
|
14
|
+
* internals, works from the plain `gulp` CLI as well.
|
|
15
|
+
*
|
|
16
|
+
* The active language resolves from `MICROGULP_LANG` (the µGulp engine
|
|
17
|
+
* forwards the dashboard language here), then the host OS locale, then the
|
|
18
|
+
* en-US source. Regional/script variants fall back through a compact map
|
|
19
|
+
* (`de-AT` → `de-DE`, `zh-TW` → `zh-CN`, `en-GB` → `en-US`, …) onto whichever
|
|
20
|
+
* languages the project actually ships.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* import { Log, Warn } from 'gulp-mu-gulp-api';
|
|
24
|
+
* export async function BUILD() {
|
|
25
|
+
* Log('Compiling styles\u2026<context="task log"/>');
|
|
26
|
+
* Log('Chunk <n/>/<total/> written<context="task log"/>', { n: 3, total: 5 });
|
|
27
|
+
* }
|
|
28
|
+
*
|
|
29
|
+
* @module gulp-mu-gulp-api/i18x
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
33
|
+
import { join } from 'node:path';
|
|
34
|
+
|
|
35
|
+
const DEFAULT_LID = 'en-US';
|
|
36
|
+
|
|
37
|
+
// Compact locale fallback tables (subset of the µLib i18x LIDS_MAPS /
|
|
38
|
+
// LIDS_MAIN_CULTURES). The resolver also falls back on the bare language
|
|
39
|
+
// prefix, so only the non-obvious chains need listing.
|
|
40
|
+
const LIDS_MAPS = {
|
|
41
|
+
'zh-CHS': ['zh-CN'], 'zh-CHT': ['zh-CHS', 'zh-CN'], 'zh-HK': ['zh-CHS', 'zh-CN'],
|
|
42
|
+
'zh-Hans': ['zh-CHS', 'zh-CN'], 'zh-Hant': ['zh-CHT', 'zh-CHS', 'zh-CN'],
|
|
43
|
+
'zh-MO': ['zh-CHS', 'zh-CN'], 'zh-SG': ['zh-CHS', 'zh-CN'], 'zh-TW': ['zh-CHT', 'zh-CHS', 'zh-CN'],
|
|
44
|
+
'de-CH': ['de-DE'], 'de-AT': ['de-DE'], 'de-LU': ['de-DE'], 'de-LI': ['de-DE'],
|
|
45
|
+
'en-GB': ['en-US'], 'en-AU': ['en-GB', 'en-US'], 'en-CA': ['en-GB', 'en-US'],
|
|
46
|
+
};
|
|
47
|
+
const LIDS_MAIN_CULTURES = {
|
|
48
|
+
zh: 'CN', de: 'DE', en: 'GB', fr: 'FR', es: 'ES', it: 'IT', pt: 'PT',
|
|
49
|
+
nl: 'NL', ru: 'RU', ja: 'JP', ko: 'KR',
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
let dictionaryCache = new Map();
|
|
53
|
+
let availableLids = null;
|
|
54
|
+
let activeLid = null;
|
|
55
|
+
const timeZoneFormatters = {};
|
|
56
|
+
|
|
57
|
+
/** @returns {string} IANA timezone from MICROGULP_TZ, then ECMAScript Intl */
|
|
58
|
+
export function GetTimeZone() {
|
|
59
|
+
if (process.env.MICROGULP_TZ) return process.env.MICROGULP_TZ;
|
|
60
|
+
try {
|
|
61
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
|
|
62
|
+
} catch {
|
|
63
|
+
return 'UTC';
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Wall-clock Date in an IANA timezone (µLib-compatible). */
|
|
68
|
+
export function DateInTimeZone(_date, _timeZone) {
|
|
69
|
+
let tz = _timeZone || GetTimeZone();
|
|
70
|
+
try {
|
|
71
|
+
let formatter = timeZoneFormatters[tz];
|
|
72
|
+
if (formatter === undefined) {
|
|
73
|
+
formatter = new Intl.DateTimeFormat('en-US', {
|
|
74
|
+
timeZone: tz, hour12: false,
|
|
75
|
+
year: 'numeric', month: '2-digit', day: '2-digit',
|
|
76
|
+
hour: '2-digit', minute: '2-digit', second: '2-digit',
|
|
77
|
+
});
|
|
78
|
+
timeZoneFormatters[tz] = formatter;
|
|
79
|
+
}
|
|
80
|
+
let values = {};
|
|
81
|
+
for (let part of formatter.formatToParts(_date)) values[part.type] = part.value;
|
|
82
|
+
return new Date(
|
|
83
|
+
parseInt(values.year, 10), parseInt(values.month, 10) - 1, parseInt(values.day, 10),
|
|
84
|
+
parseInt(values.hour, 10) % 24, parseInt(values.minute, 10), parseInt(values.second, 10),
|
|
85
|
+
_date.getMilliseconds(),
|
|
86
|
+
);
|
|
87
|
+
} catch {
|
|
88
|
+
return _date;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Normalizes casing: language lower, 2-letter region upper, 4-letter script Title. */
|
|
93
|
+
function _NormalizeLid(_lid) {
|
|
94
|
+
if (!_lid) return '';
|
|
95
|
+
let parts = String(_lid).replace(/_/g, '-').trim().split('-').filter(Boolean);
|
|
96
|
+
if (parts.length === 0) return '';
|
|
97
|
+
return parts.map((_part, _index) => {
|
|
98
|
+
if (_index === 0) return _part.toLowerCase();
|
|
99
|
+
if (_part.length === 2) return _part.toUpperCase();
|
|
100
|
+
if (_part.length === 4) return _part.charAt(0).toUpperCase() + _part.slice(1).toLowerCase();
|
|
101
|
+
return _part;
|
|
102
|
+
}).join('-');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Picks the closest shipped language for a wanted locale: exact
|
|
107
|
+
* (case-insensitive) → LIDS_MAPS chain → main culture → language prefix →
|
|
108
|
+
* en-US default → first available.
|
|
109
|
+
*/
|
|
110
|
+
function _BestAvailableLid(_wantedLid, _available) {
|
|
111
|
+
if (_available.length === 0) return DEFAULT_LID;
|
|
112
|
+
let wanted = _NormalizeLid(_wantedLid);
|
|
113
|
+
if (!wanted) return _available.includes(DEFAULT_LID) ? DEFAULT_LID : _available[0];
|
|
114
|
+
if (_available.includes(wanted)) return wanted;
|
|
115
|
+
let caseInsensitive = _available.find((_lid) => _lid.toLowerCase() === wanted.toLowerCase());
|
|
116
|
+
if (caseInsensitive) return caseInsensitive;
|
|
117
|
+
if (Object.prototype.hasOwnProperty.call(LIDS_MAPS, wanted)) {
|
|
118
|
+
for (let candidate of LIDS_MAPS[wanted]) if (_available.includes(candidate)) return candidate;
|
|
119
|
+
}
|
|
120
|
+
let language = wanted.split('-')[0];
|
|
121
|
+
if (Object.prototype.hasOwnProperty.call(LIDS_MAIN_CULTURES, language)) {
|
|
122
|
+
let mainCulture = language + '-' + LIDS_MAIN_CULTURES[language];
|
|
123
|
+
if (_available.includes(mainCulture)) return mainCulture;
|
|
124
|
+
}
|
|
125
|
+
let byPrefix = _available.find((_lid) => _lid.split('-')[0].toLowerCase() === language);
|
|
126
|
+
if (byPrefix) return byPrefix;
|
|
127
|
+
return _available.includes(DEFAULT_LID) ? DEFAULT_LID : _available[0];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Directory holding the project dictionaries: env override or `<cwd>/i18x/gulp`. */
|
|
131
|
+
function _DictionaryDir() {
|
|
132
|
+
return process.env.MICROGULP_I18X_DIR || join(process.cwd(), 'i18x', 'gulp');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Languages shipped by the project (en-US source is always available). */
|
|
136
|
+
function _AvailableLids() {
|
|
137
|
+
if (availableLids) return availableLids;
|
|
138
|
+
let lids = [DEFAULT_LID];
|
|
139
|
+
try {
|
|
140
|
+
for (let entry of readdirSync(_DictionaryDir())) {
|
|
141
|
+
if (!entry.toLowerCase().endsWith('.json')) continue;
|
|
142
|
+
let lid = entry.slice(0, -5);
|
|
143
|
+
if (!lids.includes(lid)) lids.push(lid);
|
|
144
|
+
}
|
|
145
|
+
} catch {
|
|
146
|
+
// No dictionary directory — only the en-US source is available.
|
|
147
|
+
}
|
|
148
|
+
availableLids = lids;
|
|
149
|
+
return availableLids;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Resolves the active language from MICROGULP_LANG, OS locale, then default. */
|
|
153
|
+
function _ResolveLid() {
|
|
154
|
+
let wanted = process.env.MICROGULP_LANG;
|
|
155
|
+
if (!wanted) {
|
|
156
|
+
try {
|
|
157
|
+
wanted = Intl.DateTimeFormat().resolvedOptions().locale;
|
|
158
|
+
} catch {
|
|
159
|
+
// Intl is always present in Node 22, but stay defensive.
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return _BestAvailableLid(wanted || process.env.LANG || DEFAULT_LID, _AvailableLids());
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** @returns {string} the active localization id (cached after first use) */
|
|
166
|
+
export function GetLid() {
|
|
167
|
+
if (!activeLid) activeLid = _ResolveLid();
|
|
168
|
+
return activeLid;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Overrides the active language explicitly and clears caches so the next
|
|
173
|
+
* translation reloads. Pass a falsy value to reset to automatic resolution.
|
|
174
|
+
* @param {string} [_lid]
|
|
175
|
+
*/
|
|
176
|
+
export function SetLid(_lid) {
|
|
177
|
+
activeLid = _lid ? _BestAvailableLid(_lid, _AvailableLids()) : null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Loads and caches `<dir>/<lid>.json`; a missing/broken file yields source text. */
|
|
181
|
+
function _LoadDictionary(_lid) {
|
|
182
|
+
if (dictionaryCache.has(_lid)) return dictionaryCache.get(_lid);
|
|
183
|
+
let dictionary = {};
|
|
184
|
+
try {
|
|
185
|
+
let filePath = join(_DictionaryDir(), _lid + '.json');
|
|
186
|
+
if (existsSync(filePath)) dictionary = JSON.parse(readFileSync(filePath, 'utf8'));
|
|
187
|
+
} catch {
|
|
188
|
+
// A malformed dictionary must not break the build — fall back to source.
|
|
189
|
+
}
|
|
190
|
+
dictionaryCache.set(_lid, dictionary);
|
|
191
|
+
return dictionary;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Substitutes `<name/>` placeholders; honors `format="…"` for int and clock times. */
|
|
195
|
+
function _ApplyPlaceholders(_text, _values) {
|
|
196
|
+
let text = String(_text);
|
|
197
|
+
for (let key of Object.keys(_values ?? {})) {
|
|
198
|
+
let pattern = new RegExp('<' + key + '(?:\\s+format="([^"]*)")?\\s*/>', 'g');
|
|
199
|
+
text = text.replace(pattern, (_match, _formatName) => {
|
|
200
|
+
if (_formatName === 'int') return String(Math.round(Number(_values[key]) || 0));
|
|
201
|
+
if (_formatName === 'stdtime' || _formatName === 'stddatetime') {
|
|
202
|
+
let date = new Date(Number(_values[key]) || 0);
|
|
203
|
+
let lid = GetLid();
|
|
204
|
+
try {
|
|
205
|
+
return new Intl.DateTimeFormat(lid, {
|
|
206
|
+
...( _formatName === 'stddatetime'
|
|
207
|
+
? { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }
|
|
208
|
+
: { hour: '2-digit', minute: '2-digit' }),
|
|
209
|
+
timeZone: GetTimeZone(),
|
|
210
|
+
}).format(date);
|
|
211
|
+
} catch {
|
|
212
|
+
return date.toISOString();
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return String(_values[key]);
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
return text.replace(/<[^>]*>/g, '');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Translates a source phrase (including its context tag) into the active
|
|
223
|
+
* language and resolves placeholders. Unknown phrases fall back to the en-US
|
|
224
|
+
* source text with its tags stripped.
|
|
225
|
+
* @param {string} _text source phrase, e.g. 'Build ready<context="task log"/>'
|
|
226
|
+
* @param {object} [_values] placeholder values for `<name/>` tags
|
|
227
|
+
* @returns {string}
|
|
228
|
+
*/
|
|
229
|
+
export function Translate(_text, _values) {
|
|
230
|
+
if (_text == null) return '';
|
|
231
|
+
let source = String(_text);
|
|
232
|
+
let lid = GetLid();
|
|
233
|
+
let dictionary = lid === DEFAULT_LID ? null : _LoadDictionary(lid);
|
|
234
|
+
let translated = (dictionary && Object.prototype.hasOwnProperty.call(dictionary, source)) ? dictionary[source] : source;
|
|
235
|
+
return _ApplyPlaceholders(translated, _values);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* console.log through i18x. Prefer the `<context="task log"/>` tag.
|
|
240
|
+
* @param {string} _text source phrase
|
|
241
|
+
* @param {object} [_values]
|
|
242
|
+
*/
|
|
243
|
+
export function Log(_text, _values) {
|
|
244
|
+
console.log(Translate(_text, _values));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* console.warn through i18x. Prefer the `<context="task warning"/>` tag.
|
|
249
|
+
* @param {string} _text source phrase
|
|
250
|
+
* @param {object} [_values]
|
|
251
|
+
*/
|
|
252
|
+
export function Warn(_text, _values) {
|
|
253
|
+
console.warn(Translate(_text, _values));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* console.error through i18x. Prefer the `<context="task error"/>` tag.
|
|
258
|
+
* Named LogError to avoid shadowing the global Error constructor.
|
|
259
|
+
* @param {string} _text source phrase
|
|
260
|
+
* @param {object} [_values]
|
|
261
|
+
*/
|
|
262
|
+
export function LogError(_text, _values) {
|
|
263
|
+
console.error(Translate(_text, _values));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* µLib-compatible String-prototype API for gulpfiles that prefer the
|
|
268
|
+
* `"text<context=\"…\"/>".i18xTrans({ … })` ergonomics over the Log/Warn
|
|
269
|
+
* helpers. Installs `I18xTrans`/`i18xTrans` and `I18xRegister`/`i18xRegister`
|
|
270
|
+
* — but never overwrites an implementation already provided by µLib (µLib
|
|
271
|
+
* wins when both are loaded). Called automatically on import; also exported
|
|
272
|
+
* for explicit control.
|
|
273
|
+
*/
|
|
274
|
+
export function InstallStringExtensions() {
|
|
275
|
+
if (typeof String.prototype.I18xTrans !== 'function') {
|
|
276
|
+
Object.defineProperty(String.prototype, 'I18xTrans', {
|
|
277
|
+
value: function (_values) { return Translate(String(this), _values); },
|
|
278
|
+
writable: true, configurable: true,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
if (typeof String.prototype.i18xTrans !== 'function') {
|
|
282
|
+
Object.defineProperty(String.prototype, 'i18xTrans', {
|
|
283
|
+
value: String.prototype.I18xTrans,
|
|
284
|
+
writable: true, configurable: true,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
if (typeof String.prototype.I18xRegister !== 'function') {
|
|
288
|
+
Object.defineProperty(String.prototype, 'I18xRegister', {
|
|
289
|
+
value: function () { return String(this); },
|
|
290
|
+
writable: true, configurable: true,
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
if (typeof String.prototype.i18xRegister !== 'function') {
|
|
294
|
+
Object.defineProperty(String.prototype, 'i18xRegister', {
|
|
295
|
+
value: String.prototype.I18xRegister,
|
|
296
|
+
writable: true, configurable: true,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
InstallStringExtensions();
|
|
302
|
+
|
|
303
|
+
export default {
|
|
304
|
+
GetLid, SetLid, GetTimeZone, DateInTimeZone,
|
|
305
|
+
Translate, Log, Warn, LogError, InstallStringExtensions,
|
|
306
|
+
};
|
package/src/index.mjs
CHANGED
|
@@ -28,6 +28,11 @@
|
|
|
28
28
|
* @module gulp-mu-gulp-api
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
|
+
// Localized console output (i18x). Re-exported so tasks import everything
|
|
32
|
+
// from one place: `import { Log, Warn, ReportProgress } from 'gulp-mu-gulp-api'`.
|
|
33
|
+
export { Log, Warn, LogError, Translate, GetLid, SetLid, GetTimeZone, DateInTimeZone, InstallStringExtensions } from './i18x.mjs';
|
|
34
|
+
import * as I18x from './i18x.mjs';
|
|
35
|
+
|
|
31
36
|
let uiRequestCounter = 0;
|
|
32
37
|
let ipcListenerAttached = false;
|
|
33
38
|
let lastLoggedPercent = -1;
|
|
@@ -47,25 +52,20 @@ export const IsµGulp = IsMicroGulp;
|
|
|
47
52
|
/** @deprecated Use {@link IsMicroGulp} or {@link IsµGulp}. */
|
|
48
53
|
export const IsUGulp = IsMicroGulp;
|
|
49
54
|
|
|
50
|
-
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* i18xe-
|
|
55
|
-
*
|
|
55
|
+
/*
|
|
56
|
+
* Task metadata (µDisplayName, µDescription, µTooltip, µGroup) is localized
|
|
57
|
+
* the same way as any other i18x phrase: with the context tag written *into*
|
|
58
|
+
* the source phrase and a String.prototype.i18xRegister() marker so the
|
|
59
|
+
* i18xe-sync tooling extracts it and the dashboard can translate it later.
|
|
60
|
+
*
|
|
61
|
+
* MAKE_BUILDS.µDisplayName =
|
|
62
|
+
* 'Make Build V<version/><context="µDisplayName"/>'.i18xRegister();
|
|
56
63
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
64
|
+
* i18xRegister() returns the phrase unchanged (the i18x key), so the classic
|
|
65
|
+
* gulp CLI keeps seeing a plain string while the dashboard translates it via
|
|
66
|
+
* the merged project dictionary. The prototype is installed by
|
|
67
|
+
* InstallStringExtensions() (auto-run on import of this module).
|
|
60
68
|
*/
|
|
61
|
-
export function µMeta(_text, _contextProperty) {
|
|
62
|
-
let text = String(_text ?? '').trim();
|
|
63
|
-
if (!text || text.includes('<context="')) return text;
|
|
64
|
-
return text + '<context="' + _contextProperty + '"/>';
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** ASCII export alias for {@link µMeta}. */
|
|
68
|
-
export const MicroGulpMeta = µMeta;
|
|
69
69
|
|
|
70
70
|
// -------------------------------------------------
|
|
71
71
|
// progress
|
|
@@ -111,6 +111,108 @@ export function CreateProgress(_label) {
|
|
|
111
111
|
};
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
// -------------------------------------------------
|
|
115
|
+
// structured log output (table / tree)
|
|
116
|
+
// -------------------------------------------------
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Sends a table to the dashboard log pane. The payload is JSON:
|
|
120
|
+
* `{ columns: string[] | {id, label}[], rows: (string|number)[][] | object[], hyphenate?: boolean }`.
|
|
121
|
+
* CLI fallback: a simple ASCII table on stdout.
|
|
122
|
+
*
|
|
123
|
+
* @param {object} _spec table descriptor
|
|
124
|
+
*/
|
|
125
|
+
export function LogTable(_spec) {
|
|
126
|
+
let payload = _NormalizeTableSpec(_spec);
|
|
127
|
+
if (IsMicroGulp()) {
|
|
128
|
+
process.send({ type: 'structured-log', format: 'table', payload });
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
console.log(_AsciiTable(payload));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Sends a tree structure to the dashboard log pane. The payload is JSON:
|
|
136
|
+
* `{ label?: string, children?: object[], roots?: object[] }` where each node
|
|
137
|
+
* has `{ label, children? }`. CLI fallback: indented text lines.
|
|
138
|
+
*
|
|
139
|
+
* @param {object} _spec tree descriptor (single root or `{ roots: [...] }`)
|
|
140
|
+
*/
|
|
141
|
+
export function LogTree(_spec) {
|
|
142
|
+
let payload = _NormalizeTreeSpec(_spec);
|
|
143
|
+
if (IsMicroGulp()) {
|
|
144
|
+
process.send({ type: 'structured-log', format: 'tree', payload });
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
for (let line of _AsciiTreeLines(payload)) console.log(line);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Sends an image gallery to the dashboard log pane. Each item needs a `src`
|
|
152
|
+
* (data URI or https URL) and a `label`; optional `caption` and per-item `title`.
|
|
153
|
+
* CLI fallback: lists labels and src lengths on stdout.
|
|
154
|
+
*
|
|
155
|
+
* @param {object} _spec `{ title?, columns?, items: { label, src, caption?, title? }[] }`
|
|
156
|
+
*/
|
|
157
|
+
export function LogGallery(_spec) {
|
|
158
|
+
let payload = _NormalizeGallerySpec(_spec);
|
|
159
|
+
if (IsMicroGulp()) {
|
|
160
|
+
process.send({ type: 'structured-log', format: 'gallery', payload });
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
console.log(_AsciiGallery(payload));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Renders µCSS BuildSkin `report.debug.previews` as a thumbnail gallery.
|
|
168
|
+
* Items without `thumbDataUri` are omitted (paths-only assets stay in the text log).
|
|
169
|
+
*
|
|
170
|
+
* @param {object[]} _previews preview entries from `report.debug.previews`
|
|
171
|
+
* @param {object} [_options] `{ title?, columns? }`
|
|
172
|
+
*/
|
|
173
|
+
export function LogAssetPreviews(_previews, _options = {}) {
|
|
174
|
+
let items = (_previews ?? [])
|
|
175
|
+
.filter((_entry) => _entry?.thumbDataUri)
|
|
176
|
+
.map((_entry) => ({
|
|
177
|
+
label: String(_entry.relPath ?? _entry.path ?? ''),
|
|
178
|
+
src: _entry.thumbDataUri,
|
|
179
|
+
caption: [
|
|
180
|
+
_entry.step,
|
|
181
|
+
_entry.skipped ? 'cached' : null,
|
|
182
|
+
_entry.width && _entry.height ? `${_entry.width}\u00d7${_entry.height}` : null,
|
|
183
|
+
].filter(Boolean).join(' \u00b7 '),
|
|
184
|
+
}));
|
|
185
|
+
if (!items.length) return;
|
|
186
|
+
LogGallery({
|
|
187
|
+
title: _options.title,
|
|
188
|
+
columns: _options.columns ?? 4,
|
|
189
|
+
items,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Convenience wrapper for a full µCSS `BuildSkin` debug block: summary table
|
|
195
|
+
* plus thumbnail gallery when `report.debug` is present.
|
|
196
|
+
*
|
|
197
|
+
* @param {object} _report BuildSkin return value
|
|
198
|
+
* @param {object} [_options] `{ title?, columns?, table?: boolean }`
|
|
199
|
+
*/
|
|
200
|
+
export function LogBuildDebugReport(_report, _options = {}) {
|
|
201
|
+
let debug = _report?.debug;
|
|
202
|
+
if (!debug) return;
|
|
203
|
+
if (_options.table !== false && debug.summary) {
|
|
204
|
+
LogTable({
|
|
205
|
+
columns: [{ id: 'metric', label: 'Metric' }, { id: 'value', label: 'Value' }],
|
|
206
|
+
rows: Object.entries(debug.summary).map(([metric, value]) => ({ metric, value: String(value) })),
|
|
207
|
+
hyphenate: false,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
LogAssetPreviews(debug.previews, {
|
|
211
|
+
title: _options.title ?? 'Generated assets',
|
|
212
|
+
columns: _options.columns,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
114
216
|
// -------------------------------------------------
|
|
115
217
|
// sound & speech
|
|
116
218
|
// -------------------------------------------------
|
|
@@ -333,12 +435,92 @@ async function _FallbackField(_field) {
|
|
|
333
435
|
}
|
|
334
436
|
}
|
|
335
437
|
|
|
438
|
+
function _NormalizeColumn(_column, _index) {
|
|
439
|
+
if (typeof _column === 'string') return { id: 'c' + _index, label: _column };
|
|
440
|
+
return { id: String(_column.id ?? 'c' + _index), label: String(_column.label ?? _column.id ?? 'c' + _index) };
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function _NormalizeTableSpec(_spec) {
|
|
444
|
+
let columns = (_spec?.columns ?? []).map(_NormalizeColumn);
|
|
445
|
+
let rows = _spec?.rows ?? [];
|
|
446
|
+
let normalizedRows = rows.map((_row) => {
|
|
447
|
+
if (Array.isArray(_row)) {
|
|
448
|
+
let obj = {};
|
|
449
|
+
for (let c = 0; c < columns.length; c++) obj[columns[c].id] = _row[c] ?? '';
|
|
450
|
+
return obj;
|
|
451
|
+
}
|
|
452
|
+
return _row;
|
|
453
|
+
});
|
|
454
|
+
return { columns, rows: normalizedRows, hyphenate: _spec?.hyphenate !== false };
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function _NormalizeTreeSpec(_spec) {
|
|
458
|
+
if (_spec?.roots) return { roots: _spec.roots };
|
|
459
|
+
if (_spec?.label != null || _spec?.children) return { roots: [_spec] };
|
|
460
|
+
return { roots: [] };
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function _AsciiTable(_payload) {
|
|
464
|
+
let headers = _payload.columns.map((_c) => _c.label);
|
|
465
|
+
let widths = headers.map((_h) => _h.length);
|
|
466
|
+
for (let row of _payload.rows) {
|
|
467
|
+
for (let c = 0; c < _payload.columns.length; c++) {
|
|
468
|
+
let cell = String(row[_payload.columns[c].id] ?? '');
|
|
469
|
+
widths[c] = Math.max(widths[c], cell.length);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
let sep = widths.map((_w) => '-'.repeat(_w)).join('-+-');
|
|
473
|
+
let lines = [headers.map((_h, _i) => _h.padEnd(widths[_i])).join(' | ')];
|
|
474
|
+
lines.push(sep);
|
|
475
|
+
for (let row of _payload.rows) {
|
|
476
|
+
lines.push(_payload.columns.map((_col, _i) => String(row[_col.id] ?? '').padEnd(widths[_i])).join(' | '));
|
|
477
|
+
}
|
|
478
|
+
return lines.join('\n');
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function _AsciiTreeLines(_payload, _indent = '', _roots = null) {
|
|
482
|
+
let roots = _roots ?? _payload.roots ?? [];
|
|
483
|
+
let lines = [];
|
|
484
|
+
for (let i = 0; i < roots.length; i++) {
|
|
485
|
+
let node = roots[i];
|
|
486
|
+
let branch = i < roots.length - 1 ? '├─ ' : '└─ ';
|
|
487
|
+
lines.push(_indent + branch + String(node.label ?? ''));
|
|
488
|
+
let childIndent = _indent + (i < roots.length - 1 ? '│ ' : ' ');
|
|
489
|
+
if (node.children?.length) lines.push(..._AsciiTreeLines(_payload, childIndent, node.children));
|
|
490
|
+
}
|
|
491
|
+
return lines;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function _NormalizeGallerySpec(_spec) {
|
|
495
|
+
let columns = Math.max(1, Math.min(8, Number(_spec?.columns) || 4));
|
|
496
|
+
let items = (_spec?.items ?? []).map((_item) => ({
|
|
497
|
+
label: String(_item?.label ?? ''),
|
|
498
|
+
src: String(_item?.src ?? ''),
|
|
499
|
+
caption: _item?.caption != null ? String(_item.caption) : '',
|
|
500
|
+
title: _item?.title != null ? String(_item.title) : '',
|
|
501
|
+
})).filter((_item) => _item.src);
|
|
502
|
+
return {
|
|
503
|
+
title: _spec?.title != null ? String(_spec.title) : '',
|
|
504
|
+
columns,
|
|
505
|
+
items,
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function _AsciiGallery(_payload) {
|
|
510
|
+
let lines = [];
|
|
511
|
+
if (_payload.title) lines.push(_payload.title);
|
|
512
|
+
for (let item of _payload.items) {
|
|
513
|
+
let meta = item.caption ? ` (${item.caption})` : '';
|
|
514
|
+
let srcHint = item.src.startsWith('data:') ? `[data URI, ${item.src.length} chars]` : item.src;
|
|
515
|
+
lines.push(` ${item.label}${meta}: ${srcHint}`);
|
|
516
|
+
}
|
|
517
|
+
return lines.join('\n');
|
|
518
|
+
}
|
|
519
|
+
|
|
336
520
|
export default {
|
|
337
521
|
IsMicroGulp,
|
|
338
522
|
IsµGulp,
|
|
339
523
|
IsUGulp,
|
|
340
|
-
µMeta,
|
|
341
|
-
MicroGulpMeta,
|
|
342
524
|
ReportProgress,
|
|
343
525
|
CreateProgress,
|
|
344
526
|
PlaySignal,
|
|
@@ -349,4 +531,18 @@ export default {
|
|
|
349
531
|
RequestFontInput,
|
|
350
532
|
RequestSelectInput,
|
|
351
533
|
RequestMultiSelectInput,
|
|
534
|
+
Log: I18x.Log,
|
|
535
|
+
Warn: I18x.Warn,
|
|
536
|
+
LogError: I18x.LogError,
|
|
537
|
+
Translate: I18x.Translate,
|
|
538
|
+
GetLid: I18x.GetLid,
|
|
539
|
+
SetLid: I18x.SetLid,
|
|
540
|
+
GetTimeZone: I18x.GetTimeZone,
|
|
541
|
+
DateInTimeZone: I18x.DateInTimeZone,
|
|
542
|
+
InstallStringExtensions: I18x.InstallStringExtensions,
|
|
543
|
+
LogTable,
|
|
544
|
+
LogTree,
|
|
545
|
+
LogGallery,
|
|
546
|
+
LogAssetPreviews,
|
|
547
|
+
LogBuildDebugReport,
|
|
352
548
|
};
|