amateras 0.11.2 → 0.12.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.
- package/README.md +17 -16
- package/package.json +1 -1
- package/packages/core/src/index.ts +16 -6
- package/packages/core/src/lib/hmr.ts +4 -1
- package/packages/core/src/structure/ElementProto.ts +12 -5
- package/packages/core/src/structure/GlobalState.ts +7 -2
- package/packages/core/src/structure/NodeProto.ts +6 -0
- package/packages/core/src/structure/Proto.ts +18 -3
- package/packages/css/src/lib/cache.ts +4 -1
- package/packages/for/src/structure/For.ts +1 -0
- package/packages/i18n/src/index.ts +12 -40
- package/packages/i18n/src/structure/I18n.ts +90 -35
- package/packages/i18n/src/structure/I18nSession.ts +49 -0
- package/packages/i18n/src/structure/I18nTranslation.ts +17 -29
- package/packages/i18n/src/types.ts +3 -1
- package/packages/if/src/global.ts +3 -3
- package/packages/if/src/structure/Condition.ts +1 -0
- package/packages/if/src/structure/ConditionStatement.ts +4 -2
- package/packages/if/src/structure/ElseIf.ts +2 -1
- package/packages/if/src/structure/If.ts +2 -1
- package/packages/match/src/structure/Match.ts +1 -0
- package/packages/meta/src/index.ts +14 -2
- package/packages/prefetch/src/index.ts +2 -8
- package/packages/router/src/global.ts +1 -1
- package/packages/router/src/index.ts +8 -5
- package/packages/router/src/structure/Link.ts +2 -1
- package/packages/router/src/structure/Page.ts +20 -3
- package/packages/router/src/structure/Route.ts +3 -3
- package/packages/router/src/structure/RouteNode.ts +1 -1
- package/packages/router/src/structure/RouteSlot.ts +8 -15
- package/packages/router/src/structure/Router.ts +16 -10
- package/packages/signal/src/index.ts +16 -21
- package/packages/signal/src/lib/objectSignal.ts +21 -0
- package/packages/signal/src/structure/Signal.ts +10 -1
- package/packages/ui/src/index.ts +6 -1
- package/packages/ui/src/structure/Accordion.ts +67 -0
- package/packages/ui/src/structure/Radio.ts +3 -8
- package/packages/ui/src/structure/Slide.ts +6 -0
- package/packages/ui/src/structure/Slideshow.ts +23 -1
- package/packages/ui/src/structure/Tabs.ts +120 -0
- package/packages/ui/src/structure/TextBlock.ts +11 -0
- package/packages/ui/src/structure/Waterfall.ts +73 -0
- package/packages/ui/src/structure/WaterfallItem.ts +21 -0
- package/packages/utils/src/global.ts +7 -0
- package/packages/utils/src/lib/utils.ts +1 -4
- package/packages/utils/src/structure/UID.ts +1 -0
- package/packages/widget/src/index.ts +4 -7
- package/packages/widget/src/structure/Widget.ts +1 -1
package/README.md
CHANGED
|
@@ -53,7 +53,8 @@ const Counter = $.widget(() => ({
|
|
|
53
53
|
|
|
54
54
|
console.log('This template only run once.');
|
|
55
55
|
|
|
56
|
-
$('button', $$ => {
|
|
56
|
+
$('button', $$ => {
|
|
57
|
+
$([ double$ ])
|
|
57
58
|
$$.on('click', () => count$.set(val => val + 1));
|
|
58
59
|
})
|
|
59
60
|
}
|
|
@@ -79,21 +80,21 @@ Amateras 能让你编写接近 HTML 排版的模板代码,实现了在原生 J
|
|
|
79
80
|
|
|
80
81
|
| 模块库 | 体积 | Gzip | 简介 |
|
|
81
82
|
| --- | --- | --- | --- |
|
|
82
|
-
| core |
|
|
83
|
-
| widget |
|
|
84
|
-
| signal |
|
|
85
|
-
| css |
|
|
86
|
-
| for |
|
|
87
|
-
| if |
|
|
88
|
-
| match |
|
|
89
|
-
| router |
|
|
90
|
-
| i18n |
|
|
91
|
-
| idb |
|
|
92
|
-
| markdown |
|
|
93
|
-
| prefetch |
|
|
94
|
-
| meta |
|
|
95
|
-
| ui |
|
|
96
|
-
| utils |
|
|
83
|
+
| core | 5.47 kB | 2.32 kB | 核心模块 |
|
|
84
|
+
| widget | 0.35 kB | 0.17 kB | 组件模块 |
|
|
85
|
+
| signal | 1.78 kB | 0.74 kB | 响应式数据模块 |
|
|
86
|
+
| css | 1.56 kB | 0.71 kB | 样式模块 |
|
|
87
|
+
| for | 1.05 kB | 0.35 kB | 控制流 For 模块 |
|
|
88
|
+
| if | 3.08 kB | 1.15 kB | 控制流 If 模块 |
|
|
89
|
+
| match | 1.31 kB | 0.39 kB | 控制流 Match 模块 |
|
|
90
|
+
| router | 6.03 kB | 2.24 kB | 页面路由器模块 |
|
|
91
|
+
| i18n | 2.92 kB | 0.98 kB | 多语言界面模块 |
|
|
92
|
+
| idb | 5.26 kB | 1.99 kB | IndexedDB 模块 |
|
|
93
|
+
| markdown | 7.48 kB | 2.93 kB | Markdown 转换 HTML 模块 |
|
|
94
|
+
| prefetch | 0.41 kB | 0.20 kB | SSR 数据预取 |
|
|
95
|
+
| meta | 0.18 kB | 0.08 kB | SSR 页面 `meta` 标签管理 |
|
|
96
|
+
| ui | 6.50 kB | 2.25 kB | UI 组件模块 |
|
|
97
|
+
| utils | 0.00 kB | 0.00 kB | 通用工具库 |
|
|
97
98
|
|
|
98
99
|
## 文档
|
|
99
100
|
1. [基础入门](/docs/Basic.md)
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { onclient, onserver } from '#env';
|
|
2
|
-
import {
|
|
3
|
-
import { _instanceof, _null, forEach, isArray, isFunction, isString, isUndefined } from '@amateras/utils';
|
|
2
|
+
import { _instanceof, _null, forEach, isArray, isFunction, isString, isUndefined, toArray } from '@amateras/utils';
|
|
4
3
|
import './global';
|
|
5
4
|
import { ElementProto } from './structure/ElementProto';
|
|
6
5
|
import { Proto } from './structure/Proto';
|
|
7
6
|
import { TextProto } from './structure/TextProto';
|
|
7
|
+
import { hmr } from '#lib/hmr';
|
|
8
8
|
|
|
9
9
|
type ElementProtoArguments<C extends Constructor> =
|
|
10
10
|
RequiredKeys<RemoveIndexSignature<ConstructorParameters<C>[0]>> extends never
|
|
@@ -158,11 +158,21 @@ export namespace $ {
|
|
|
158
158
|
callback(match as any);
|
|
159
159
|
return cases.get(condition)?.() ?? cases.get(symbol_default)?.();
|
|
160
160
|
}
|
|
161
|
+
|
|
162
|
+
export const async = (fn: () => Promise<void>) => {
|
|
163
|
+
Proto.proto?.global.promises.add(fn());
|
|
164
|
+
}
|
|
165
|
+
|
|
161
166
|
export const stylesheet = onclient() ? new CSSStyleSheet() : _null;
|
|
162
|
-
export const styleMap = new Map<Constructor<ElementProto>, string
|
|
163
|
-
export const style = (proto: Constructor<ElementProto> | null, css: string) => {
|
|
164
|
-
|
|
165
|
-
|
|
167
|
+
export const styleMap = new Map<Constructor<ElementProto>, Set<string>>();
|
|
168
|
+
export const style = (proto: Constructor<ElementProto> | null, css: string | string[]) => {
|
|
169
|
+
let rules = toArray(css)
|
|
170
|
+
if (proto) {
|
|
171
|
+
let set = styleMap.get(proto) ?? new Set();
|
|
172
|
+
forEach(rules, rule => set.add(rule));
|
|
173
|
+
styleMap.set(proto, set);
|
|
174
|
+
}
|
|
175
|
+
if (stylesheet) forEach(rules, rule => stylesheet!.insertRule(rule));
|
|
166
176
|
}
|
|
167
177
|
|
|
168
178
|
if (stylesheet) document.adoptedStyleSheets.push(stylesheet);
|
|
@@ -6,7 +6,10 @@ import { TextProto } from "#structure/TextProto";
|
|
|
6
6
|
import { _Array_from, _instanceof, _undefined, forEach, is } from "@amateras/utils";
|
|
7
7
|
import { symbol_ProtoType } from "./symbols";
|
|
8
8
|
|
|
9
|
-
const elementProtoMap =
|
|
9
|
+
const elementProtoMap = (() => {
|
|
10
|
+
if (import.meta.hot) return import.meta.hot.data.protoMap ?? new Map<HTMLElement, Proto>();
|
|
11
|
+
return new Map<HTMLElement, Proto>()
|
|
12
|
+
})()
|
|
10
13
|
// save data before HMR
|
|
11
14
|
if (import.meta.hot) import.meta.hot.dispose(data => {
|
|
12
15
|
data.protoMap = elementProtoMap;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _Array_from, _Object_entries, forEach, isNull, isUndefined, map } from "@amateras/utils";
|
|
1
|
+
import { _Array_from, _null, _Object_assign, _Object_entries, forEach, isNull, isUndefined, map } from "@amateras/utils";
|
|
2
2
|
import { NodeProto } from "./NodeProto";
|
|
3
3
|
|
|
4
4
|
const SELF_CLOSING_TAGNAMES = ['img', 'hr', 'br', 'input', 'link', 'meta'];
|
|
@@ -15,10 +15,12 @@ export class ElementProto<H extends HTMLElement = HTMLElement> extends NodeProto
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
on<K extends keyof HTMLElementEventMap>(type: K, listener: (event: HTMLElementEventMap[K] & { currentTarget: H }) => void) {
|
|
18
|
-
|
|
18
|
+
let setListener = (node: Node) => {
|
|
19
19
|
node.addEventListener(type, listener as any)
|
|
20
20
|
this.disposers.add(() => node.removeEventListener(type, listener as any))
|
|
21
|
-
}
|
|
21
|
+
}
|
|
22
|
+
if (this.node) setListener(this.node);
|
|
23
|
+
else this.ondom(setListener);
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
override toString(): string {
|
|
@@ -57,14 +59,15 @@ export class ElementProto<H extends HTMLElement = HTMLElement> extends NodeProto
|
|
|
57
59
|
|
|
58
60
|
innerHTML(html: string) {
|
|
59
61
|
this.#innerHTML = html;
|
|
62
|
+
if (this.node) this.node.innerHTML = html;
|
|
60
63
|
}
|
|
61
64
|
|
|
62
65
|
attr(): Map<string, string>;
|
|
63
|
-
attr(attrName: string): string |
|
|
66
|
+
attr(attrName: string): string | null;
|
|
64
67
|
attr(attrName: string, attrValue: string | null): this;
|
|
65
68
|
attr(attrName?: string, attrValue?: string | null) {
|
|
66
69
|
if (!arguments.length) return this.#attr;
|
|
67
|
-
if (isUndefined(attrValue)) return this.#attr.get(attrName!);
|
|
70
|
+
if (isUndefined(attrValue)) return this.#attr.get(attrName!) ?? _null;
|
|
68
71
|
if (isNull(attrValue)) {
|
|
69
72
|
this.#attr.delete(attrName!);
|
|
70
73
|
this.node?.removeAttribute(attrName!);
|
|
@@ -86,6 +89,10 @@ export class ElementProto<H extends HTMLElement = HTMLElement> extends NodeProto
|
|
|
86
89
|
this.node?.classList.remove(...tokens);
|
|
87
90
|
}
|
|
88
91
|
|
|
92
|
+
style(declarations: Partial<CSSStyleDeclaration>) {
|
|
93
|
+
if (this.node) _Object_assign(this.node.style, declarations);
|
|
94
|
+
}
|
|
95
|
+
|
|
89
96
|
private token(method: 'add' | 'delete', name: string, ...tokens: string[]) {
|
|
90
97
|
let value = this.#attr.get(name);
|
|
91
98
|
let tokenArr = new Set(value?.split(' ') ?? []);
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import { forEach } from "@amateras/utils";
|
|
1
|
+
import { _Object_assign, forEach } from "@amateras/utils";
|
|
2
2
|
|
|
3
3
|
export class GlobalState {
|
|
4
4
|
static disposers = new Set<(global: GlobalState) => void>()
|
|
5
|
-
|
|
5
|
+
promises = new Set<Promise<any>>();
|
|
6
6
|
dispose() {
|
|
7
|
+
this.promises.clear();
|
|
7
8
|
forEach(GlobalState.disposers, disposer => disposer(this))
|
|
8
9
|
}
|
|
10
|
+
|
|
11
|
+
static assign(obj: object) {
|
|
12
|
+
_Object_assign(GlobalState.prototype, obj);
|
|
13
|
+
}
|
|
9
14
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { _null } from "@amateras/utils";
|
|
2
2
|
import { Proto } from "./Proto";
|
|
3
|
+
import { onclient } from "#env";
|
|
3
4
|
|
|
4
5
|
export class NodeProto<N extends Node & ChildNode = Node & ChildNode> extends Proto {
|
|
5
6
|
node: null | N = _null;
|
|
@@ -12,6 +13,11 @@ export class NodeProto<N extends Node & ChildNode = Node & ChildNode> extends Pr
|
|
|
12
13
|
this.modifiers.add(callback);
|
|
13
14
|
}
|
|
14
15
|
|
|
16
|
+
inDOM() {
|
|
17
|
+
if (onclient()) return document.contains(this.node);
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
|
|
15
21
|
override removeNode(): void {
|
|
16
22
|
this.node?.remove();
|
|
17
23
|
}
|
|
@@ -42,10 +42,10 @@ export abstract class Proto {
|
|
|
42
42
|
}).flat()
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
build(
|
|
45
|
+
build(cascading = true): this {
|
|
46
46
|
this.clear(true);
|
|
47
47
|
$.context(Proto, this, () => this.layout?.(this));
|
|
48
|
-
if (
|
|
48
|
+
if (cascading) forEach(this.protos, proto => {
|
|
49
49
|
proto.build()
|
|
50
50
|
});
|
|
51
51
|
return this
|
|
@@ -73,7 +73,7 @@ export abstract class Proto {
|
|
|
73
73
|
if (dispose) forEach(this.protos, proto => proto.dispose())
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
findAbove<T>(filter: (proto: Proto) =>
|
|
76
|
+
findAbove<T extends Proto>(filter: (proto: Proto) => any): T | null {
|
|
77
77
|
let parent = this.parent;
|
|
78
78
|
if (parent) return filter(parent) ? parent as T : parent.findAbove(filter);
|
|
79
79
|
return _null;
|
|
@@ -87,4 +87,19 @@ export abstract class Proto {
|
|
|
87
87
|
}
|
|
88
88
|
return _null;
|
|
89
89
|
}
|
|
90
|
+
|
|
91
|
+
findBelowAll<T extends Proto = Proto>(filter: (proto: Proto) => boolean | void): T[] {
|
|
92
|
+
let matches: T[] = [];
|
|
93
|
+
for (let proto of this.protos) {
|
|
94
|
+
if (filter(proto)) matches.push(proto as T);
|
|
95
|
+
matches.push(...proto.findBelowAll(filter) as T[]);
|
|
96
|
+
}
|
|
97
|
+
return matches;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* This method will be called when control flow proto is updated,
|
|
102
|
+
* it's useful when you need re-render content of component while content updated.
|
|
103
|
+
*/
|
|
104
|
+
mutate() {}
|
|
90
105
|
}
|
|
@@ -18,7 +18,10 @@ export const cssGlobalRuleSet = new Set<$CSSRule>();
|
|
|
18
18
|
* This is very suitable for storing CSS objects in JSON format, as the length of the JSON string will not
|
|
19
19
|
* affect the retrieve efficiency of the Map.
|
|
20
20
|
*/
|
|
21
|
-
export const cssRuleByJSONMap: Map<string, $CSSRule> =
|
|
21
|
+
export const cssRuleByJSONMap: Map<string, $CSSRule> = $.call(() => {
|
|
22
|
+
if (import.meta.hot) return import.meta.hot.data.cssMap ?? new Map();
|
|
23
|
+
return new Map();
|
|
24
|
+
})
|
|
22
25
|
|
|
23
26
|
if (import.meta.hot) {
|
|
24
27
|
import.meta.hot.dispose(data => {
|
|
@@ -1,69 +1,41 @@
|
|
|
1
1
|
import { I18n } from "#structure/I18n";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { _instanceof, _Object_assign } from "@amateras/utils";
|
|
5
|
-
import type { GetDictionaryContextByKey, I18nTranslationDirKey, I18nTranslationKey, I18nTranslationParams, Mixin, ResolvedAsyncDictionary } from "./types";
|
|
2
|
+
import { I18nTranslation as _I18nTranslation, I18nTranslation } from "#structure/I18nTranslation";
|
|
3
|
+
import { _instanceof, _null, _Object_assign } from "@amateras/utils";
|
|
6
4
|
import { GlobalState } from "@amateras/core";
|
|
5
|
+
import type { I18nSession } from "#structure/I18nSession";
|
|
7
6
|
|
|
8
7
|
declare global {
|
|
9
8
|
export namespace $ {
|
|
10
|
-
export
|
|
11
|
-
<K extends I18nTranslationKey<D>, P extends I18nTranslationParams<K, D>>(path: K, ...params: P extends Record<string, never> ? [] : [P]): I18nTranslation;
|
|
12
|
-
i18n: I18n;
|
|
13
|
-
locale(lang?: string): Promise<void>;
|
|
14
|
-
add<F extends I18nDictionaryContext | I18nDictionaryContextImporter>(lang: string, dictionary: F): I18nFunction<Mixin<D, (F extends I18nDictionaryContextImporter ? ResolvedAsyncDictionary<F> : F)>>;
|
|
15
|
-
delete(lang: string): this;
|
|
16
|
-
dir<K extends I18nTranslationDirKey<D>>(path: K): I18nFunction<GetDictionaryContextByKey<K, D>>
|
|
17
|
-
}
|
|
18
|
-
export function i18n(defaultLocale: string): I18nFunction;
|
|
9
|
+
export function i18n(defaultLocale: string): I18n;
|
|
19
10
|
export type I18nTranslation = _I18nTranslation;
|
|
20
11
|
|
|
21
12
|
export interface TextProcessorValueMap {
|
|
22
13
|
i18n: I18nTranslation
|
|
23
14
|
}
|
|
24
15
|
}
|
|
16
|
+
|
|
17
|
+
export interface GlobalEventHandlersEventMap {
|
|
18
|
+
localeupdate: Event;
|
|
19
|
+
}
|
|
25
20
|
}
|
|
26
21
|
|
|
27
22
|
declare module '@amateras/core' {
|
|
28
23
|
export interface GlobalState {
|
|
29
24
|
i18n: {
|
|
30
|
-
|
|
25
|
+
session: I18nSession | null
|
|
31
26
|
}
|
|
32
27
|
}
|
|
33
28
|
}
|
|
34
29
|
|
|
35
|
-
|
|
30
|
+
GlobalState.assign({
|
|
36
31
|
i18n: {
|
|
37
|
-
|
|
32
|
+
session: _null
|
|
38
33
|
}
|
|
39
34
|
})
|
|
40
35
|
|
|
41
|
-
GlobalState.disposers.add(global => {
|
|
42
|
-
global.i18n.promises = [];
|
|
43
|
-
})
|
|
44
|
-
|
|
45
36
|
_Object_assign($, {
|
|
46
37
|
i18n(defaultLocale: string) {
|
|
47
|
-
|
|
48
|
-
const i18nFn = (key: string, options?: I18nTranslationOptions) => i18n.translate(key, options);
|
|
49
|
-
_Object_assign(i18nFn, {
|
|
50
|
-
i18n,
|
|
51
|
-
async locale(locale: string) {
|
|
52
|
-
await i18n.setLocale(locale);
|
|
53
|
-
},
|
|
54
|
-
add(lang: string, context: I18nDictionaryContext | I18nDictionaryContextImporter) {
|
|
55
|
-
i18n.map.set(lang, new I18nDictionary(context));
|
|
56
|
-
return this;
|
|
57
|
-
},
|
|
58
|
-
delete(lang: string) {
|
|
59
|
-
i18n.map.delete(lang);
|
|
60
|
-
return this;
|
|
61
|
-
},
|
|
62
|
-
dir(path: string) {
|
|
63
|
-
return (key: string, options?: I18nTranslationOptions) => i18n.translate(`${path}.${key}`, options)
|
|
64
|
-
}
|
|
65
|
-
})
|
|
66
|
-
return i18nFn
|
|
38
|
+
return new I18n(defaultLocale)
|
|
67
39
|
}
|
|
68
40
|
})
|
|
69
41
|
|
|
@@ -1,51 +1,106 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { I18nDictionary } from "#structure/I18nDictionary";
|
|
1
|
+
import { I18nDictionary, type I18nDictionaryContext, type I18nDictionaryContextImporter } from "#structure/I18nDictionary";
|
|
3
2
|
import { I18nTranslation, type I18nTranslationOptions } from "./I18nTranslation";
|
|
3
|
+
import { I18nSession } from "./I18nSession";
|
|
4
|
+
import { onclient, Proto } from "@amateras/core";
|
|
5
|
+
import type { I18nTranslationKey, I18nTranslationParams, Mixin, ResolvedAsyncDictionary, I18nTranslationDirKey, GetDictionaryContextByKey } from "../types";
|
|
6
|
+
import { map } from "@amateras/utils";
|
|
4
7
|
|
|
5
|
-
export class I18n {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
export class I18n<D extends I18nDictionaryContext = {}> {
|
|
9
|
+
#locale: string;
|
|
10
|
+
dictionaries = new Map<string, I18nDictionary>();
|
|
11
|
+
defaultLocale: string;
|
|
12
|
+
sessions = new Set<I18nSession>();
|
|
13
|
+
session = new I18nSession(this);
|
|
14
|
+
path = '';
|
|
15
|
+
static key = '__locale__';
|
|
10
16
|
constructor(defaultLocale: string) {
|
|
11
|
-
this
|
|
12
|
-
this
|
|
17
|
+
this.defaultLocale = defaultLocale;
|
|
18
|
+
this.#locale = defaultLocale;
|
|
19
|
+
this.sessions.add(this.session);
|
|
13
20
|
}
|
|
14
21
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
22
|
+
add(lang: string, dictionary: I18nDictionaryContext | I18nDictionaryContextImporter) {
|
|
23
|
+
this.dictionaries.set(lang, new I18nDictionary(dictionary));
|
|
24
|
+
return this as any;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
delete(lang: string) {
|
|
28
|
+
this.dictionaries.delete(lang);
|
|
20
29
|
return this;
|
|
21
30
|
}
|
|
22
31
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
t<K extends I18nTranslationKey<D>, P extends I18nTranslationParams<K, D>>(path: K, ...params: P extends Record<string, never> ? [] : [P]): I18nTranslation;
|
|
33
|
+
t(key: string, options?: I18nTranslationOptions) {
|
|
34
|
+
return new I18nTranslation(this.getSession(), this.getFullPath(key), options);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
text<K extends I18nTranslationKey<D>, P extends I18nTranslationParams<K, D>>(path: K, ...params: P extends Record<string, never> ? [] : [P]): Promise<string>;
|
|
38
|
+
async text(key: string, options?: I18nTranslationOptions) {
|
|
39
|
+
let content = await this.getSession().fetch(this.getFullPath(key), options)
|
|
40
|
+
return content.text.reduce((acc, str, i) => acc + str + (content.args[i] || ''), '')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
dir(path: string) {
|
|
44
|
+
let i18n = this;
|
|
45
|
+
return {
|
|
46
|
+
t: (key: string, options: any) => i18n.t(`${path}.${key}` as any, options),
|
|
47
|
+
text: (key: string, options: any) => i18n.text(`${path}.${key}` as any, options),
|
|
48
|
+
dir: (postPath: string) => i18n.dir(`${path}.${postPath}`)
|
|
49
|
+
} as unknown as I18nDir;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
locale(): string;
|
|
53
|
+
locale(locale: string): Promise<void>;
|
|
54
|
+
locale(locale?: string) {
|
|
55
|
+
if (!arguments.length) {
|
|
56
|
+
this.readStoreLocale();
|
|
57
|
+
return this.#locale;
|
|
37
58
|
}
|
|
38
|
-
return
|
|
59
|
+
if (!locale) return;
|
|
60
|
+
let dictionary = this.dictionaries.get(locale);
|
|
61
|
+
if (!dictionary) {
|
|
62
|
+
let splited = locale.split('-');
|
|
63
|
+
if (splited.length === 1) return;
|
|
64
|
+
return this.locale(splited[0]!);
|
|
65
|
+
}
|
|
66
|
+
this.#locale = locale;
|
|
67
|
+
this.writeStoreLocale(locale);
|
|
68
|
+
return Promise.all(map(this.sessions, session => session.locale(locale)));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private getFullPath(key: string) {
|
|
72
|
+
return this.path ? `${this.path}.${key}` : key;
|
|
39
73
|
}
|
|
40
74
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
75
|
+
private getSession() {
|
|
76
|
+
let parentProto = Proto.proto;
|
|
77
|
+
if (parentProto) {
|
|
78
|
+
let session = parentProto.global.i18n.session ?? new I18nSession(this);
|
|
79
|
+
parentProto.global.i18n.session = session;
|
|
80
|
+
return session;
|
|
81
|
+
}
|
|
82
|
+
else return this.session;
|
|
45
83
|
}
|
|
46
84
|
|
|
47
|
-
|
|
48
|
-
|
|
85
|
+
private readStoreLocale() {
|
|
86
|
+
if (onclient()) this.#locale = localStorage.getItem(I18n.key) ?? this.defaultLocale;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private writeStoreLocale(locale: string) {
|
|
90
|
+
if (onclient()) localStorage.setItem(I18n.key, locale);
|
|
49
91
|
}
|
|
50
92
|
}
|
|
51
93
|
|
|
94
|
+
export interface I18n<D extends I18nDictionaryContext = {}> {
|
|
95
|
+
add<F extends I18nDictionaryContext | I18nDictionaryContextImporter>(lang: string, dictionary: F): I18n<Mixin<D, (F extends I18nDictionaryContextImporter ? ResolvedAsyncDictionary<F> : F)>>;
|
|
96
|
+
delete(lang: string): this;
|
|
97
|
+
t<K extends I18nTranslationKey<D>, P extends I18nTranslationParams<K, D>>(path: K, ...params: P extends Record<string, never> ? [] : [P]): I18nTranslation;
|
|
98
|
+
text<K extends I18nTranslationKey<D>, P extends I18nTranslationParams<K, D>>(path: K, ...params: P extends Record<string, never> ? [] : [P]): Promise<string>;
|
|
99
|
+
dir<K extends I18nTranslationDirKey<D>>(path: K): I18nDir<GetDictionaryContextByKey<K, D>>
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface I18nDir<D extends I18nDictionaryContext = {}> {
|
|
103
|
+
t<K extends I18nTranslationKey<D>, P extends I18nTranslationParams<K, D>>(path: K, ...params: P extends Record<string, never> ? [] : [P]): I18nTranslation;
|
|
104
|
+
text<K extends I18nTranslationKey<D>, P extends I18nTranslationParams<K, D>>(path: K, ...params: P extends Record<string, never> ? [] : [P]): Promise<string>;
|
|
105
|
+
dir<K extends I18nTranslationDirKey<D>>(path: K): I18n<GetDictionaryContextByKey<K, D>>
|
|
106
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { isUndefined, map } from "@amateras/utils";
|
|
2
|
+
import type { I18nTranslationResult } from "../types";
|
|
3
|
+
import type { I18n } from "./I18n";
|
|
4
|
+
import type { I18nTranslation, I18nTranslationOptions } from "./I18nTranslation";
|
|
5
|
+
import { onclient } from "@amateras/core";
|
|
6
|
+
|
|
7
|
+
export class I18nSession {
|
|
8
|
+
translations = new Set<I18nTranslation>()
|
|
9
|
+
i18n: I18n;
|
|
10
|
+
#locale: string;
|
|
11
|
+
constructor(i18n: I18n) {
|
|
12
|
+
this.i18n = i18n;
|
|
13
|
+
this.#locale = i18n.locale();
|
|
14
|
+
i18n.sessions.add(this);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async fetch(key: string, options?: I18nTranslationOptions): Promise<I18nTranslationResult> {
|
|
18
|
+
const dictionary = this.i18n.dictionaries.get(this.#locale);
|
|
19
|
+
if (!dictionary) return {text: [key], args: []};
|
|
20
|
+
const translate = await dictionary.find(key);
|
|
21
|
+
if (isUndefined(translate)) return {text: [key], args: []};
|
|
22
|
+
const snippets = translate.split(/\$[a-zA-Z0-9_]+\$/);
|
|
23
|
+
if (snippets.length === 1 || !options) return {text: [translate], args: []}
|
|
24
|
+
const matches = translate.matchAll(/(\$([a-zA-Z0-9_]+)\$)/g);
|
|
25
|
+
return {text: snippets, args: map(matches as unknown as [string, string, string][], ([,,value]) => options[value])}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
locale(): string;
|
|
29
|
+
locale(locale: string): Promise<void>;
|
|
30
|
+
locale(locale?: string) {
|
|
31
|
+
if (!arguments.length) return this.#locale;
|
|
32
|
+
if (locale) {
|
|
33
|
+
let dictionary = this.i18n.dictionaries.get(locale);
|
|
34
|
+
if (!dictionary) {
|
|
35
|
+
let splited = locale.split('-');
|
|
36
|
+
if (splited.length === 1) return;
|
|
37
|
+
return this.locale(splited[0]!);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (locale && locale !== this.#locale) {
|
|
41
|
+
this.#locale = locale;
|
|
42
|
+
return new Promise<void>(async (resolve) => {
|
|
43
|
+
await Promise.all(map(this.translations, translation => translation.update()));
|
|
44
|
+
resolve();
|
|
45
|
+
if (onclient()) dispatchEvent(new Event('localeupdate'));
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import type { I18n } from "#structure/I18n";
|
|
2
1
|
import { ProxyProto } from "@amateras/core";
|
|
3
|
-
import { forEach
|
|
2
|
+
import { forEach } from "@amateras/utils";
|
|
3
|
+
import type { I18nSession } from "./I18nSession";
|
|
4
4
|
|
|
5
5
|
export class I18nTranslation extends ProxyProto {
|
|
6
|
-
|
|
6
|
+
session: I18nSession;
|
|
7
7
|
key: string;
|
|
8
8
|
options: I18nTranslationOptions | undefined;
|
|
9
|
-
constructor(
|
|
9
|
+
constructor(session: I18nSession, key: string, options?: I18nTranslationOptions) {
|
|
10
10
|
super()
|
|
11
|
-
this.
|
|
11
|
+
this.session = session;
|
|
12
12
|
this.key = key;
|
|
13
13
|
this.options = options;
|
|
14
|
-
|
|
14
|
+
session.translations.add(this);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
override build(): this {
|
|
@@ -20,30 +20,18 @@ export class I18nTranslation extends ProxyProto {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
async update() {
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
forEach(this.protos, proto => proto.removeNode());
|
|
32
|
-
super.build();
|
|
33
|
-
this.node?.replaceWith(...this.toDOM());
|
|
34
|
-
}
|
|
35
|
-
update: {
|
|
36
|
-
const dictionary = i18n.dictionary();
|
|
37
|
-
if (!dictionary) { contentUpdate([key]); break update }
|
|
38
|
-
const request = dictionary.find(key);
|
|
39
|
-
this.global.i18n.promises.push(request);
|
|
40
|
-
const translate = await request;
|
|
41
|
-
if (isUndefined(translate)) break update;
|
|
42
|
-
const snippets = translate.split(/\$[a-zA-Z0-9_]+\$/);
|
|
43
|
-
if (snippets.length === 1 || !options) { contentUpdate([translate]); break update }
|
|
44
|
-
const matches = translate.matchAll(/(\$([a-zA-Z0-9_]+)\$)/g);
|
|
45
|
-
contentUpdate(snippets, map(matches as unknown as [string, string, string][], ([,,value]) => options[value]));
|
|
23
|
+
const request = this.session.fetch(this.key, this.options)
|
|
24
|
+
this.global.promises.add(request);
|
|
25
|
+
const {text, args} = await request;
|
|
26
|
+
this.layout = () => {
|
|
27
|
+
// make this array become Template String Array;
|
|
28
|
+
//@ts-ignore
|
|
29
|
+
text.raw = text;
|
|
30
|
+
$(text as any, ...args);
|
|
46
31
|
}
|
|
32
|
+
forEach(this.protos, proto => proto.removeNode());
|
|
33
|
+
super.build();
|
|
34
|
+
this.node?.replaceWith(...this.toDOM());
|
|
47
35
|
return this;
|
|
48
36
|
}
|
|
49
37
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { I18nDictionaryContext, I18nDictionaryContextImporter } from "#structure/I18nDictionary";
|
|
2
2
|
|
|
3
|
+
export type I18nTranslationResult = { text: string[], args: any[] }
|
|
4
|
+
|
|
3
5
|
export type ResolvedAsyncDictionary<F extends I18nDictionaryContextImporter> = Awaited<ReturnType<F>>['default'];
|
|
4
6
|
|
|
5
7
|
export type I18nTranslationKey<T> =
|
|
@@ -39,7 +41,7 @@ export type FindParam<T extends string> =
|
|
|
39
41
|
T extends `${string}$${infer Param}$${infer Rest}`
|
|
40
42
|
? Param extends `${string}${' '}${string}`
|
|
41
43
|
? Prettify<{} & FindParam<Rest>>
|
|
42
|
-
: Prettify<Record<Param,
|
|
44
|
+
: Prettify<Record<Param, any> & FindParam<Rest>>
|
|
43
45
|
: {}
|
|
44
46
|
|
|
45
47
|
export type FindTranslationByKey<K extends string, T extends I18nDictionaryContext> =
|
|
@@ -2,14 +2,14 @@ import type { Condition } from "#structure/Condition";
|
|
|
2
2
|
import * as _Else from "#structure/Else";
|
|
3
3
|
import * as _ElseIf from "#structure/ElseIf";
|
|
4
4
|
import * as _If from "#structure/If";
|
|
5
|
-
import type {
|
|
5
|
+
import type { SignalType } from "@amateras/signal";
|
|
6
6
|
|
|
7
7
|
declare global {
|
|
8
8
|
export var If: typeof _If.If
|
|
9
9
|
export var Else: typeof _Else.Else
|
|
10
10
|
export var ElseIf: typeof _ElseIf.ElseIf
|
|
11
11
|
|
|
12
|
-
export function
|
|
13
|
-
export function
|
|
12
|
+
export function $<T>(statement: typeof _If.If, signal: SignalType<T>, layout: _If.IfLayout<T>): Condition;
|
|
13
|
+
export function $<T>(statement: typeof _ElseIf.ElseIf, signal: SignalType<T>, layout: _ElseIf.ElseIfLayout<T>): Condition;
|
|
14
14
|
export function $(statement: typeof _Else.Else, layout: _Else.ElseLayout): Condition;
|
|
15
15
|
}
|
|
@@ -23,6 +23,7 @@ export class Condition extends ProxyProto {
|
|
|
23
23
|
this.statement = matchProto ?? _null;
|
|
24
24
|
forEach(this.statements, proto => proto !== matchProto && proto.removeNode())
|
|
25
25
|
this.node?.replaceWith(...this.toDOM());
|
|
26
|
+
this.parent?.mutate();
|
|
26
27
|
}
|
|
27
28
|
// build statements proto and subscribe expression signal
|
|
28
29
|
forEach(this.statements, proto => {
|