@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 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
@@ -0,0 +1,2 @@
1
+ import type { QwikFluentResource } from "./index";
2
+ export declare const i18nResources: QwikFluentResource[];
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
+ }