@nilvn/core 0.14.0

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.
@@ -0,0 +1,181 @@
1
+ // Schemas for the engine's built-in CommandNode commands, grounded in
2
+ // packages/engine/src/builtins.ts. These drive the editor's command palette and forms.
3
+ //
4
+ // Excluded on purpose:
5
+ // - use / alias / actor -> project setup (Project.plugins / alias / actors), not nodes
6
+ // - jump / if / set -> first-class node kinds (JumpNode / JumpNode+condition / SetNode)
7
+ export const BUILTIN_COMMANDS = [
8
+ {
9
+ name: 'bg',
10
+ label: 'cmd.bg.label',
11
+ category: 'stage',
12
+ icon: '๐Ÿ–ผ',
13
+ hint: 'cmd.bg.hint',
14
+ params: [
15
+ { key: 'bg', label: 'cmd.bg.bg', type: 'asset:bg', positional: 0 },
16
+ { key: 'color', label: 'cmd.bg.color', type: 'color' },
17
+ { key: 'fade', label: 'cmd.bg.fade', type: 'number', default: 0 },
18
+ ],
19
+ },
20
+ {
21
+ name: 'char',
22
+ label: 'cmd.char.label',
23
+ category: 'stage',
24
+ icon: '๐Ÿง',
25
+ hint: 'cmd.char.hint',
26
+ params: [
27
+ { key: 'id', label: 'cmd.char.id', type: 'actor', required: true, positional: 0 },
28
+ { key: 'face', label: 'cmd.char.face', type: 'face', positional: 1 },
29
+ {
30
+ key: 'at',
31
+ label: 'cmd.char.at',
32
+ type: 'enum',
33
+ options: [
34
+ { value: 'left', label: 'cmd.char.at.left' },
35
+ { value: 'center', label: 'cmd.char.at.center' },
36
+ { value: 'right', label: 'cmd.char.at.right' },
37
+ ],
38
+ },
39
+ // Birth transform โ€” written by the stage drag / scale / rotate handles (4c-4).
40
+ // NO schema default: these channels carry forward (an absent param means "keep
41
+ // the previous value", not "identity"), so the serializer must never drop an
42
+ // explicit identity that overrides an inherited non-identity value.
43
+ { key: 'y', label: 'cmd.char.y', type: 'number', advanced: true },
44
+ { key: 'scale', label: 'cmd.char.scale', type: 'number', advanced: true },
45
+ { key: 'rotation', label: 'cmd.char.rotation', type: 'number', advanced: true },
46
+ { key: 'fade', label: 'cmd.char.fade', type: 'number', default: 0.3 },
47
+ { key: 'src', label: 'cmd.char.src', type: 'string', advanced: true },
48
+ ],
49
+ },
50
+ {
51
+ name: 'hide',
52
+ label: 'cmd.hide.label',
53
+ category: 'stage',
54
+ icon: '๐Ÿšช',
55
+ params: [
56
+ { key: 'id', label: 'cmd.hide.id', type: 'actor', required: true, positional: 0 },
57
+ { key: 'fade', label: 'cmd.hide.fade', type: 'number', default: 0.3 },
58
+ ],
59
+ },
60
+ {
61
+ name: 'clear',
62
+ label: 'cmd.clear.label',
63
+ category: 'stage',
64
+ icon: '๐Ÿงน',
65
+ hint: 'cmd.clear.hint',
66
+ params: [{ key: 'fade', label: 'cmd.clear.fade', type: 'number', default: 0.3 }],
67
+ },
68
+ {
69
+ name: 'wait',
70
+ label: 'cmd.wait.label',
71
+ category: 'flow',
72
+ icon: 'โฑ',
73
+ hint: 'cmd.wait.hint',
74
+ params: [{ key: 'sec', label: 'cmd.wait.sec', type: 'number', required: true, default: 0.5, positional: 0 }],
75
+ },
76
+ {
77
+ name: 'fadeout',
78
+ label: 'cmd.fadeout.label',
79
+ category: 'fx',
80
+ icon: '๐ŸŒ‘',
81
+ params: [
82
+ { key: 'sec', label: 'cmd.fadeout.sec', type: 'number', default: 0.6, positional: 0 },
83
+ { key: 'color', label: 'cmd.fadeout.color', type: 'color', default: '#000' },
84
+ ],
85
+ },
86
+ {
87
+ name: 'fadein',
88
+ label: 'cmd.fadein.label',
89
+ category: 'fx',
90
+ icon: '๐ŸŒ—',
91
+ params: [{ key: 'sec', label: 'cmd.fadein.sec', type: 'number', default: 0.6, positional: 0 }],
92
+ },
93
+ {
94
+ name: 'dialog',
95
+ label: 'cmd.dialog.label',
96
+ category: 'stage',
97
+ icon: '๐Ÿ’ฌ',
98
+ hint: 'cmd.dialog.hint',
99
+ params: [
100
+ {
101
+ key: 'mode',
102
+ label: 'cmd.dialog.mode',
103
+ type: 'enum',
104
+ default: 'show',
105
+ positional: 0,
106
+ options: [
107
+ { value: 'show', label: 'cmd.dialog.mode.show' },
108
+ { value: 'hide', label: 'cmd.dialog.mode.hide' },
109
+ ],
110
+ },
111
+ ],
112
+ },
113
+ {
114
+ name: 'window',
115
+ label: 'cmd.window.label',
116
+ category: 'stage',
117
+ icon: '๐ŸชŸ',
118
+ hint: 'cmd.window.hint',
119
+ params: [
120
+ // The skin picker reuses the background asset pool (a window texture is a
121
+ // flat image like a bg); type `skin=none` by hand to restore the default.
122
+ { key: 'skin', label: 'cmd.window.skin', type: 'asset:bg', required: true, positional: 0 },
123
+ { key: 'target', label: 'cmd.window.target', type: 'string', default: 'dialog', advanced: true },
124
+ ],
125
+ },
126
+ {
127
+ name: 'textspeed',
128
+ label: 'cmd.textspeed.label',
129
+ category: 'text',
130
+ icon: 'โฉ',
131
+ hint: 'cmd.textspeed.hint',
132
+ params: [{ key: 'cps', label: 'cmd.textspeed.cps', type: 'number', default: 40, positional: 0 }],
133
+ },
134
+ {
135
+ name: 'bgm',
136
+ label: 'cmd.bgm.label',
137
+ category: 'audio',
138
+ icon: '๐ŸŽต',
139
+ params: [
140
+ { key: 'bgm', label: 'cmd.bgm.bgm', type: 'asset:bgm', required: true, positional: 0 },
141
+ { key: 'loop', label: 'cmd.bgm.loop', type: 'boolean', default: true },
142
+ { key: 'volume', label: 'cmd.bgm.volume', type: 'number', default: 1 },
143
+ // Multi-track: 'music' = the classic BGM slot; any other name is an
144
+ // ambience bed layered alongside (its own loop channel + master volume).
145
+ { key: 'track', label: 'cmd.bgm.track', type: 'string', default: 'music', advanced: true },
146
+ { key: 'fade', label: 'cmd.bgm.fade', type: 'number', default: 0, advanced: true },
147
+ ],
148
+ },
149
+ {
150
+ name: 'stopbgm',
151
+ label: 'cmd.stopbgm.label',
152
+ category: 'audio',
153
+ icon: '๐Ÿ”‡',
154
+ params: [
155
+ { key: 'fade', label: 'cmd.stopbgm.fade', type: 'number', default: 0 },
156
+ { key: 'track', label: 'cmd.stopbgm.track', type: 'string', default: 'music', advanced: true },
157
+ ],
158
+ },
159
+ {
160
+ name: 'se',
161
+ label: 'cmd.se.label',
162
+ category: 'audio',
163
+ icon: '๐Ÿ””',
164
+ params: [
165
+ { key: 'se', label: 'cmd.se.se', type: 'asset:se', required: true, positional: 0 },
166
+ { key: 'volume', label: 'cmd.se.volume', type: 'number', default: 1 },
167
+ ],
168
+ },
169
+ {
170
+ name: 'end',
171
+ label: 'cmd.end.label',
172
+ category: 'flow',
173
+ icon: '๐Ÿ',
174
+ hint: 'cmd.end.hint',
175
+ params: [{ key: 'sec', label: 'cmd.end.sec', type: 'number', default: 1, positional: 0 }],
176
+ },
177
+ ];
178
+ export const BUILTIN_COMMAND_MAP = Object.fromEntries(BUILTIN_COMMANDS.map((c) => [c.name, c]));
179
+ export function getCommandSchema(name) {
180
+ return BUILTIN_COMMAND_MAP[name];
181
+ }
package/dist/i18n.d.ts ADDED
@@ -0,0 +1,50 @@
1
+ export type Catalog = Record<string, string>;
2
+ export type Params = Record<string, string | number>;
3
+ /** Namespaced, per-language catalogs as produced by a LocaleLoader.
4
+ * Shape: `{ [namespace]: { [lang]: { [id]: text } } }`. Namespaces (`editor`,
5
+ * `core`, `engine`, `plugin:<id>`) are an authoring/override grouping โ€” the
6
+ * runtime merges them flat because ids are already globally unique. */
7
+ export interface LoadedCatalogs {
8
+ [namespace: string]: {
9
+ [lang: string]: Catalog;
10
+ };
11
+ }
12
+ /** Loads the catalogs for a host (web glob, Tauri resources + user overrides, or
13
+ * an inline injected blob for the engine's offline single-file export). */
14
+ export interface LocaleLoader {
15
+ load(): Promise<LoadedCatalogs>;
16
+ }
17
+ export interface I18n {
18
+ /** Switch the active language (unknown languages just miss into the base). */
19
+ setLocale(lang: string): void;
20
+ getLocale(): string;
21
+ /** Merge one language's catalog. `namespace` is advisory (kept for override
22
+ * composition at the loader layer); lookup is flat since ids are unique.
23
+ * Idempotent, last-write-wins per id. */
24
+ registerCatalog(namespace: string, lang: string, catalog: Catalog): void;
25
+ /** Resolve `id` for the active language: active โ†’ base โ†’ id. `{name}`
26
+ * placeholders are filled from `params` (in every language). */
27
+ t(id: string, params?: Params): string;
28
+ /** Whether `id` resolves to a real string (in the active or base language). */
29
+ has(id: string): boolean;
30
+ }
31
+ /** Create an i18n instance. `base` (default `en`) is the guaranteed-complete
32
+ * fallback language; `active` defaults to `base`. */
33
+ export declare function createI18n(opts?: {
34
+ base?: string;
35
+ active?: string;
36
+ }): I18n;
37
+ /** Register every namespace/language of a LoadedCatalogs into an instance. */
38
+ export declare function applyCatalogs(i18n: I18n, loaded: LoadedCatalogs): void;
39
+ /** The minimal shape `registerEnabledPluginCatalogs` needs from a plugin: its
40
+ * id (โ†’ `plugin:<id>` namespace) and an optional baked message fallback for
41
+ * when no JSON catalog was loaded (e.g. zero-dep core tooling). PluginManifest
42
+ * satisfies this. */
43
+ export interface PluginI18nSource {
44
+ id: string;
45
+ messages?: Partial<Record<string, Catalog>>;
46
+ }
47
+ /** Merge each enabled plugin's catalog under its `plugin:<id>` namespace: prefer
48
+ * the loaded JSON catalog, else fall back to the manifest's baked `messages`.
49
+ * Called by BOTH the editor and the engine so plugin chrome localizes uniformly. */
50
+ export declare function registerEnabledPluginCatalogs(i18n: I18n, plugins: PluginI18nSource[], loaded: LoadedCatalogs): void;
package/dist/i18n.js ADDED
@@ -0,0 +1,77 @@
1
+ // Shared UI-chrome i18n runtime for the studio (editor + engine).
2
+ //
3
+ // Unlike the authored-story text i18n (see catalog.ts / project.catalogs), this
4
+ // localizes the *tool/game chrome*: buttons, labels, menus, toasts. It replaces
5
+ // the old "key-as-default" scheme (where the Chinese source string was the lookup
6
+ // key) with STABLE DOTTED IDs โ€” e.g. `topBar.home`, `cmd.bg.label`,
7
+ // `plugin.charfx.name`, `engine.menu.save`. Lookup falls back active โ†’ base(en) โ†’
8
+ // the raw id, so a missing translation surfaces a visible id rather than silently
9
+ // showing another language.
10
+ //
11
+ // Zero-dependency and pure: no fetch/fs. Catalogs are plain data injected via
12
+ // registerCatalog / applyCatalogs; the fetching/globbing/override lives in the
13
+ // host packages' LocaleLoader implementations (which produce a LoadedCatalogs and
14
+ // hand it here). The editor and engine each own their own instance โ€” they can't
15
+ // share memory across the engine's IIFE boundary, but they share this code and the
16
+ // same JSON catalog files, which is what removes the old duplicated-key problem.
17
+ const PLACEHOLDER = /\{(\w+)\}/g;
18
+ function fill(tpl, params) {
19
+ return tpl.replace(PLACEHOLDER, (_, name) => (name in params ? String(params[name]) : `{${name}}`));
20
+ }
21
+ /** Create an i18n instance. `base` (default `en`) is the guaranteed-complete
22
+ * fallback language; `active` defaults to `base`. */
23
+ export function createI18n(opts) {
24
+ const base = opts?.base ?? 'en';
25
+ let active = opts?.active ?? base;
26
+ // Flat merged catalog per language; ids are globally unique so namespace need
27
+ // not be a lookup dimension.
28
+ const merged = {};
29
+ return {
30
+ setLocale(lang) {
31
+ active = lang;
32
+ },
33
+ getLocale() {
34
+ return active;
35
+ },
36
+ registerCatalog(_namespace, lang, catalog) {
37
+ Object.assign((merged[lang] ??= {}), catalog);
38
+ },
39
+ t(id, params) {
40
+ const tpl = merged[active]?.[id] ?? merged[base]?.[id] ?? id;
41
+ return params ? fill(tpl, params) : tpl;
42
+ },
43
+ has(id) {
44
+ return merged[active]?.[id] !== undefined || merged[base]?.[id] !== undefined;
45
+ },
46
+ };
47
+ }
48
+ /** Register every namespace/language of a LoadedCatalogs into an instance. */
49
+ export function applyCatalogs(i18n, loaded) {
50
+ for (const namespace in loaded) {
51
+ const byLang = loaded[namespace];
52
+ for (const lang in byLang)
53
+ i18n.registerCatalog(namespace, lang, byLang[lang]);
54
+ }
55
+ }
56
+ /** Merge each enabled plugin's catalog under its `plugin:<id>` namespace: prefer
57
+ * the loaded JSON catalog, else fall back to the manifest's baked `messages`.
58
+ * Called by BOTH the editor and the engine so plugin chrome localizes uniformly. */
59
+ export function registerEnabledPluginCatalogs(i18n, plugins, loaded) {
60
+ for (const p of plugins) {
61
+ const ns = `plugin:${p.id}`;
62
+ const fromJson = loaded[ns];
63
+ if (fromJson) {
64
+ for (const lang in fromJson)
65
+ i18n.registerCatalog(ns, lang, fromJson[lang]);
66
+ continue;
67
+ }
68
+ const baked = p.messages;
69
+ if (baked) {
70
+ for (const lang in baked) {
71
+ const cat = baked[lang];
72
+ if (cat)
73
+ i18n.registerCatalog(ns, lang, cat);
74
+ }
75
+ }
76
+ }
77
+ }
@@ -0,0 +1,15 @@
1
+ export * from './ir.js';
2
+ export * from './schema.js';
3
+ export * from './commands.js';
4
+ export * from './plugin-manifest.js';
5
+ export * from './semver.js';
6
+ export * from './plugins.js';
7
+ export * from './serialize.js';
8
+ export * from './catalog.js';
9
+ export * from './i18n.js';
10
+ export * from './chunk.js';
11
+ export * from './chunk-build.js';
12
+ export * from './package.js';
13
+ export * from './versions.js';
14
+ export * from './screenplay.js';
15
+ export * from './screenplay-format.js';
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ // @nilvn/core โ€” shared document model (IR) and command schema for the creator studio.
2
+ // Consumed by the engine, editor, AI generation and the in-browser bundler.
3
+ export * from './ir.js';
4
+ export * from './schema.js';
5
+ export * from './commands.js';
6
+ export * from './plugin-manifest.js';
7
+ export * from './semver.js';
8
+ export * from './plugins.js';
9
+ export * from './serialize.js';
10
+ export * from './catalog.js';
11
+ export * from './i18n.js';
12
+ export * from './chunk.js';
13
+ export * from './chunk-build.js';
14
+ export * from './package.js';
15
+ export * from './versions.js';
16
+ export * from './screenplay.js';
17
+ export * from './screenplay-format.js';