duto 0.0.0 → 0.260808.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/LICENSE +21 -0
- package/README.md +21 -12
- package/package.json +56 -2
- package/src/cli/commands/build.ts +74 -0
- package/src/cli/commands/dev-old.ts +213 -0
- package/src/cli/commands/dev.ts +87 -0
- package/src/cli/commands/preview.ts +33 -0
- package/src/cli/index.ts +28 -0
- package/src/cli/utils.ts +146 -0
- package/src/client/app.tsx +123 -0
- package/src/client/hmr.ts +13 -0
- package/src/client/island.ts +136 -0
- package/src/client/link.ts +70 -0
- package/src/client/router.ts +329 -0
- package/src/client/storage.ts +95 -0
- package/src/compiler/aliases.ts +36 -0
- package/src/compiler/assets.ts +43 -0
- package/src/compiler/ast.ts +764 -0
- package/src/compiler/constants.ts +24 -0
- package/src/compiler/file.ts +60 -0
- package/src/compiler/index.ts +307 -0
- package/src/compiler/island.ts +108 -0
- package/src/compiler/meta.ts +601 -0
- package/src/compiler/routes.ts +29 -0
- package/src/compiler/types.ts +209 -0
- package/src/compiler/utils.ts +5 -0
- package/src/compiler/vite-plugin.ts +324 -0
- package/src/components/link.tsx +38 -0
- package/src/i18n/client.ts +403 -0
- package/src/i18n/element.ts +44 -0
- package/src/i18n/server.ts +56 -0
- package/src/index.ts +28 -0
- package/src/preact/client-dev.ts +5 -0
- package/src/preact/client.ts +87 -0
- package/src/preact/context.ts +41 -0
- package/src/preact/server.ts +186 -0
- package/src/preact/signals.ts +89 -0
- package/src/preact/static-html.ts +45 -0
- package/src/preact/types.ts +26 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import { h, type JSX } from 'preact'
|
|
2
|
+
import { local } from '#/storage'
|
|
3
|
+
import { navigate } from '../'
|
|
4
|
+
|
|
5
|
+
const accepted = (lang: string | undefined, locales: string[] = [], fallback: string) => lang && locales.includes(lang) ? lang : fallback
|
|
6
|
+
|
|
7
|
+
const splitLang = (lang: string) => {
|
|
8
|
+
let arr = []
|
|
9
|
+
if (lang.indexOf('-') > -1) {
|
|
10
|
+
arr = lang.split('-')
|
|
11
|
+
} else if (lang.indexOf('_') > -1) {
|
|
12
|
+
arr = lang.split('_')
|
|
13
|
+
} else {
|
|
14
|
+
arr = [lang]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return arr.map(v => v.toLowerCase())
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const isAccepted = (lang: string, locales: string[] = []) => {
|
|
21
|
+
if (locales.includes(lang)) return true
|
|
22
|
+
|
|
23
|
+
for (var locale in locales) {
|
|
24
|
+
if (locale == lang || lang.startsWith(locale) || lang.endsWith(locale)) return true
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return false
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const getAccepted = (lang: string, locales: string[] = [], defaultLocale = '') => {
|
|
31
|
+
if (!lang || !locales?.length) return defaultLocale
|
|
32
|
+
|
|
33
|
+
lang = lang.toLowerCase()
|
|
34
|
+
|
|
35
|
+
const spl = splitLang(lang)
|
|
36
|
+
|
|
37
|
+
for (var key in spl) {
|
|
38
|
+
if (isAccepted(spl[key], locales)) return spl[key]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return defaultLocale
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const detect = (locales: string[], defaultLocale: string) => {
|
|
45
|
+
if (!defaultLocale) defaultLocale = 'en'
|
|
46
|
+
if (typeof window === 'undefined') return defaultLocale
|
|
47
|
+
|
|
48
|
+
locales = locales.map(l => l.toLowerCase())
|
|
49
|
+
|
|
50
|
+
if (window?.navigator?.languages)
|
|
51
|
+
return window.navigator.languages.map(lang => getAccepted(lang, locales)).filter(Boolean)[0] || defaultLocale
|
|
52
|
+
|
|
53
|
+
// @ts-ignore
|
|
54
|
+
return getAccepted(window?.navigator?.language || window.navigator?.userLanguage || '', locales, defaultLocale)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
const fixDate = (str: string) => str.replace(/de/g, (match, offset, val) => val.lastIndexOf('de', offset - 1) === -1 ? match : '').replace(/\s+/g, ' ').trim()
|
|
59
|
+
|
|
60
|
+
let _locales: string[] = []
|
|
61
|
+
let _messages: Record<string, Record<string, string>> = {}
|
|
62
|
+
let _routes: Record<string, Record<string, string>> = {}
|
|
63
|
+
let _routeMap: Record<string, string> = {}
|
|
64
|
+
|
|
65
|
+
let _locale: string = ''
|
|
66
|
+
let _detected: string = ''
|
|
67
|
+
export let _prefixed: boolean = false
|
|
68
|
+
|
|
69
|
+
export const isPrefixed = () => _prefixed
|
|
70
|
+
|
|
71
|
+
export const getLocale = () => {
|
|
72
|
+
if (typeof window === 'undefined') {
|
|
73
|
+
return cached() || _detected
|
|
74
|
+
} else {
|
|
75
|
+
const matched = window?.location?.pathname.match(new RegExp(`^/(${_locales.join('|')})(/|$)`))
|
|
76
|
+
if (matched && matched[1]) {
|
|
77
|
+
_prefixed = true
|
|
78
|
+
return matched[1]
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
_prefixed = false
|
|
82
|
+
return cached() || _detected
|
|
83
|
+
// return matched && matched[1] ? matched[1] : cached() || _detected
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const setLocale = (lang: string) => {
|
|
88
|
+
cache(lang)
|
|
89
|
+
_locale = accepted(lang, _locales, _detected)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export default function i18n(
|
|
93
|
+
fallbackLocale: string,
|
|
94
|
+
locales: string[],
|
|
95
|
+
messages: Record<string, Record<string, string>>,
|
|
96
|
+
routes: Record<string, Record<string, string>>,
|
|
97
|
+
routeMap: Record<string, string>,
|
|
98
|
+
) {
|
|
99
|
+
_locales = locales
|
|
100
|
+
_messages = messages
|
|
101
|
+
_routes = routes
|
|
102
|
+
_routeMap = routeMap
|
|
103
|
+
_detected = detect(locales, fallbackLocale)
|
|
104
|
+
_locale = typeof window === 'undefined' ? fallbackLocale : getLocale()
|
|
105
|
+
|
|
106
|
+
// console.error(routes)
|
|
107
|
+
// console.error(routeMap)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export const cache = (val: string) => local.set('lang', accepted(val, _locales, ''))
|
|
111
|
+
export const cached = () => local.get('lang') as string || ''
|
|
112
|
+
|
|
113
|
+
export const updateWidth = (el: HTMLSelectElement) => {
|
|
114
|
+
if (el) {
|
|
115
|
+
const option = el.querySelector(`option[value="${el.value}"]`) as HTMLOptionElement
|
|
116
|
+
const length = option.textContent.split(' ')[0]?.length || 0
|
|
117
|
+
el.style.width = (length * 15.7)+ 'px'
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
export const updatePickers = (lang?: string) => {
|
|
121
|
+
lang ??= accepted(lang, _locales, _locale)
|
|
122
|
+
|
|
123
|
+
document.querySelectorAll('.locale-select').forEach(el => {
|
|
124
|
+
const select = el.querySelector('select')
|
|
125
|
+
if (select) {
|
|
126
|
+
select.value = lang
|
|
127
|
+
updateWidth(select)
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export const route = (route = '', lang?: string) => {
|
|
133
|
+
lang ??= accepted(lang, _locales, _locale)
|
|
134
|
+
const path = _routeMap[route]
|
|
135
|
+
return _routes[lang][path ? path : route] || _routes[lang]?.notfound || undefined
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export const redirect = (key = '', lang?: string) => r(key, lang)
|
|
139
|
+
export const r = (key: string, lang?: string) => {
|
|
140
|
+
lang ??= accepted(lang, _locales, _locale)
|
|
141
|
+
const path = '/'+ lang + (key ? '/'+ route(key, lang) : '')
|
|
142
|
+
|
|
143
|
+
if (!location.pathname.endsWith(path))
|
|
144
|
+
navigate(path)
|
|
145
|
+
|
|
146
|
+
// return '/' + lang + '/' + (key in _routes[lang] ? _routes[lang][key] : key)
|
|
147
|
+
}
|
|
148
|
+
// export const redirect = (r = '', lang?: string) => {
|
|
149
|
+
// lang = accepted(lang, _locales, _locale)
|
|
150
|
+
// const path = '/'+ lang + (r ? '/'+ route(r, lang) : '')
|
|
151
|
+
|
|
152
|
+
// if (!location.pathname.endsWith(path))
|
|
153
|
+
// location.pathname = path
|
|
154
|
+
// }
|
|
155
|
+
|
|
156
|
+
////////////////////////////////////////////////////////////////////////////////
|
|
157
|
+
|
|
158
|
+
type Primitives = string | number | boolean
|
|
159
|
+
|
|
160
|
+
type TFunc = {
|
|
161
|
+
(key: string, lang?: string): string
|
|
162
|
+
(key: string, params: Record<string, Primitives>, lang?: string): string
|
|
163
|
+
(key: string, params: Primitives[], lang?: string): string
|
|
164
|
+
(strings: TemplateStringsArray, ...values: Primitives[]): string & JSX.Element
|
|
165
|
+
(props: TProps): JSX.Element
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
type TProps = {
|
|
169
|
+
s?: string
|
|
170
|
+
children?: string
|
|
171
|
+
lang?: string
|
|
172
|
+
params?: Record<string, Primitives> | Primitives[]
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const lookup = (key: string, lang: string): string => {
|
|
176
|
+
if (!key) return '';
|
|
177
|
+
if (key.startsWith('_')) return key.replace(/^_+/, '');
|
|
178
|
+
const msg = _messages[lang] ?? _messages[_locale];
|
|
179
|
+
return (key in msg ? msg[key] : key) as string;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const interpolate = (template: string, params: Record<string, Primitives> | Primitives[]): string => {
|
|
183
|
+
if (Array.isArray(params))
|
|
184
|
+
return template.replace(/\{(\d+)\}/g, (_, i) => String(params[Number(i)] ?? `{${i}}`))
|
|
185
|
+
|
|
186
|
+
return template.replace(/\{(\w+)\}/g, (_, k) => String(params[k] ?? `{${k}}`))
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const taggedKey = (strings: TemplateStringsArray, values: Primitives[]): string =>
|
|
190
|
+
strings.reduce((acc, s, i) => acc + s + (values[i] !== undefined ? `%${i}%` : ''), '');
|
|
191
|
+
|
|
192
|
+
const resolveTagged = (strings: TemplateStringsArray, values: Primitives[], lang: string): string => {
|
|
193
|
+
const template = lookup(taggedKey(strings, values), lang)
|
|
194
|
+
return values.reduce((acc, val, i) => acc.replace(`%${i}%`, String(val)), template)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// --- VNode que se comporta como string em qualquer coerção ---
|
|
198
|
+
const hybridNode = (key: string, resolved: string, lang?: string, params?: TProps['params']) => {
|
|
199
|
+
const attrs: Record<string, string> = { s: key, children: resolved }
|
|
200
|
+
if (lang) attrs.lang = lang
|
|
201
|
+
if (params) attrs.params = JSON.stringify(params)
|
|
202
|
+
|
|
203
|
+
const vnode = h('i-i', attrs) as unknown as Record<string | symbol, unknown>
|
|
204
|
+
|
|
205
|
+
const _attrs = { value: () => resolved, enumerable: false }
|
|
206
|
+
Object.defineProperty(vnode, 'toString', _attrs)
|
|
207
|
+
Object.defineProperty(vnode, 'valueOf', _attrs)
|
|
208
|
+
vnode[Symbol.toPrimitive] = () => resolved
|
|
209
|
+
|
|
210
|
+
return vnode as unknown as string & JSX.Element
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const isJSXProps = (val: unknown): val is TProps =>
|
|
214
|
+
typeof val === 'object' && val !== null && !Array.isArray(val) && !('raw' in val)
|
|
215
|
+
|
|
216
|
+
export const t = ((first: string | TemplateStringsArray | TProps, ...rest: unknown[]): unknown => {
|
|
217
|
+
|
|
218
|
+
// --- JSX: <t s='home' /> ou <t>home</t> ---
|
|
219
|
+
if (isJSXProps(first)) {
|
|
220
|
+
const { s, children, lang, params } = first;
|
|
221
|
+
const key = s ?? children ?? '';
|
|
222
|
+
const resolved = params
|
|
223
|
+
? interpolate(lookup(key, lang ?? _locale), params)
|
|
224
|
+
: lookup(key, lang ?? _locale);
|
|
225
|
+
|
|
226
|
+
return hybridNode(key, resolved, lang, params)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// --- Tagged template: t`home` ou t`Bem-vindo ${name}` ---
|
|
230
|
+
if (Array.isArray(first) && 'raw' in first) {
|
|
231
|
+
const strings = first as TemplateStringsArray;
|
|
232
|
+
const values = rest as Primitives[];
|
|
233
|
+
const key = taggedKey(strings, values);
|
|
234
|
+
const resolved = resolveTagged(strings, values, _locale);
|
|
235
|
+
|
|
236
|
+
return hybridNode(key, resolved);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// --- Chamada normal: t('key', ...) ---
|
|
240
|
+
const key = first as string;
|
|
241
|
+
const lang = () => typeof rest[rest.length - 1] === 'string' ? rest[rest.length - 1] as string : _locale;
|
|
242
|
+
|
|
243
|
+
if (typeof rest[0] === 'object' && rest[0] !== null)
|
|
244
|
+
return interpolate(lookup(key, lang()), rest[0] as Record<string, Primitives> | Primitives[]);
|
|
245
|
+
|
|
246
|
+
return lookup(key, rest[0] && typeof rest[0] === 'string' ? rest[0] : _locale)
|
|
247
|
+
}) as TFunc
|
|
248
|
+
|
|
249
|
+
export const tl = (lang: string) =>
|
|
250
|
+
(strings: TemplateStringsArray, ...values: Primitives[]): string =>
|
|
251
|
+
resolveTagged(strings, values, lang);
|
|
252
|
+
////////////////////////////////////////////////////////////////////////////////
|
|
253
|
+
|
|
254
|
+
// export const t = (key: string, lang?: string) => {
|
|
255
|
+
// lang ??= accepted(lang, _locales, _locale)
|
|
256
|
+
// const msg = _messages[lang]
|
|
257
|
+
|
|
258
|
+
// if (key && !key.startsWith('_') && key in msg)
|
|
259
|
+
// return msg[key]
|
|
260
|
+
|
|
261
|
+
// return key?.replace(/^_+/, '')
|
|
262
|
+
// }
|
|
263
|
+
export const d = (str: string, format?: string, lang?: string) => {
|
|
264
|
+
lang ??= accepted(lang, _locales, _locale)
|
|
265
|
+
|
|
266
|
+
const date = new Date(str+ 'T00:00:00')
|
|
267
|
+
if (isNaN(date.getTime())) return str
|
|
268
|
+
|
|
269
|
+
if (format && !['short', 'long'].includes(format)) {
|
|
270
|
+
const year = date.getFullYear(),
|
|
271
|
+
month = date.getMonth(),
|
|
272
|
+
day = date.getDate(),
|
|
273
|
+
formatter = new Intl.DateTimeFormat(lang, {month: 'long'}),
|
|
274
|
+
monthName = formatter.format(date),
|
|
275
|
+
shortFormatter = new Intl.DateTimeFormat(lang, {month: 'short'}),
|
|
276
|
+
shortMonthName = shortFormatter.format(date)
|
|
277
|
+
|
|
278
|
+
return fixDate(format
|
|
279
|
+
.replace(/YYYY/g, year.toString())
|
|
280
|
+
.replace(/YY/g, year.toString().slice(-2))
|
|
281
|
+
.replace(/MMMM/g, monthName)
|
|
282
|
+
.replace(/MMM/g, shortMonthName)
|
|
283
|
+
.replace(/MM/g, (month + 1).toString().padStart(2, '0'))
|
|
284
|
+
.replace(/M/g, (month + 1).toString())
|
|
285
|
+
.replace(/DD/g, day.toString().padStart(2, '0'))
|
|
286
|
+
.replace(/D/g, day.toString()))
|
|
287
|
+
// .replace(/de/g, (match, offset, val) => val.lastIndexOf('de', offset - 1) === -1 ? match : '')
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return fixDate(new Intl.DateTimeFormat(lang, {
|
|
291
|
+
// dateStyle: 'full',
|
|
292
|
+
year: 'numeric',
|
|
293
|
+
month: format == 'short' ? 'short' : 'long',
|
|
294
|
+
day: 'numeric',
|
|
295
|
+
}).format(date))
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// export default function i18n(
|
|
299
|
+
// fallbackLocale: string,
|
|
300
|
+
// locales: string[],
|
|
301
|
+
// messages: Record<string, Record<string, string>>,
|
|
302
|
+
// routes: Record<string, Record<string, string>>,
|
|
303
|
+
// routeMap: Record<string, string>,
|
|
304
|
+
// ) {
|
|
305
|
+
// // const fallbackLocale = 'pt-br'
|
|
306
|
+
// // const locales = ['pt-br', 'en']
|
|
307
|
+
|
|
308
|
+
// const localeRegex = new RegExp(`^/(${locales.join('|')})(/|$)`)
|
|
309
|
+
// const matched = window.location.pathname.match(localeRegex)
|
|
310
|
+
|
|
311
|
+
// const detected = detect(locales, fallbackLocale)
|
|
312
|
+
// const locale = matched ? matched[1] : detected
|
|
313
|
+
|
|
314
|
+
// return {
|
|
315
|
+
// detected,
|
|
316
|
+
// locale,
|
|
317
|
+
// locales,
|
|
318
|
+
// cache(val: string) {
|
|
319
|
+
// localStorage.setItem('lang', accepted(val, locales, ''))
|
|
320
|
+
// },
|
|
321
|
+
// cached() {
|
|
322
|
+
// return localStorage.lang || ''
|
|
323
|
+
// },
|
|
324
|
+
// updateWidth (el: HTMLSelectElement) {
|
|
325
|
+
// if (el) {
|
|
326
|
+
// const option = el.querySelector(`option[value="${el.value}"]`) as HTMLOptionElement
|
|
327
|
+
// const length = option.textContent.split(' ')[0]?.length || 0
|
|
328
|
+
// el.style.width = (length * 15.7)+ 'px'
|
|
329
|
+
// }
|
|
330
|
+
// },
|
|
331
|
+
// updatePickers(lang = '') {
|
|
332
|
+
// lang = accepted(lang, locales, locale)
|
|
333
|
+
// document.querySelectorAll('.locale-select').forEach(el => {
|
|
334
|
+
// const select = el.querySelector('select')
|
|
335
|
+
// if (select) {
|
|
336
|
+
// select.value = lang
|
|
337
|
+
// this.updateWidth(select)
|
|
338
|
+
// }
|
|
339
|
+
// })
|
|
340
|
+
// },
|
|
341
|
+
// route(route = '', lang?: string) {
|
|
342
|
+
// lang = accepted(lang, locales, locale)
|
|
343
|
+
// const path = routeMap[route]
|
|
344
|
+
// return routes[lang][path ? path : route] || routes[lang]?.notfound || undefined
|
|
345
|
+
// },
|
|
346
|
+
// redirect(route = '', lang?: string) {
|
|
347
|
+
// lang = accepted(lang, locales, locale)
|
|
348
|
+
// const path = '/'+ lang + (route ? '/'+ this.route(route, lang) : '')
|
|
349
|
+
|
|
350
|
+
// if (!window.location.pathname.endsWith(path))
|
|
351
|
+
// window.location.pathname = path
|
|
352
|
+
// },
|
|
353
|
+
|
|
354
|
+
// t(key: string, lang?: string) {
|
|
355
|
+
// lang ??= accepted(lang, locales, locale)
|
|
356
|
+
// const msg = messages[lang]
|
|
357
|
+
|
|
358
|
+
// if (key && !key.startsWith('_') && key in msg)
|
|
359
|
+
// return msg[key]
|
|
360
|
+
|
|
361
|
+
// return key?.replace(/^_+/, '')
|
|
362
|
+
// },
|
|
363
|
+
// r(key: string, lang?: string) {
|
|
364
|
+
// lang ??= accepted(lang, locales, locale)
|
|
365
|
+
|
|
366
|
+
// return '/' + lang + '/' + (key in routes[lang] ? routes[lang][key] : key)
|
|
367
|
+
// },
|
|
368
|
+
// d(str: string, format?: string, lang?: string) {
|
|
369
|
+
// lang ??= accepted(lang, locales, locale)
|
|
370
|
+
|
|
371
|
+
// const date = new Date(str+ 'T00:00:00')
|
|
372
|
+
// if (isNaN(date.getTime())) return str
|
|
373
|
+
|
|
374
|
+
// if (format && !['short', 'long'].includes(format)) {
|
|
375
|
+
// const year = date.getFullYear(),
|
|
376
|
+
// month = date.getMonth(),
|
|
377
|
+
// day = date.getDate(),
|
|
378
|
+
// formatter = new Intl.DateTimeFormat(lang, {month: 'long'}),
|
|
379
|
+
// monthName = formatter.format(date),
|
|
380
|
+
// shortFormatter = new Intl.DateTimeFormat(lang, {month: 'short'}),
|
|
381
|
+
// shortMonthName = shortFormatter.format(date)
|
|
382
|
+
|
|
383
|
+
// return fixDate(format
|
|
384
|
+
// .replace(/YYYY/g, year.toString())
|
|
385
|
+
// .replace(/YY/g, year.toString().slice(-2))
|
|
386
|
+
// .replace(/MMMM/g, monthName)
|
|
387
|
+
// .replace(/MMM/g, shortMonthName)
|
|
388
|
+
// .replace(/MM/g, (month + 1).toString().padStart(2, '0'))
|
|
389
|
+
// .replace(/M/g, (month + 1).toString())
|
|
390
|
+
// .replace(/DD/g, day.toString().padStart(2, '0'))
|
|
391
|
+
// .replace(/D/g, day.toString()))
|
|
392
|
+
// // .replace(/de/g, (match, offset, val) => val.lastIndexOf('de', offset - 1) === -1 ? match : '')
|
|
393
|
+
// }
|
|
394
|
+
|
|
395
|
+
// return fixDate(new Intl.DateTimeFormat(lang, {
|
|
396
|
+
// // dateStyle: 'full',
|
|
397
|
+
// year: 'numeric',
|
|
398
|
+
// month: format == 'short' ? 'short' : 'long',
|
|
399
|
+
// day: 'numeric',
|
|
400
|
+
// }).format(date))
|
|
401
|
+
// },
|
|
402
|
+
// }
|
|
403
|
+
// }
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { local } from '#/storage'
|
|
2
|
+
import { t, _prefixed } from './client'
|
|
3
|
+
|
|
4
|
+
export default class EI18n extends HTMLElement {
|
|
5
|
+
static observedAttributes = ['s']
|
|
6
|
+
#builded = false
|
|
7
|
+
#unsubscribe: (() => void) | null = null
|
|
8
|
+
|
|
9
|
+
connectedCallback() {
|
|
10
|
+
if (!_prefixed) // @ts-ignore // TODO: talvez usar função para obter o valor atual..
|
|
11
|
+
this.#unsubscribe = local.subscribe('lang', e => this.build(e.val))
|
|
12
|
+
|
|
13
|
+
if (document.readyState === 'interactive' || document.readyState === 'complete') {
|
|
14
|
+
this.build()
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
setTimeout(() => {
|
|
19
|
+
this.build()
|
|
20
|
+
}, 0)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
attributeChangedCallback() {
|
|
24
|
+
if (this.#builded)
|
|
25
|
+
this.build()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
disconnectedCallback() {
|
|
29
|
+
if (this.#unsubscribe) {
|
|
30
|
+
this.#unsubscribe?.()
|
|
31
|
+
this.#unsubscribe = null
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
build(val?: string) {
|
|
36
|
+
// if (!this.isConnected) return
|
|
37
|
+
const key = this.getAttribute('s')
|
|
38
|
+
if (!key) return
|
|
39
|
+
|
|
40
|
+
this.innerHTML = t(key, val)
|
|
41
|
+
|
|
42
|
+
this.#builded = true
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { join } from 'node:path'
|
|
2
|
+
import { IMPORT, JSJSON } from 't0n'
|
|
3
|
+
|
|
4
|
+
import type { Metadata, I18nMetadata } from '@/types'
|
|
5
|
+
import { existsSync } from 'node:fs'
|
|
6
|
+
|
|
7
|
+
export default async function getI18n(meta: Metadata) {
|
|
8
|
+
let file = ''
|
|
9
|
+
|
|
10
|
+
for (const variant of [
|
|
11
|
+
'i18n.ts',
|
|
12
|
+
'i18n/index.ts',
|
|
13
|
+
]) {
|
|
14
|
+
const path = join(meta.root, variant)
|
|
15
|
+
if (existsSync(path)) {
|
|
16
|
+
file = path
|
|
17
|
+
break
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!file) return {} as I18nMetadata
|
|
22
|
+
const mod = await IMPORT<I18nMetadata>(file)
|
|
23
|
+
|
|
24
|
+
// @ts-ignore
|
|
25
|
+
if (['locales', 'fallbackLocale', 'messages', 'routes', 'options'].some(i => !mod[i]))
|
|
26
|
+
return {} as I18nMetadata
|
|
27
|
+
|
|
28
|
+
const i18n: I18nMetadata = {
|
|
29
|
+
...mod,
|
|
30
|
+
routeMap: Object.fromEntries(
|
|
31
|
+
Object.values(mod.routes)
|
|
32
|
+
.map(paths => Object.entries(paths).map(([key, path]) => path == key ? 0 : [path, key]).filter(Boolean))
|
|
33
|
+
.flat() as Array<[string, string]>
|
|
34
|
+
),
|
|
35
|
+
pageRoutes: [],
|
|
36
|
+
string: '',
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
Object.entries(mod.routes)
|
|
40
|
+
.forEach(([locale, paths]) => {
|
|
41
|
+
Object.entries(paths)
|
|
42
|
+
.forEach(([route, path]) => i18n.pageRoutes.push({ locale, path }))
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
i18n.string = `export const fallbackLocale = ${JSJSON(mod.fallbackLocale)}
|
|
46
|
+
export const locales = ${JSJSON(mod.locales)}
|
|
47
|
+
export const messages = ${JSJSON(mod.messages)}
|
|
48
|
+
export const routes = ${JSJSON(mod.routes)}
|
|
49
|
+
export const routeMap = ${JSJSON(i18n.routeMap)}
|
|
50
|
+
export const pageRoutes = ${JSJSON(i18n.pageRoutes)}
|
|
51
|
+
export const options = ${JSJSON(mod.options)}
|
|
52
|
+
`
|
|
53
|
+
|
|
54
|
+
// console.error(i18n.string)
|
|
55
|
+
return i18n
|
|
56
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export {
|
|
2
|
+
Redirect,
|
|
3
|
+
useLocation, useParams, useSearch, useSearchParams,
|
|
4
|
+
matchRoute,
|
|
5
|
+
} from 'wouter-preact'
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
t, r, d,
|
|
9
|
+
getLocale, setLocale,
|
|
10
|
+
isPrefixed,
|
|
11
|
+
} from './i18n/client'
|
|
12
|
+
|
|
13
|
+
export { local } from './client/storage'
|
|
14
|
+
|
|
15
|
+
export const navigate = <S = any>(
|
|
16
|
+
to: string | URL,
|
|
17
|
+
options?: { replace?: boolean; state?: S; transition?: boolean }
|
|
18
|
+
) => {
|
|
19
|
+
if (window?.$duto?.sroutes?.has(to)) {
|
|
20
|
+
location.pathname = to
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const n = window?.$duto?.navigate || useLocation()[1]
|
|
25
|
+
n(to, options)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { Link } from './components/link'
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { h, hydrate, render } from 'preact'
|
|
2
|
+
import StaticHtml from './static-html'
|
|
3
|
+
import type { SignalLike } from './types'
|
|
4
|
+
|
|
5
|
+
const sharedSignalMap = new Map<string, SignalLike>()
|
|
6
|
+
|
|
7
|
+
type MaskId = number | string
|
|
8
|
+
|
|
9
|
+
function getIslandMaskId(el: HTMLElement): MaskId | null {
|
|
10
|
+
return el.id || null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function setVNodeMask(child: any, maskId: MaskId) {
|
|
14
|
+
const mask: [MaskId, number] = [maskId, 0]
|
|
15
|
+
child._mask = mask
|
|
16
|
+
child.__m = mask
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default async (
|
|
20
|
+
el: HTMLElement,
|
|
21
|
+
Component: unknown,
|
|
22
|
+
props: Record<string, unknown>,
|
|
23
|
+
{ default: children, ...slotted }: Record<string, unknown>,
|
|
24
|
+
): Promise<void> => {
|
|
25
|
+
for (const [key, value] of Object.entries(slotted)) // @ts-ignore
|
|
26
|
+
props[key] = h(StaticHtml, { value, name: key })
|
|
27
|
+
|
|
28
|
+
const signalsRaw = el.dataset.set
|
|
29
|
+
if (signalsRaw) {
|
|
30
|
+
const { signal } = await import('@preact/signals')
|
|
31
|
+
const signals: Record<string, string | [string, number][]> = JSON.parse(signalsRaw)
|
|
32
|
+
|
|
33
|
+
for (const [propName, signalId] of Object.entries(signals)) {
|
|
34
|
+
if (Array.isArray(signalId)) {
|
|
35
|
+
signalId.forEach(([id, indexOrKeyInProps]) => {
|
|
36
|
+
const mapValue = (props[propName] as any)[indexOrKeyInProps]
|
|
37
|
+
let valueOfSignal = mapValue
|
|
38
|
+
let resolvedKey: string | number = indexOrKeyInProps
|
|
39
|
+
|
|
40
|
+
if (typeof indexOrKeyInProps !== 'string') {
|
|
41
|
+
valueOfSignal = mapValue[0]
|
|
42
|
+
resolvedKey = mapValue[1]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!sharedSignalMap.has(id))
|
|
46
|
+
sharedSignalMap.set(id, signal(valueOfSignal))
|
|
47
|
+
|
|
48
|
+
// @ts-ignore
|
|
49
|
+
props[propName][resolvedKey] = sharedSignalMap.get(id)
|
|
50
|
+
})
|
|
51
|
+
} else {
|
|
52
|
+
if (!sharedSignalMap.has(signalId))
|
|
53
|
+
sharedSignalMap.set(signalId, signal(props[propName]))
|
|
54
|
+
|
|
55
|
+
props[propName] = sharedSignalMap.get(signalId)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const rawChildren = children ?? props.children
|
|
61
|
+
delete props.children
|
|
62
|
+
|
|
63
|
+
const child = h(
|
|
64
|
+
Component as any,
|
|
65
|
+
props,
|
|
66
|
+
rawChildren == null
|
|
67
|
+
? undefined
|
|
68
|
+
: typeof rawChildren === 'string'
|
|
69
|
+
? h(StaticHtml, { value: rawChildren })
|
|
70
|
+
: rawChildren
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
const islandMaskId = getIslandMaskId(el)
|
|
74
|
+
if (islandMaskId !== null)
|
|
75
|
+
setVNodeMask(child, islandMaskId)
|
|
76
|
+
|
|
77
|
+
hydrate(child, el)
|
|
78
|
+
|
|
79
|
+
el.addEventListener('d:unmount', () => {
|
|
80
|
+
render(null, el)
|
|
81
|
+
|
|
82
|
+
// 2. Remove o elemento do DOM apenas após o desmonte,
|
|
83
|
+
// usando microtask para garantir que o Preact finalizou
|
|
84
|
+
// o ciclo de reconciliação antes da remoção física
|
|
85
|
+
Promise.resolve().then(() => el.remove())
|
|
86
|
+
}, { once: true })
|
|
87
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { PropNameToSignalMap, SignalLike } from './types'
|
|
2
|
+
|
|
3
|
+
export type Context = {
|
|
4
|
+
id: string;
|
|
5
|
+
c: number;
|
|
6
|
+
islandCount: number;
|
|
7
|
+
signals: Map<SignalLike, string>;
|
|
8
|
+
propsToSignals: Map<Record<string, any>, PropNameToSignalMap>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const contexts = new Map<string, Context>()
|
|
12
|
+
|
|
13
|
+
export function getContext(id: string): Context {
|
|
14
|
+
if (contexts.has(id))
|
|
15
|
+
return contexts.get(id)!
|
|
16
|
+
|
|
17
|
+
let ctx = {
|
|
18
|
+
c: 0,
|
|
19
|
+
islandCount: 0,
|
|
20
|
+
get id() {
|
|
21
|
+
return 'p' + this.c.toString()
|
|
22
|
+
},
|
|
23
|
+
signals: new Map(),
|
|
24
|
+
propsToSignals: new Map(),
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
contexts.set(id, ctx)
|
|
28
|
+
return ctx
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function incrementId(ctx: Context): string {
|
|
32
|
+
let id = ctx.id
|
|
33
|
+
ctx.c++
|
|
34
|
+
return id
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function incrementIslandId(ctx: Context): number {
|
|
38
|
+
const islandId = ctx.islandCount
|
|
39
|
+
ctx.islandCount++
|
|
40
|
+
return islandId
|
|
41
|
+
}
|