@yozora-tech/qfluent 0.1.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 +20 -0
- package/README.md +39 -0
- package/lib/assets/DVjQdr_6-bundle-graph.json +1 -0
- package/lib/assets/l4W0yvKV-bundle-graph.json +1 -0
- package/lib/i18n.d.ts +2 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.qwik.cjs +13 -0
- package/lib/index.qwik.mjs +13 -0
- package/lib/q-manifest.json +69 -0
- package/lib/qfluent/context.d.ts +54 -0
- package/lib/qfluent/context.qwik.cjs +195 -0
- package/lib/qfluent/context.qwik.mjs +173 -0
- package/lib/qfluent/mod.d.ts +4 -0
- package/lib/qfluent/tools.d.ts +9 -0
- package/lib/qfluent/tools.qwik.cjs +13 -0
- package/lib/qfluent/tools.qwik.mjs +13 -0
- package/lib/qfluent/translate.d.ts +33 -0
- package/lib/qfluent/translate.qwik.cjs +88 -0
- package/lib/qfluent/translate.qwik.mjs +88 -0
- package/lib/qfluent/types.d.ts +129 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (C) 2026 Yozora Tech LTD
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
7
|
+
the Software without restriction, including without limitation the rights to
|
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
10
|
+
subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# qfluent
|
|
2
|
+
|
|
3
|
+
ProjectFluent.org translations support for Qwik framework v2.
|
|
4
|
+
|
|
5
|
+
## Synopsis
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { T, useT } from "@yozora-tech/qfluent";
|
|
9
|
+
|
|
10
|
+
<T s="hello-world" />
|
|
11
|
+
|
|
12
|
+
const t_search_empty = useT("search-the-web");
|
|
13
|
+
<input type="search" placeholder={t_search_empty.value} />
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Links
|
|
17
|
+
|
|
18
|
+
- Document: https://qfluent.yozora.tech/
|
|
19
|
+
- Source: [site/src/routes/en/index.mdx](./site/src/routes/en/index.mdx)
|
|
20
|
+
- npm: https://www.npmjs.com/package/@yozora-tech/qfluent
|
|
21
|
+
- jsr: https://jsr.io/@yozora-tech/qfluent
|
|
22
|
+
|
|
23
|
+
## AI (LLM) Usage
|
|
24
|
+
|
|
25
|
+
### By Human
|
|
26
|
+
|
|
27
|
+
- `src/qfluent/*`
|
|
28
|
+
- `site/src/routes/*`
|
|
29
|
+
|
|
30
|
+
### By LLM
|
|
31
|
+
|
|
32
|
+
- `src/tests/*`
|
|
33
|
+
- `site/src/*.css`
|
|
34
|
+
|
|
35
|
+
## Legal
|
|
36
|
+
|
|
37
|
+
Copyright © 2026 Yozora Tech LTD. [MIT/Expat](./LICENSE) License.
|
|
38
|
+
|
|
39
|
+
Feel free to use the idea.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
["../index.qwik.cjs",4,5,"../qfluent/context.qwik.cjs","../qfluent/tools.qwik.cjs","../qfluent/translate.qwik.cjs",3]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
["../index.qwik.mjs",4,5,"../qfluent/context.qwik.mjs","../qfluent/tools.qwik.mjs","../qfluent/translate.qwik.mjs",3]
|
package/lib/i18n.d.ts
ADDED
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./qfluent/mod";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const context = require("./qfluent/context.qwik.cjs");
|
|
4
|
+
const translate = require("./qfluent/translate.qwik.cjs");
|
|
5
|
+
const tools = require("./qfluent/tools.qwik.cjs");
|
|
6
|
+
exports.FluentProvider = context.FluentProvider;
|
|
7
|
+
exports.useFluentErrors = context.useFluentErrors;
|
|
8
|
+
exports.useFluentLocales = context.useFluentLocales;
|
|
9
|
+
exports.useFluentResources = context.useFluentResources;
|
|
10
|
+
exports.T = translate.T;
|
|
11
|
+
exports.useLocalizedTitle = translate.useLocalizedTitle;
|
|
12
|
+
exports.useT = translate.useT;
|
|
13
|
+
exports.parseAcceptLanguage = tools.parseAcceptLanguage;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { FluentProvider, useFluentErrors, useFluentLocales, useFluentResources } from "./qfluent/context.qwik.mjs";
|
|
2
|
+
import { T, useLocalizedTitle, useT } from "./qfluent/translate.qwik.mjs";
|
|
3
|
+
import { parseAcceptLanguage } from "./qfluent/tools.qwik.mjs";
|
|
4
|
+
export {
|
|
5
|
+
FluentProvider,
|
|
6
|
+
T,
|
|
7
|
+
parseAcceptLanguage,
|
|
8
|
+
useFluentErrors,
|
|
9
|
+
useFluentLocales,
|
|
10
|
+
useFluentResources,
|
|
11
|
+
useLocalizedTitle,
|
|
12
|
+
useT
|
|
13
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1",
|
|
3
|
+
"manifestHash": "m6gwie",
|
|
4
|
+
"options": {
|
|
5
|
+
"target": "lib",
|
|
6
|
+
"buildMode": "development",
|
|
7
|
+
"entryStrategy": {
|
|
8
|
+
"type": "inline"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"bundleGraphAsset": "assets/DVjQdr_6-bundle-graph.json",
|
|
12
|
+
"injections": [],
|
|
13
|
+
"mapping": {},
|
|
14
|
+
"bundles": {
|
|
15
|
+
"../index.qwik.cjs": {
|
|
16
|
+
"size": 632,
|
|
17
|
+
"total": 17193,
|
|
18
|
+
"imports": [
|
|
19
|
+
"../qfluent/context.qwik.cjs",
|
|
20
|
+
"../qfluent/tools.qwik.cjs",
|
|
21
|
+
"../qfluent/translate.qwik.cjs"
|
|
22
|
+
],
|
|
23
|
+
"origins": [
|
|
24
|
+
"src/index.ts"
|
|
25
|
+
],
|
|
26
|
+
"interactivity": 0
|
|
27
|
+
},
|
|
28
|
+
"../qfluent/context.qwik.cjs": {
|
|
29
|
+
"size": 6830,
|
|
30
|
+
"total": 6830,
|
|
31
|
+
"origins": [
|
|
32
|
+
"src/qfluent/context.tsx"
|
|
33
|
+
],
|
|
34
|
+
"interactivity": 0
|
|
35
|
+
},
|
|
36
|
+
"../qfluent/tools.qwik.cjs": {
|
|
37
|
+
"size": 505,
|
|
38
|
+
"total": 505,
|
|
39
|
+
"origins": [
|
|
40
|
+
"src/qfluent/tools.ts"
|
|
41
|
+
],
|
|
42
|
+
"interactivity": 0
|
|
43
|
+
},
|
|
44
|
+
"../qfluent/translate.qwik.cjs": {
|
|
45
|
+
"size": 2396,
|
|
46
|
+
"total": 9226,
|
|
47
|
+
"imports": [
|
|
48
|
+
"../qfluent/context.qwik.cjs"
|
|
49
|
+
],
|
|
50
|
+
"origins": [
|
|
51
|
+
"src/qfluent/translate.tsx"
|
|
52
|
+
],
|
|
53
|
+
"interactivity": 0
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"assets": {
|
|
57
|
+
"assets/DVjQdr_6-bundle-graph.json": {
|
|
58
|
+
"name": "bundle-graph.json",
|
|
59
|
+
"size": 7
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"symbols": {},
|
|
63
|
+
"platform": {
|
|
64
|
+
"vite": "",
|
|
65
|
+
"rollup": "4.62.2",
|
|
66
|
+
"env": "deno",
|
|
67
|
+
"os": "linux"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { NoSerialize, Signal } from "@qwik.dev/core";
|
|
2
|
+
import type { FluentProviderProps, QwikFluentBundleMap, QwikFluentError, QwikFluentResourceMap } from "./types";
|
|
3
|
+
/** @internal */
|
|
4
|
+
export declare const FluentLocalesId: import("@qwik.dev/core").ContextId<Signal<string[]>>;
|
|
5
|
+
/** @internal */
|
|
6
|
+
export declare const FluentResourcesId: import("@qwik.dev/core").ContextId<Signal<NoSerialize<QwikFluentResourceMap>>>;
|
|
7
|
+
/** @internal */
|
|
8
|
+
export declare const FluentBundlesId: import("@qwik.dev/core").ContextId<Signal<NoSerialize<QwikFluentBundleMap>>>;
|
|
9
|
+
/** @internal */
|
|
10
|
+
export declare const FluentErrorsId: import("@qwik.dev/core").ContextId<Signal<NoSerialize<QwikFluentError[]>>>;
|
|
11
|
+
/**
|
|
12
|
+
* Signal to list of preferred locales. Translations are picked from these languages, with fallbacks as needed.
|
|
13
|
+
*
|
|
14
|
+
* Normally you want to set this to `preferred_languages.concat(navigator.languages)`.
|
|
15
|
+
*
|
|
16
|
+
* ```tsx
|
|
17
|
+
* const locales = useFluentLocales();
|
|
18
|
+
* return (
|
|
19
|
+
* <select onChange$={(event, select) => {
|
|
20
|
+
* locales.value = [select.value].concat(navigator.languages);
|
|
21
|
+
* }}>
|
|
22
|
+
* <option value="ar">Arabic</option>
|
|
23
|
+
* <option value="en">English</option>
|
|
24
|
+
* <option value="ja">Japanese</option>
|
|
25
|
+
* ...
|
|
26
|
+
* </select>
|
|
27
|
+
* );
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare function useFluentLocales(): Signal<string[]>;
|
|
31
|
+
/**
|
|
32
|
+
* Includes your resources and compiled FluentBundles by locales.
|
|
33
|
+
*
|
|
34
|
+
* You can make use of your custom resource properties with this.
|
|
35
|
+
*/
|
|
36
|
+
export declare function useFluentResources<T extends QwikFluentResourceMap>(): Signal<T | undefined>;
|
|
37
|
+
/**
|
|
38
|
+
* Useful for debugging errors in resources.
|
|
39
|
+
*
|
|
40
|
+
* You can clear the errors after use.
|
|
41
|
+
*/
|
|
42
|
+
export declare function useFluentErrors(): Signal<QwikFluentError[]>;
|
|
43
|
+
/**
|
|
44
|
+
* Use this in `src/root.tsx`.
|
|
45
|
+
*
|
|
46
|
+
* ```tsx
|
|
47
|
+
* <body>
|
|
48
|
+
* <FluentProvider resources={...}>
|
|
49
|
+
* <RouterOutlet />
|
|
50
|
+
* </FluentProvider>
|
|
51
|
+
* </body>
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export declare const FluentProvider: import("@qwik.dev/core").Component<FluentProviderProps>;
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (let key of __getOwnPropNames(from))
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
12
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
13
|
+
}
|
|
14
|
+
return to;
|
|
15
|
+
};
|
|
16
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
17
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
18
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
19
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
20
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
21
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
|
+
mod
|
|
23
|
+
));
|
|
24
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
25
|
+
const core = require("@qwik.dev/core");
|
|
26
|
+
const bundle = require("@fluent/bundle");
|
|
27
|
+
const FluentLocalesId = core.createContextId("QwikFluentLocales");
|
|
28
|
+
const FluentResourcesId = core.createContextId("QwikFluentResources");
|
|
29
|
+
const FluentBundlesId = core.createContextId("QwikFluentBundles");
|
|
30
|
+
const FluentErrorsId = core.createContextId("QwikFluentErrors");
|
|
31
|
+
function useFluentLocales() {
|
|
32
|
+
return core.useContext(FluentLocalesId);
|
|
33
|
+
}
|
|
34
|
+
function useFluentResources() {
|
|
35
|
+
return core.useContext(FluentResourcesId);
|
|
36
|
+
}
|
|
37
|
+
function useFluentErrors() {
|
|
38
|
+
return core.useContext(FluentErrorsId);
|
|
39
|
+
}
|
|
40
|
+
function ataToUtf8(pathname) {
|
|
41
|
+
const label = ";base64,";
|
|
42
|
+
const a = pathname.indexOf(label);
|
|
43
|
+
if (a === -1) {
|
|
44
|
+
throw new Error("Invalid ata url pathname: " + pathname);
|
|
45
|
+
}
|
|
46
|
+
const binaryString = atob(pathname.slice(a + label.length));
|
|
47
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
48
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
49
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
50
|
+
}
|
|
51
|
+
return new TextDecoder().decode(bytes);
|
|
52
|
+
}
|
|
53
|
+
const FluentProvider = /* @__PURE__ */ core.componentQrl(/* @__PURE__ */ core.inlinedQrl((_rawProps) => {
|
|
54
|
+
const locales = core.useSignal(_rawProps.acceptLanguage ?? [
|
|
55
|
+
"en"
|
|
56
|
+
]);
|
|
57
|
+
const bunmap = core.useSignal({});
|
|
58
|
+
const resmap = core.useSignal({});
|
|
59
|
+
const errors = core.useSignal([]);
|
|
60
|
+
core.useContextProvider(FluentLocalesId, locales);
|
|
61
|
+
core.useContextProvider(FluentBundlesId, bunmap);
|
|
62
|
+
core.useContextProvider(FluentResourcesId, resmap);
|
|
63
|
+
core.useContextProvider(FluentErrorsId, errors);
|
|
64
|
+
const ssr = core.useSignal(true);
|
|
65
|
+
core.useOnDocument("load", /* @__PURE__ */ core.inlinedQrl(() => {
|
|
66
|
+
const locales2 = core._captures[0], ssr2 = core._captures[1];
|
|
67
|
+
if (!ssr2.value) return;
|
|
68
|
+
ssr2.value = false;
|
|
69
|
+
locales2.value = navigator.languages.concat();
|
|
70
|
+
}, "FluentProvider_component_useOnDocument_9BL0L0WgLlY", [
|
|
71
|
+
locales,
|
|
72
|
+
ssr
|
|
73
|
+
]));
|
|
74
|
+
core.useTaskQrl(/* @__PURE__ */ core.inlinedQrl((_rawProps1) => {
|
|
75
|
+
const _rawProps2 = core._captures[0], bunmap2 = core._captures[1], errors2 = core._captures[2], locales2 = core._captures[3], resmap2 = core._captures[4], ssr2 = core._captures[5];
|
|
76
|
+
_rawProps1.track(locales2);
|
|
77
|
+
_rawProps1.track(ssr2);
|
|
78
|
+
if (!bunmap2.value) {
|
|
79
|
+
bunmap2.value = {};
|
|
80
|
+
}
|
|
81
|
+
if (!resmap2.value) {
|
|
82
|
+
resmap2.value = {};
|
|
83
|
+
}
|
|
84
|
+
if (!errors2.value) {
|
|
85
|
+
errors2.value = [];
|
|
86
|
+
}
|
|
87
|
+
const res = core.noSerialize(resmap2.value);
|
|
88
|
+
const bun = core.noSerialize(bunmap2.value);
|
|
89
|
+
const promises = [];
|
|
90
|
+
const whitelist = locales2.value;
|
|
91
|
+
let has_errors = false;
|
|
92
|
+
for (const r of _rawProps2.resources) {
|
|
93
|
+
let ok = false;
|
|
94
|
+
for (const x of r.locales) {
|
|
95
|
+
if (res[x]) {
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
if (whitelist.includes(x)) {
|
|
99
|
+
ok = true;
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (!ok) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const b = new bundle.FluentBundle(r.locales);
|
|
107
|
+
for (const x of r.locales) {
|
|
108
|
+
res[x] = r;
|
|
109
|
+
bun[x] = b;
|
|
110
|
+
}
|
|
111
|
+
for (const url of r.resources) {
|
|
112
|
+
let promise;
|
|
113
|
+
if (core.isServer) {
|
|
114
|
+
let path = url.toString().slice(1);
|
|
115
|
+
{
|
|
116
|
+
path = new URL(path, new URL("../dist/", _rawProps2.origin));
|
|
117
|
+
}
|
|
118
|
+
if (path.protocol === "ata:") {
|
|
119
|
+
promise = new Promise((resolve, reject) => {
|
|
120
|
+
try {
|
|
121
|
+
resolve(ataToUtf8(path.pathname));
|
|
122
|
+
} catch (err) {
|
|
123
|
+
reject(err);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
} else {
|
|
127
|
+
promise = import("node:fs/promises").then((fs) => fs.readFile(path.pathname)).then((buf) => new TextDecoder().decode(buf));
|
|
128
|
+
}
|
|
129
|
+
} else {
|
|
130
|
+
promise = fetch(url).then((resp) => resp.text());
|
|
131
|
+
}
|
|
132
|
+
promises.push(promise.then((s) => {
|
|
133
|
+
const errset = b.addResource(new bundle.FluentResource(s));
|
|
134
|
+
if (errset.length) {
|
|
135
|
+
errors2.value.push({
|
|
136
|
+
resource: r,
|
|
137
|
+
errors: errset
|
|
138
|
+
});
|
|
139
|
+
has_errors = true;
|
|
140
|
+
console.error(`[qfluent] Errors in resource ${url}:`, errset);
|
|
141
|
+
}
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return Promise.all(promises).then(() => {
|
|
146
|
+
bunmap2.trigger();
|
|
147
|
+
resmap2.trigger();
|
|
148
|
+
if (has_errors) {
|
|
149
|
+
errors2.trigger();
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}, "FluentProvider_component_useTask_0r0LUJYLJnU", [
|
|
153
|
+
_rawProps,
|
|
154
|
+
bunmap,
|
|
155
|
+
errors,
|
|
156
|
+
locales,
|
|
157
|
+
resmap,
|
|
158
|
+
ssr
|
|
159
|
+
]));
|
|
160
|
+
core.useTaskQrl(/* @__PURE__ */ core.inlinedQrl(({ track }) => {
|
|
161
|
+
const locales2 = core._captures[0], resmap2 = core._captures[1], ssr2 = core._captures[2];
|
|
162
|
+
if (track(ssr2)) return;
|
|
163
|
+
if (!track(resmap2)) return;
|
|
164
|
+
track(locales2);
|
|
165
|
+
const root = document.querySelector("html");
|
|
166
|
+
if (!root) return;
|
|
167
|
+
for (const l of locales2.value) {
|
|
168
|
+
const b = resmap2.value[l];
|
|
169
|
+
if (!b) {
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
root.lang = l;
|
|
173
|
+
const dir = b.dir;
|
|
174
|
+
if (dir) {
|
|
175
|
+
root.dir = dir;
|
|
176
|
+
} else {
|
|
177
|
+
root.dir = "";
|
|
178
|
+
}
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
}, "FluentProvider_component_useTask_1_GdWGQNUVI4g", [
|
|
182
|
+
locales,
|
|
183
|
+
resmap,
|
|
184
|
+
ssr
|
|
185
|
+
]));
|
|
186
|
+
return /* @__PURE__ */ core._jsxSorted(core.Slot, null, null, null, 3, "76_0");
|
|
187
|
+
}, "FluentProvider_component_LWfKAJ3jVqM"));
|
|
188
|
+
exports.FluentBundlesId = FluentBundlesId;
|
|
189
|
+
exports.FluentErrorsId = FluentErrorsId;
|
|
190
|
+
exports.FluentLocalesId = FluentLocalesId;
|
|
191
|
+
exports.FluentProvider = FluentProvider;
|
|
192
|
+
exports.FluentResourcesId = FluentResourcesId;
|
|
193
|
+
exports.useFluentErrors = useFluentErrors;
|
|
194
|
+
exports.useFluentLocales = useFluentLocales;
|
|
195
|
+
exports.useFluentResources = useFluentResources;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { createContextId, useContext, componentQrl, inlinedQrl, useSignal, useContextProvider, useOnDocument, _captures, useTaskQrl, noSerialize, isServer, _jsxSorted, Slot } from "@qwik.dev/core";
|
|
2
|
+
import { FluentBundle, FluentResource } from "@fluent/bundle";
|
|
3
|
+
const FluentLocalesId = createContextId("QwikFluentLocales");
|
|
4
|
+
const FluentResourcesId = createContextId("QwikFluentResources");
|
|
5
|
+
const FluentBundlesId = createContextId("QwikFluentBundles");
|
|
6
|
+
const FluentErrorsId = createContextId("QwikFluentErrors");
|
|
7
|
+
function useFluentLocales() {
|
|
8
|
+
return useContext(FluentLocalesId);
|
|
9
|
+
}
|
|
10
|
+
function useFluentResources() {
|
|
11
|
+
return useContext(FluentResourcesId);
|
|
12
|
+
}
|
|
13
|
+
function useFluentErrors() {
|
|
14
|
+
return useContext(FluentErrorsId);
|
|
15
|
+
}
|
|
16
|
+
function ataToUtf8(pathname) {
|
|
17
|
+
const label = ";base64,";
|
|
18
|
+
const a = pathname.indexOf(label);
|
|
19
|
+
if (a === -1) {
|
|
20
|
+
throw new Error("Invalid ata url pathname: " + pathname);
|
|
21
|
+
}
|
|
22
|
+
const binaryString = atob(pathname.slice(a + label.length));
|
|
23
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
24
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
25
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
26
|
+
}
|
|
27
|
+
return new TextDecoder().decode(bytes);
|
|
28
|
+
}
|
|
29
|
+
const FluentProvider = /* @__PURE__ */ componentQrl(/* @__PURE__ */ inlinedQrl((_rawProps) => {
|
|
30
|
+
const locales = useSignal(_rawProps.acceptLanguage ?? [
|
|
31
|
+
"en"
|
|
32
|
+
]);
|
|
33
|
+
const bunmap = useSignal({});
|
|
34
|
+
const resmap = useSignal({});
|
|
35
|
+
const errors = useSignal([]);
|
|
36
|
+
useContextProvider(FluentLocalesId, locales);
|
|
37
|
+
useContextProvider(FluentBundlesId, bunmap);
|
|
38
|
+
useContextProvider(FluentResourcesId, resmap);
|
|
39
|
+
useContextProvider(FluentErrorsId, errors);
|
|
40
|
+
const ssr = useSignal(true);
|
|
41
|
+
useOnDocument("load", /* @__PURE__ */ inlinedQrl(() => {
|
|
42
|
+
const locales2 = _captures[0], ssr2 = _captures[1];
|
|
43
|
+
if (!ssr2.value) return;
|
|
44
|
+
ssr2.value = false;
|
|
45
|
+
locales2.value = navigator.languages.concat();
|
|
46
|
+
}, "FluentProvider_component_useOnDocument_9BL0L0WgLlY", [
|
|
47
|
+
locales,
|
|
48
|
+
ssr
|
|
49
|
+
]));
|
|
50
|
+
useTaskQrl(/* @__PURE__ */ inlinedQrl((_rawProps1) => {
|
|
51
|
+
const _rawProps2 = _captures[0], bunmap2 = _captures[1], errors2 = _captures[2], locales2 = _captures[3], resmap2 = _captures[4], ssr2 = _captures[5];
|
|
52
|
+
_rawProps1.track(locales2);
|
|
53
|
+
_rawProps1.track(ssr2);
|
|
54
|
+
if (!bunmap2.value) {
|
|
55
|
+
bunmap2.value = {};
|
|
56
|
+
}
|
|
57
|
+
if (!resmap2.value) {
|
|
58
|
+
resmap2.value = {};
|
|
59
|
+
}
|
|
60
|
+
if (!errors2.value) {
|
|
61
|
+
errors2.value = [];
|
|
62
|
+
}
|
|
63
|
+
const res = noSerialize(resmap2.value);
|
|
64
|
+
const bun = noSerialize(bunmap2.value);
|
|
65
|
+
const promises = [];
|
|
66
|
+
const whitelist = locales2.value;
|
|
67
|
+
let has_errors = false;
|
|
68
|
+
for (const r of _rawProps2.resources) {
|
|
69
|
+
let ok = false;
|
|
70
|
+
for (const x of r.locales) {
|
|
71
|
+
if (res[x]) {
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
if (whitelist.includes(x)) {
|
|
75
|
+
ok = true;
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (!ok) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const b = new FluentBundle(r.locales);
|
|
83
|
+
for (const x of r.locales) {
|
|
84
|
+
res[x] = r;
|
|
85
|
+
bun[x] = b;
|
|
86
|
+
}
|
|
87
|
+
for (const url of r.resources) {
|
|
88
|
+
let promise;
|
|
89
|
+
if (isServer) {
|
|
90
|
+
let path = url.toString().slice(1);
|
|
91
|
+
{
|
|
92
|
+
path = new URL(path, new URL("../dist/", _rawProps2.origin));
|
|
93
|
+
}
|
|
94
|
+
if (path.protocol === "ata:") {
|
|
95
|
+
promise = new Promise((resolve, reject) => {
|
|
96
|
+
try {
|
|
97
|
+
resolve(ataToUtf8(path.pathname));
|
|
98
|
+
} catch (err) {
|
|
99
|
+
reject(err);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
} else {
|
|
103
|
+
promise = import("node:fs/promises").then((fs) => fs.readFile(path.pathname)).then((buf) => new TextDecoder().decode(buf));
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
promise = fetch(url).then((resp) => resp.text());
|
|
107
|
+
}
|
|
108
|
+
promises.push(promise.then((s) => {
|
|
109
|
+
const errset = b.addResource(new FluentResource(s));
|
|
110
|
+
if (errset.length) {
|
|
111
|
+
errors2.value.push({
|
|
112
|
+
resource: r,
|
|
113
|
+
errors: errset
|
|
114
|
+
});
|
|
115
|
+
has_errors = true;
|
|
116
|
+
console.error(`[qfluent] Errors in resource ${url}:`, errset);
|
|
117
|
+
}
|
|
118
|
+
}));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return Promise.all(promises).then(() => {
|
|
122
|
+
bunmap2.trigger();
|
|
123
|
+
resmap2.trigger();
|
|
124
|
+
if (has_errors) {
|
|
125
|
+
errors2.trigger();
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}, "FluentProvider_component_useTask_0r0LUJYLJnU", [
|
|
129
|
+
_rawProps,
|
|
130
|
+
bunmap,
|
|
131
|
+
errors,
|
|
132
|
+
locales,
|
|
133
|
+
resmap,
|
|
134
|
+
ssr
|
|
135
|
+
]));
|
|
136
|
+
useTaskQrl(/* @__PURE__ */ inlinedQrl(({ track }) => {
|
|
137
|
+
const locales2 = _captures[0], resmap2 = _captures[1], ssr2 = _captures[2];
|
|
138
|
+
if (track(ssr2)) return;
|
|
139
|
+
if (!track(resmap2)) return;
|
|
140
|
+
track(locales2);
|
|
141
|
+
const root = document.querySelector("html");
|
|
142
|
+
if (!root) return;
|
|
143
|
+
for (const l of locales2.value) {
|
|
144
|
+
const b = resmap2.value[l];
|
|
145
|
+
if (!b) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
root.lang = l;
|
|
149
|
+
const dir = b.dir;
|
|
150
|
+
if (dir) {
|
|
151
|
+
root.dir = dir;
|
|
152
|
+
} else {
|
|
153
|
+
root.dir = "";
|
|
154
|
+
}
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
}, "FluentProvider_component_useTask_1_GdWGQNUVI4g", [
|
|
158
|
+
locales,
|
|
159
|
+
resmap,
|
|
160
|
+
ssr
|
|
161
|
+
]));
|
|
162
|
+
return /* @__PURE__ */ _jsxSorted(Slot, null, null, null, 3, "76_0");
|
|
163
|
+
}, "FluentProvider_component_LWfKAJ3jVqM"));
|
|
164
|
+
export {
|
|
165
|
+
FluentBundlesId,
|
|
166
|
+
FluentErrorsId,
|
|
167
|
+
FluentLocalesId,
|
|
168
|
+
FluentProvider,
|
|
169
|
+
FluentResourcesId,
|
|
170
|
+
useFluentErrors,
|
|
171
|
+
useFluentLocales,
|
|
172
|
+
useFluentResources
|
|
173
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type { FluentProviderProps, FluentTProps, QwikFluentError, QwikFluentResource, QwikFluentResourceMap, } from "./types";
|
|
2
|
+
export { FluentProvider, useFluentErrors, useFluentLocales, useFluentResources, } from "./context";
|
|
3
|
+
export { T, useLocalizedTitle, useT } from "./translate";
|
|
4
|
+
export { parseAcceptLanguage } from "./tools";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse an `Accept-Language` header as array of locales, sorted by quality value.
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* parseAcceptLanguage("en-US;q=0.3, ja, zh-CN;q=0.9, zh;q=0.7")
|
|
6
|
+
* // [ "ja", "zh-CN", "zh", "en-US" ]
|
|
7
|
+
* ```
|
|
8
|
+
*/
|
|
9
|
+
export declare function parseAcceptLanguage(header: string): string[];
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
function parseAcceptLanguage(header) {
|
|
4
|
+
return header.split(",").map((entry) => {
|
|
5
|
+
const [locale, ...params] = entry.split(";");
|
|
6
|
+
const q = params.find((p) => p.trim().startsWith("q="));
|
|
7
|
+
return {
|
|
8
|
+
locale: locale.trim(),
|
|
9
|
+
q: q ? parseFloat(q.split("=")[1]) : 1
|
|
10
|
+
};
|
|
11
|
+
}).filter((x) => x.locale).sort((a, b) => b.q - a.q).map((x) => x.locale);
|
|
12
|
+
}
|
|
13
|
+
exports.parseAcceptLanguage = parseAcceptLanguage;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
function parseAcceptLanguage(header) {
|
|
2
|
+
return header.split(",").map((entry) => {
|
|
3
|
+
const [locale, ...params] = entry.split(";");
|
|
4
|
+
const q = params.find((p) => p.trim().startsWith("q="));
|
|
5
|
+
return {
|
|
6
|
+
locale: locale.trim(),
|
|
7
|
+
q: q ? parseFloat(q.split("=")[1]) : 1
|
|
8
|
+
};
|
|
9
|
+
}).filter((x) => x.locale).sort((a, b) => b.q - a.q).map((x) => x.locale);
|
|
10
|
+
}
|
|
11
|
+
export {
|
|
12
|
+
parseAcceptLanguage
|
|
13
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Component, Signal } from "@qwik.dev/core";
|
|
2
|
+
import type { FluentTProps } from "./types";
|
|
3
|
+
/**
|
|
4
|
+
* Translate a string in TSX idiom.
|
|
5
|
+
*
|
|
6
|
+
* Pass a signal for `args`.
|
|
7
|
+
*
|
|
8
|
+
* ```tsx
|
|
9
|
+
* const x = useSignal(1);
|
|
10
|
+
* return <T s="x-apples" args={x} />;
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export declare const T: Component<FluentTProps>;
|
|
14
|
+
/**
|
|
15
|
+
* Return a reactive signal to a translated string.
|
|
16
|
+
*
|
|
17
|
+
* Useful for non-DOM use cases (like `document.title`, see `useLocalizedTitle`).
|
|
18
|
+
*
|
|
19
|
+
* ```ts
|
|
20
|
+
* const t = useT("my-title");
|
|
21
|
+
* useTask$(({ track }) => {
|
|
22
|
+
* track(t);
|
|
23
|
+
* document.title = t.value;
|
|
24
|
+
* });
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare function useT<T>(s: string, args?: Signal<T>): Signal<string>;
|
|
28
|
+
/**
|
|
29
|
+
* Make the document title (`<title>` element in `<head>`) translated, and follow locale changes.
|
|
30
|
+
*
|
|
31
|
+
* Note: Only effective after resumption on clients. Title generated during SSG is untouched.
|
|
32
|
+
*/
|
|
33
|
+
export declare function useLocalizedTitle<T>(s: string, args?: Signal<T>): void;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const core = require("@qwik.dev/core");
|
|
4
|
+
const context = require("./context.qwik.cjs");
|
|
5
|
+
function wrapArgs(args) {
|
|
6
|
+
switch (typeof args) {
|
|
7
|
+
case "string":
|
|
8
|
+
case "number":
|
|
9
|
+
case "bigint":
|
|
10
|
+
case "boolean":
|
|
11
|
+
return {
|
|
12
|
+
x: args
|
|
13
|
+
};
|
|
14
|
+
case "undefined":
|
|
15
|
+
return {
|
|
16
|
+
x: "undefined"
|
|
17
|
+
};
|
|
18
|
+
case "object":
|
|
19
|
+
if (args === null) {
|
|
20
|
+
return {
|
|
21
|
+
x: "null"
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
return args;
|
|
25
|
+
default:
|
|
26
|
+
return {
|
|
27
|
+
x: String(args)
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function dummyT(s, args) {
|
|
32
|
+
return s + (args ? " + " + args : "");
|
|
33
|
+
}
|
|
34
|
+
function useT(s, args) {
|
|
35
|
+
const locales = context.useFluentLocales();
|
|
36
|
+
const bundles = core.useContext(context.FluentBundlesId);
|
|
37
|
+
const message = core.useComputedQrl(/* @__PURE__ */ core.inlinedQrl(() => {
|
|
38
|
+
const args2 = core._captures[0], bundles2 = core._captures[1], locales2 = core._captures[2], s2 = core._captures[3];
|
|
39
|
+
const value = args2?.value;
|
|
40
|
+
const placeholder = dummyT(s2, value);
|
|
41
|
+
const wargs = wrapArgs(value);
|
|
42
|
+
if (!bundles2.value) {
|
|
43
|
+
return placeholder;
|
|
44
|
+
}
|
|
45
|
+
for (const k of locales2.value) {
|
|
46
|
+
const b = bundles2.value[k];
|
|
47
|
+
if (!b) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const m = b.getMessage(s2);
|
|
51
|
+
if (!m || !m.value) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
return b.formatPattern(m.value, wargs);
|
|
55
|
+
}
|
|
56
|
+
return placeholder;
|
|
57
|
+
}, "useT_message_useComputed_S0G7htNeJXI", [
|
|
58
|
+
args,
|
|
59
|
+
bundles,
|
|
60
|
+
locales,
|
|
61
|
+
s
|
|
62
|
+
]));
|
|
63
|
+
return message;
|
|
64
|
+
}
|
|
65
|
+
const T = /* @__PURE__ */ core.componentQrl(/* @__PURE__ */ core.inlinedQrl((_rawProps) => {
|
|
66
|
+
return useT(_rawProps.s, _rawProps.args).value;
|
|
67
|
+
}, "T_component_g8a5BXhl0hY"));
|
|
68
|
+
function useLocalizedTitle(s, args) {
|
|
69
|
+
const t = useT(s, args);
|
|
70
|
+
core.useOnDocument("qidle", /* @__PURE__ */ core.inlinedQrl(() => {
|
|
71
|
+
const t2 = core._captures[0];
|
|
72
|
+
if (typeof document === "undefined") return;
|
|
73
|
+
document.title = t2.value;
|
|
74
|
+
}, "useLocalizedTitle_useOnDocument_VXsb0xE3U2c", [
|
|
75
|
+
t
|
|
76
|
+
]));
|
|
77
|
+
core.useTaskQrl(/* @__PURE__ */ core.inlinedQrl(({ track }) => {
|
|
78
|
+
const t2 = core._captures[0];
|
|
79
|
+
track(t2);
|
|
80
|
+
if (typeof document === "undefined") return;
|
|
81
|
+
document.title = t2.value;
|
|
82
|
+
}, "useLocalizedTitle_useTask_MxJmwSF2dkE", [
|
|
83
|
+
t
|
|
84
|
+
]));
|
|
85
|
+
}
|
|
86
|
+
exports.T = T;
|
|
87
|
+
exports.useLocalizedTitle = useLocalizedTitle;
|
|
88
|
+
exports.useT = useT;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { componentQrl, inlinedQrl, useOnDocument, _captures, useTaskQrl, useContext, useComputedQrl } from "@qwik.dev/core";
|
|
2
|
+
import { useFluentLocales, FluentBundlesId } from "./context.qwik.mjs";
|
|
3
|
+
function wrapArgs(args) {
|
|
4
|
+
switch (typeof args) {
|
|
5
|
+
case "string":
|
|
6
|
+
case "number":
|
|
7
|
+
case "bigint":
|
|
8
|
+
case "boolean":
|
|
9
|
+
return {
|
|
10
|
+
x: args
|
|
11
|
+
};
|
|
12
|
+
case "undefined":
|
|
13
|
+
return {
|
|
14
|
+
x: "undefined"
|
|
15
|
+
};
|
|
16
|
+
case "object":
|
|
17
|
+
if (args === null) {
|
|
18
|
+
return {
|
|
19
|
+
x: "null"
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
return args;
|
|
23
|
+
default:
|
|
24
|
+
return {
|
|
25
|
+
x: String(args)
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function dummyT(s, args) {
|
|
30
|
+
return s + (args ? " + " + args : "");
|
|
31
|
+
}
|
|
32
|
+
function useT(s, args) {
|
|
33
|
+
const locales = useFluentLocales();
|
|
34
|
+
const bundles = useContext(FluentBundlesId);
|
|
35
|
+
const message = useComputedQrl(/* @__PURE__ */ inlinedQrl(() => {
|
|
36
|
+
const args2 = _captures[0], bundles2 = _captures[1], locales2 = _captures[2], s2 = _captures[3];
|
|
37
|
+
const value = args2?.value;
|
|
38
|
+
const placeholder = dummyT(s2, value);
|
|
39
|
+
const wargs = wrapArgs(value);
|
|
40
|
+
if (!bundles2.value) {
|
|
41
|
+
return placeholder;
|
|
42
|
+
}
|
|
43
|
+
for (const k of locales2.value) {
|
|
44
|
+
const b = bundles2.value[k];
|
|
45
|
+
if (!b) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const m = b.getMessage(s2);
|
|
49
|
+
if (!m || !m.value) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
return b.formatPattern(m.value, wargs);
|
|
53
|
+
}
|
|
54
|
+
return placeholder;
|
|
55
|
+
}, "useT_message_useComputed_S0G7htNeJXI", [
|
|
56
|
+
args,
|
|
57
|
+
bundles,
|
|
58
|
+
locales,
|
|
59
|
+
s
|
|
60
|
+
]));
|
|
61
|
+
return message;
|
|
62
|
+
}
|
|
63
|
+
const T = /* @__PURE__ */ componentQrl(/* @__PURE__ */ inlinedQrl((_rawProps) => {
|
|
64
|
+
return useT(_rawProps.s, _rawProps.args).value;
|
|
65
|
+
}, "T_component_g8a5BXhl0hY"));
|
|
66
|
+
function useLocalizedTitle(s, args) {
|
|
67
|
+
const t = useT(s, args);
|
|
68
|
+
useOnDocument("qidle", /* @__PURE__ */ inlinedQrl(() => {
|
|
69
|
+
const t2 = _captures[0];
|
|
70
|
+
if (typeof document === "undefined") return;
|
|
71
|
+
document.title = t2.value;
|
|
72
|
+
}, "useLocalizedTitle_useOnDocument_VXsb0xE3U2c", [
|
|
73
|
+
t
|
|
74
|
+
]));
|
|
75
|
+
useTaskQrl(/* @__PURE__ */ inlinedQrl(({ track }) => {
|
|
76
|
+
const t2 = _captures[0];
|
|
77
|
+
track(t2);
|
|
78
|
+
if (typeof document === "undefined") return;
|
|
79
|
+
document.title = t2.value;
|
|
80
|
+
}, "useLocalizedTitle_useTask_MxJmwSF2dkE", [
|
|
81
|
+
t
|
|
82
|
+
]));
|
|
83
|
+
}
|
|
84
|
+
export {
|
|
85
|
+
T,
|
|
86
|
+
useLocalizedTitle,
|
|
87
|
+
useT
|
|
88
|
+
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { Signal } from "@qwik.dev/core";
|
|
2
|
+
import type { FluentBundle } from "@fluent/bundle";
|
|
3
|
+
/**
|
|
4
|
+
* One pre-defined fluent resource entry.
|
|
5
|
+
*
|
|
6
|
+
* Import .ftl files as URL. Example:
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* import en_ftl from "./i18n/en.ftl?url";
|
|
10
|
+
*
|
|
11
|
+
* export const i18nResources: QwikFluentResource[] = [{
|
|
12
|
+
* name: "English",
|
|
13
|
+
* locales: ["en", "en-US"],
|
|
14
|
+
* resources: [en_ftl],
|
|
15
|
+
* }];
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export interface QwikFluentResource {
|
|
19
|
+
/**
|
|
20
|
+
* Language name.
|
|
21
|
+
*
|
|
22
|
+
* ```ts
|
|
23
|
+
* {
|
|
24
|
+
* name: "English (US)",
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
name: string;
|
|
29
|
+
/**
|
|
30
|
+
* Applicable locales to given resources.
|
|
31
|
+
* Most specific first.
|
|
32
|
+
*
|
|
33
|
+
* ```ts
|
|
34
|
+
* {
|
|
35
|
+
* locales: ["en-US", "en"],
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
locales: string[];
|
|
40
|
+
/**
|
|
41
|
+
* Resource URLs. Import them like this:
|
|
42
|
+
*
|
|
43
|
+
* ```ts
|
|
44
|
+
* import en_ftl from "./i18n/en.ftl?url";
|
|
45
|
+
*
|
|
46
|
+
* {
|
|
47
|
+
* resources: [en_ftl],
|
|
48
|
+
* }
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
resources: (URL | string)[];
|
|
52
|
+
/**
|
|
53
|
+
* Text layout direction. Specify this as `"rtl"` for
|
|
54
|
+
* languages like Arabic, Hebrew, etc.
|
|
55
|
+
*
|
|
56
|
+
* ```ts
|
|
57
|
+
* {
|
|
58
|
+
* locales: ["ar"],
|
|
59
|
+
* dir: "rtl",
|
|
60
|
+
* }
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
dir?: "rtl";
|
|
64
|
+
}
|
|
65
|
+
export interface FluentProviderProps {
|
|
66
|
+
/** Initial locales to use. `["en"]` by default. */
|
|
67
|
+
acceptLanguage?: string[];
|
|
68
|
+
/**
|
|
69
|
+
* URL origin for development and SSR to work properly.
|
|
70
|
+
*
|
|
71
|
+
* Set this to project root directory (which has `package.json`).
|
|
72
|
+
*
|
|
73
|
+
* Typically you just need:
|
|
74
|
+
*
|
|
75
|
+
* ```tsx
|
|
76
|
+
* // src/root.tsx
|
|
77
|
+
* <FluentProvider origin={new URL("..", import.meta.url)} resources={...}>
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
origin: URL;
|
|
81
|
+
/**
|
|
82
|
+
* Fluent Resources.
|
|
83
|
+
*
|
|
84
|
+
* ```ts
|
|
85
|
+
* import en_ftl from "./i18n/en.ftl?url";
|
|
86
|
+
*
|
|
87
|
+
* export const i18nResources: QwikFluentResource[] = [
|
|
88
|
+
* {
|
|
89
|
+
* name: "English",
|
|
90
|
+
* locales: ["en-US", "en"],
|
|
91
|
+
* resources: [en_ftl],
|
|
92
|
+
* },
|
|
93
|
+
* ];
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
resources: QwikFluentResource[];
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Key is individual locale like `"en" "en-US"`.
|
|
100
|
+
*
|
|
101
|
+
* Only contains loaded resources.
|
|
102
|
+
*
|
|
103
|
+
* You can have custom properties in your resources.
|
|
104
|
+
* Re-use them with `useFluentResources`.
|
|
105
|
+
*/
|
|
106
|
+
export type QwikFluentResourceMap = {
|
|
107
|
+
[locale: string]: QwikFluentResource;
|
|
108
|
+
};
|
|
109
|
+
/** @internal */
|
|
110
|
+
export type QwikFluentBundleMap = {
|
|
111
|
+
[locale: string]: FluentBundle;
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Errors related to certain resource when parsing it.
|
|
115
|
+
*/
|
|
116
|
+
export interface QwikFluentError {
|
|
117
|
+
resource: QwikFluentResource;
|
|
118
|
+
errors: Error[];
|
|
119
|
+
}
|
|
120
|
+
export interface FluentTProps {
|
|
121
|
+
/** The key for translation string. */
|
|
122
|
+
s: string;
|
|
123
|
+
/**
|
|
124
|
+
* Optional argument(s) in a signal.
|
|
125
|
+
*
|
|
126
|
+
* If it's not an object, it will be wrapped as `{ x: args }`.
|
|
127
|
+
*/
|
|
128
|
+
args?: Signal<unknown>;
|
|
129
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yozora-tech/qfluent",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "ProjectFluent.org translations support for Qwik framework v2.",
|
|
5
|
+
"main": "./lib/index.qwik.mjs",
|
|
6
|
+
"qwik": "./lib/index.qwik.mjs",
|
|
7
|
+
"types": "./lib/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./lib/index.d.ts",
|
|
11
|
+
"import": "./lib/index.qwik.mjs",
|
|
12
|
+
"require": "./lib/index.qwik.cjs"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"LICENSE",
|
|
17
|
+
"README.md",
|
|
18
|
+
"lib"
|
|
19
|
+
],
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
|
22
|
+
},
|
|
23
|
+
"private": false,
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"type": "module",
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "qwik build",
|
|
28
|
+
"build.lib": "vite build --mode lib",
|
|
29
|
+
"build.types": "tsc -p tsconfig.types.json --emitDeclarationOnly --incremental false",
|
|
30
|
+
"dev": "vite --mode ssr",
|
|
31
|
+
"dev.debug": "node --inspect-brk ./node_modules/vite/bin/vite.js --mode ssr --force",
|
|
32
|
+
"fmt": "deno fmt .",
|
|
33
|
+
"fmt.check": "deno fmt --check .",
|
|
34
|
+
"lint": "eslint \"src/**/*.ts*\"",
|
|
35
|
+
"release": "np",
|
|
36
|
+
"start": "vite --open --mode ssr",
|
|
37
|
+
"test": "deno test --no-config src/",
|
|
38
|
+
"qwik": "qwik"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@fluent/bundle": "^0.19.1"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"@qwik.dev/core": "^2.0.0-beta.38"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@eslint/js": "^10.0.1",
|
|
48
|
+
"@qwik.dev/router": "^2.0.0-beta.38",
|
|
49
|
+
"@types/node": "24.10.0",
|
|
50
|
+
"eslint": "10.0.2",
|
|
51
|
+
"eslint-plugin-qwik": "^2.0.0-beta.38",
|
|
52
|
+
"globals": "17.0.0",
|
|
53
|
+
"typescript": "5.9.3",
|
|
54
|
+
"typescript-eslint": "8.56.1",
|
|
55
|
+
"vite": "7.3.1",
|
|
56
|
+
"vite-tsconfig-paths": "^4.3.2"
|
|
57
|
+
}
|
|
58
|
+
}
|