@prosary/nuxt 0.0.0-stage → 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,59 @@
1
+ import { TranslateRunOptions } from "@prosary/kit";
2
+ /**
3
+ * The module's configuration. The messages are always authored as FormatJS
4
+ * message descriptors that a `@prosary/kit` config points at; the module
5
+ * takes them one of two ways —
6
+ *
7
+ * - **Built here.** With no `build`, the module loads the app's own
8
+ * `prosary.config.ts` and builds it through the kit at build time. No
9
+ * options are needed at all; with `translate`, it translates first.
10
+ * - **Built elsewhere.** Point `build` at the output of a kit build —
11
+ * a content package in a monorepo, or a published one — and the module
12
+ * uses it from where it stands:
13
+ *
14
+ * ```ts
15
+ * export default defineNuxtConfig({
16
+ * prosary: { build: "@acme/messages" },
17
+ * });
18
+ * ```
19
+ */
20
+ export interface NuxtProsaryConfig {
21
+ /**
22
+ * The kit config to build from, relative to the project root. Defaults to
23
+ * `prosary.config.ts`. Ignored when a `build` is passed.
24
+ */
25
+ config?: string;
26
+ /**
27
+ * How to translate: the model — from any AI SDK provider — its call
28
+ * settings, and the project's instructions, as the kit config's `translate`
29
+ * carries them beside the source and the targets. Present, the module
30
+ * translates before it builds, exactly as `prosary translate` does: what
31
+ * each locale is missing or has out of date is translated and written to
32
+ * the translations directory, and nothing else is touched. Takes the place
33
+ * of the model and the settings in the kit config. Ignored when a `build`
34
+ * is passed, and during `nuxt prepare`.
35
+ */
36
+ translate?: NonNullable<TranslateRunOptions["translate"]>;
37
+ /**
38
+ * A kit build made elsewhere: the output directory `prosary build` wrote.
39
+ * A path — relative to the project root, or absolute — to the directory or
40
+ * its `index.mjs`, or a package whose main entry is that `index.mjs`.
41
+ */
42
+ build?: string;
43
+ /**
44
+ * The overrides: a directory, relative to the source directory (`app/`), of components
45
+ * that render in place of elements and directives. Each `.vue` file at its
46
+ * top level renders the tag or directive its name spells in kebab case:
47
+ * `H2.vue` renders every `h2`, `Callout.vue` every `callout` directive.
48
+ * `<Prose :tree>` renders a document with them. Defaults to
49
+ * `components/prose`.
50
+ */
51
+ components?: string;
52
+ }
53
+ /**
54
+ * Identity helper that types a Nuxt prosary configuration.
55
+ *
56
+ * @param config - The Nuxt prosary configuration.
57
+ * @returns The same config.
58
+ */
59
+ export declare const defineProsaryConfig: (config: NuxtProsaryConfig) => NuxtProsaryConfig;
@@ -0,0 +1,2 @@
1
+ const defineProsaryConfig = (config) => config;
2
+ export { defineProsaryConfig };
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The build-directory folder the module writes the kit's output into — the
3
+ * same `index` and `bundles` modules, locale bundles and content documents
4
+ * `prosary build` writes to its output directory, importable in the app as
5
+ * `#build/prosary/index.mjs` and `#build/prosary/bundles.mjs`.
6
+ */
7
+ export declare const MODULES = "prosary";
8
+ /**
9
+ * The folder of a kit build its content documents sit under:
10
+ * `content/<locale>/<path>`. The kit's own `CONTENT`, repeated here so a
11
+ * build made elsewhere is used without the kit installed.
12
+ */
13
+ export declare const CONTENT = "content";
14
+ /** The cookie the visitor's locale is kept in. */
15
+ export declare const LOCALE_COOKIE = "prosary-locale";
16
+ /** The cookie the visitor's time zone is kept in. */
17
+ export declare const TIME_ZONE_COOKIE = "prosary-time-zone";
18
+ /** The cookie the visitor's convention is kept in. */
19
+ export declare const CONVENTION_COOKIE = "prosary-convention";
20
+ /**
21
+ * Where the built content documents are served from, as static files:
22
+ * `{CONTENT_URL}/<locale>/<path>`.
23
+ */
24
+ export declare const CONTENT_URL = "/_prosary/content";
25
+ /**
26
+ * The directory of overrides, relative to the source directory, when the config
27
+ * names none: components that render in place of elements and directives.
28
+ */
29
+ export declare const OVERRIDES = "components/prose";
@@ -0,0 +1,8 @@
1
+ const MODULES = "prosary";
2
+ const CONTENT = "content";
3
+ const LOCALE_COOKIE = "prosary-locale";
4
+ const TIME_ZONE_COOKIE = "prosary-time-zone";
5
+ const CONVENTION_COOKIE = "prosary-convention";
6
+ const CONTENT_URL = "/_prosary/content";
7
+ const OVERRIDES = "components/prose";
8
+ export { CONTENT, CONTENT_URL, CONVENTION_COOKIE, LOCALE_COOKIE, MODULES, OVERRIDES, TIME_ZONE_COOKIE };
@@ -0,0 +1,24 @@
1
+ import { NuxtProsaryConfig } from "./config.mjs";
2
+ /**
3
+ * Nuxt module for prosary.
4
+ *
5
+ * Its messages are always FormatJS descriptors built by `@prosary/kit`:
6
+ * either the app's own `prosary.config.ts`, built here, or the output of a
7
+ * kit build made elsewhere, which `build` points at. Built here, the module
8
+ * first translates what the locales are missing when given `translate`, then
9
+ * compiles the source messages and every locale's translations at build
10
+ * time and writes the kit's output — the `index` contract module, the
11
+ * `bundles` loaders, each locale's bundle and the content documents — as
12
+ * build templates under `prosary/`. Built elsewhere, the same `index` and
13
+ * `bundles` modules re-export the build's own. Either way it serves the
14
+ * content documents as static files, and registers the runtime plugin and
15
+ * the `useProsary`, `useT`, `useLocale` and `useDocument` auto-imports, and
16
+ * `<Prose>`, which renders a document with the app's overrides.
17
+ *
18
+ * A visitor gets one locale. The server picks it — their cookie, else the
19
+ * best match for the languages their browser asks for, else the source
20
+ * locale — and renders with that locale's bundle; the browser loads the same
21
+ . * bundle as its own chunk, and another only when the visitor switches.
22
+ */
23
+ declare const _default: import("@nuxt/schema").NuxtModule<NuxtProsaryConfig, NuxtProsaryConfig, false>;
24
+ export { _default as default };
@@ -0,0 +1,223 @@
1
+ import { CONTENT, CONTENT_URL, MODULES } from "./constant.mjs";
2
+ import { addComponent, addImports, addPlugin, addServerHandler, addTemplate, createResolver, defineNuxtModule, directoryToURL, tryResolveModule, useLogger } from "@nuxt/kit";
3
+ import { basename, dirname, extname, isAbsolute, join, resolve } from "node:path";
4
+ import { existsSync, readdirSync, statSync } from "node:fs";
5
+ import { fileURLToPath, pathToFileURL } from "node:url";
6
+ const kebab = (name) => {
7
+ return name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
8
+ };
9
+ const scanOverrides = (directory) => {
10
+ if (!existsSync(directory)) return [];
11
+ return readdirSync(directory).filter((entry) => extname(entry) === ".vue").sort().map((entry) => ({
12
+ name: kebab(basename(entry, ".vue")),
13
+ file: join(directory, entry)
14
+ }));
15
+ };
16
+ const componentsModule = (overrides) => {
17
+ const names = overrides.map((override) => JSON.stringify(override.name));
18
+ const imports = overrides.map((override, index) => `import override${index} from ${JSON.stringify(override.file)};`);
19
+ const entries = overrides.map((override, index) => ` ${JSON.stringify(override.name)}: override${index},`);
20
+ const keys = names.length === 0 ? "never" : `Known<${names.join(" | ")}>`;
21
+ return [{
22
+ path: "components.mjs",
23
+ contents: [
24
+ "// Generated by @prosary/nuxt. Do not edit by hand.",
25
+ ...imports,
26
+ `export const components = Object.freeze({\n${entries.join("\n")}\n});`,
27
+ ""
28
+ ].join("\n")
29
+ }, {
30
+ path: "components.d.mts",
31
+ contents: [
32
+ "// Generated by @prosary/nuxt. Do not edit by hand.",
33
+ "import type { Component } from \"vue\";",
34
+ "import type { Directive, Element } from \"./index.mjs\";",
35
+ "/** An override names an element or a directive the content builds. */",
36
+ "type Known<K extends Element | Directive> = K;",
37
+ `export declare const components: Readonly<Record<${keys}, Component>>;`,
38
+ ""
39
+ ].join("\n")
40
+ }];
41
+ };
42
+ const ENTRIES = ["index", "bundles"];
43
+ const OUTPUT = "hast";
44
+ const assertOutput = (output, where) => {
45
+ if (output === OUTPUT) return;
46
+ const found = output === void 0 ? "names no form (it predates content.output)" : `builds it as ${JSON.stringify(output)}`;
47
+ throw new Error(`prosary: the content must be built as ${JSON.stringify(OUTPUT)}, which the module renders — ${where} ${found}. Set \`content.output\` to ${JSON.stringify(OUTPUT)} in the kit config, the default, and build again.`);
48
+ };
49
+ const outputOf = async (directory) => {
50
+ return (await import(pathToFileURL(join(directory, "index.mjs")).href)).content?.output;
51
+ };
52
+ const loadKit = async () => {
53
+ try {
54
+ return await import("@prosary/kit");
55
+ } catch (error) {
56
+ throw new Error("prosary: building the app's messages needs @prosary/kit, which could not be loaded — add it to your devDependencies, or point `build` at a kit build made elsewhere", { cause: error });
57
+ }
58
+ };
59
+ const locate = async (build, root) => {
60
+ let path;
61
+ if (build.startsWith(".") || isAbsolute(build)) path = resolve(root, build);
62
+ else {
63
+ const resolved = await tryResolveModule(build, directoryToURL(root));
64
+ path = resolved?.startsWith("file:") ? fileURLToPath(resolved) : resolved;
65
+ }
66
+ if (path === void 0 || !existsSync(path)) throw new Error(`prosary: \`build\` ${JSON.stringify(build)} could not be found from ${root}`);
67
+ const directory = statSync(path).isFile() ? dirname(path) : path;
68
+ for (const entry of ENTRIES) if (!existsSync(join(directory, `${entry}.mjs`))) throw new Error(`prosary: ${directory} is not a kit build — it has no ${entry}.mjs (run \`prosary build\` there first)`);
69
+ return directory;
70
+ };
71
+ const adopt = (directory) => {
72
+ return ENTRIES.flatMap((entry) => {
73
+ const contents = `export * from ${JSON.stringify(join(directory, `${entry}.mjs`))};\n`;
74
+ return [{
75
+ path: `${entry}.mjs`,
76
+ contents
77
+ }, {
78
+ path: `${entry}.d.mts`,
79
+ contents
80
+ }];
81
+ });
82
+ };
83
+ const runTranslate = async (translate, options, root, config) => {
84
+ const logger = useLogger("prosary");
85
+ const output = await translate({
86
+ root,
87
+ config,
88
+ translate: options
89
+ });
90
+ for (const [locale, outcome] of Object.entries(output)) {
91
+ const { written, rejected, skipped, kept, documents } = outcome;
92
+ if (written.length + documents.written.length > 0) logger.info(`${locale}: translated ${written.length} messages and ${documents.written.length} documents`);
93
+ for (const [key, issues] of Object.entries({
94
+ ...rejected,
95
+ ...documents.rejected
96
+ })) logger.warn(`${locale}: the translation of ${JSON.stringify(key)} was rejected — it ${issues.join("; it ")}`);
97
+ for (const key of skipped) logger.warn(`${locale}: ${JSON.stringify(key)} came back with no translation`);
98
+ for (const key of kept) logger.warn(`${locale}: ${JSON.stringify(key)} was written by hand and its source has since changed (kept — delete it to translate again)`);
99
+ }
100
+ };
101
+ const loadSource = async (options, nuxt) => {
102
+ const root = nuxt.options.rootDir;
103
+ if (options.build !== void 0) {
104
+ const directory = await locate(options.build, root);
105
+ const content = join(directory, CONTENT);
106
+ const served = existsSync(content);
107
+ if (served) assertOutput(await outputOf(directory), `the build at ${directory}`);
108
+ return {
109
+ files: adopt(directory),
110
+ content: served ? content : void 0
111
+ };
112
+ }
113
+ const { FILENAME, emit, loadConfig, resolveKit, translate } = await loadKit();
114
+ const path = resolve(root, options.config ?? FILENAME);
115
+ nuxt.options.watch.push(path);
116
+ if (options.translate !== void 0 && !nuxt.options._prepare) await runTranslate(translate, options.translate, root, path);
117
+ const kit = await resolveKit(await loadConfig(path), { cwd: root });
118
+ nuxt.options.watch.push(...kit.inputs);
119
+ if (kit.documents.length > 0) assertOutput(kit.output, `the kit config at ${path}`);
120
+ return {
121
+ files: emit(kit),
122
+ content: kit.documents.length > 0 ? join(nuxt.options.buildDir, MODULES, CONTENT) : void 0
123
+ };
124
+ };
125
+ var module_default = defineNuxtModule({
126
+ meta: {
127
+ name: "prosary",
128
+ configKey: "prosary"
129
+ },
130
+ setup: async (options, nuxt) => {
131
+ const resolver = createResolver(import.meta.url);
132
+ const runtime = (name) => resolver.resolvePath(`./runtime/${name}`);
133
+ const files = {
134
+ composable: await runtime("composable"),
135
+ store: await runtime("store"),
136
+ types: await runtime("types"),
137
+ document: await runtime("document"),
138
+ plugin: await runtime("plugin"),
139
+ content: await runtime("server/content")
140
+ };
141
+ const source = await loadSource(options, nuxt);
142
+ for (const file of source.files) addTemplate({
143
+ filename: `${MODULES}/${file.path}`,
144
+ write: true,
145
+ getContents: () => file.contents
146
+ });
147
+ if (source.content !== void 0) {
148
+ nuxt.options.nitro.publicAssets ||= [];
149
+ nuxt.options.nitro.publicAssets.push({
150
+ dir: source.content,
151
+ baseURL: CONTENT_URL,
152
+ maxAge: 0,
153
+ fallthrough: true
154
+ });
155
+ if (nuxt.options.dev) {
156
+ nuxt.options.nitro.serverAssets ||= [];
157
+ nuxt.options.nitro.serverAssets.push({
158
+ baseName: "prosary",
159
+ dir: source.content
160
+ });
161
+ addServerHandler({
162
+ route: `${CONTENT_URL}/**`,
163
+ handler: files.content
164
+ });
165
+ }
166
+ }
167
+ const overrides = resolve(nuxt.options.srcDir, options.components ?? "components/prose");
168
+ nuxt.options.watch.push(overrides);
169
+ for (const file of componentsModule(scanOverrides(overrides))) addTemplate({
170
+ filename: `${MODULES}/${file.path}`,
171
+ write: true,
172
+ getContents: () => file.contents
173
+ });
174
+ const document = resolve(nuxt.options.buildDir, MODULES, "document");
175
+ addTemplate({
176
+ filename: `${MODULES}/document.mjs`,
177
+ write: true,
178
+ getContents: () => `export { default } from ${JSON.stringify(files.document)};\n`
179
+ });
180
+ addTemplate({
181
+ filename: `${MODULES}/document.d.ts`,
182
+ write: true,
183
+ getContents: () => `export { default } from ${JSON.stringify(files.document.replace(/\.ts$/, ""))};\n`
184
+ });
185
+ addComponent({
186
+ name: "Prose",
187
+ filePath: `${document}.mjs`
188
+ });
189
+ addPlugin({ src: files.plugin });
190
+ addImports([
191
+ ...[
192
+ "useProsary",
193
+ "useT",
194
+ "useLocale",
195
+ "useDocument"
196
+ ].map((name) => ({
197
+ from: files.composable,
198
+ name
199
+ })),
200
+ {
201
+ from: files.store,
202
+ name: "accessProsary"
203
+ },
204
+ ...[
205
+ "AppProsaryContract",
206
+ "AppProsaryLocale",
207
+ "AppProsaryKey",
208
+ "AppProsaryMessage",
209
+ "AppProsaryDocument",
210
+ "AppProsaryBundle",
211
+ "AppProsaryConfig",
212
+ "AppProsarySelection",
213
+ "AppProsaryResolver",
214
+ "AppProsary"
215
+ ].map((name) => ({
216
+ from: files.types,
217
+ name,
218
+ type: true
219
+ }))
220
+ ]);
221
+ }
222
+ });
223
+ export { module_default as default };
@@ -0,0 +1,23 @@
1
+ import { AppProsary, ProsaryNuxtApp, SetLocale } from "./types.mjs";
2
+ /**
3
+ * Builds the app's prosary service over reactive state: the selection in
4
+ * {@link accessProsary}'s `useState`, and the active locale's messages in a
5
+ * shallow ref this call owns — replaced whole on a switch, never observed in
6
+ * depth, and never serialized.
7
+ *
8
+ * On the server the selection is settled first: the locale from the
9
+ * visitor's cookie, else the best match for their `Accept-Language`, else
10
+ * the source locale; the time zone and convention from their cookies when
11
+ * those still hold. The browser starts from the selection the server
12
+ * rendered with. Either side then loads that one locale's bundle.
13
+ *
14
+ * Every later change is written through to the cookies, so the next request
15
+ * renders the way the visitor left things.
16
+ *
17
+ * @param nuxtApp - The Nuxt app, for its hooks.
18
+ * @returns The service, and the function that switches locale.
19
+ */
20
+ export declare const makeProsary: (nuxtApp: ProsaryNuxtApp) => Promise<{
21
+ service: AppProsary;
22
+ setLocale: SetLocale;
23
+ }>;
@@ -0,0 +1,74 @@
1
+ import { negotiate } from "./negotiate.mjs";
2
+ import { onServer } from "./side.mjs";
3
+ import { accessProsary } from "./store.mjs";
4
+ import { shallowRef } from "vue";
5
+ import { useRequestHeaders } from "#imports";
6
+ import { contract } from "#build/prosary/index.mjs";
7
+ import { bundles } from "#build/prosary/bundles.mjs";
8
+ import { defineSchema, makeProsary as makeProsary$1 } from "prosary";
9
+ const makeProsary = async (nuxtApp) => {
10
+ const { selection, cookies } = accessProsary();
11
+ const schema = defineSchema(contract);
12
+ if (onServer()) {
13
+ const header = useRequestHeaders(["accept-language"])["accept-language"];
14
+ const { locale, timeZone, convention } = cookies;
15
+ selection.value = {
16
+ locale: schema.check.locale(locale.value) ? locale.value : negotiate(header, contract.locales) ?? contract.locale,
17
+ ...schema.check.timeZone(timeZone.value) ? { timeZone: timeZone.value } : {},
18
+ ...schema.check.convention(convention.value) ? { convention: convention.value } : {}
19
+ };
20
+ }
21
+ const messages = shallowRef(await bundles[selection.value.locale]());
22
+ const service = makeProsary$1(contract, {
23
+ get locale() {
24
+ return selection.value.locale;
25
+ },
26
+ set locale(value) {
27
+ selection.value.locale = value;
28
+ },
29
+ get messages() {
30
+ return messages.value;
31
+ },
32
+ set messages(value) {
33
+ messages.value = value;
34
+ },
35
+ get timeZone() {
36
+ return selection.value.timeZone;
37
+ },
38
+ set timeZone(value) {
39
+ selection.value.timeZone = value;
40
+ },
41
+ get convention() {
42
+ return selection.value.convention;
43
+ },
44
+ set convention(value) {
45
+ selection.value.convention = value;
46
+ }
47
+ }, { set: { config: {
48
+ locale: (locale) => {
49
+ cookies.locale.value = locale;
50
+ nuxtApp.callHook("prosary:locale", locale);
51
+ return locale;
52
+ },
53
+ timeZone: (timeZone) => {
54
+ cookies.timeZone.value = timeZone ?? null;
55
+ return timeZone;
56
+ },
57
+ convention: (convention) => {
58
+ cookies.convention.value = convention ?? null;
59
+ return convention;
60
+ }
61
+ } } });
62
+ let latest = 0;
63
+ const setLocale = async (locale) => {
64
+ schema.assert.locale(locale);
65
+ const call = ++latest;
66
+ const bundle = await bundles[locale]();
67
+ if (call === latest) service.apply(locale, bundle);
68
+ };
69
+ return {
70
+ service,
71
+ setLocale
72
+ };
73
+ };
74
+ export { makeProsary };
@@ -0,0 +1,40 @@
1
+ import { AppProsary, AppProsaryDocument, AppProsaryLocale, AppProsaryResolver, SetLocale } from "./types.mjs";
2
+ import { Root } from "../_chunks/libs/@types/hast.mjs";
3
+ import { ComputedRef } from "vue";
4
+ /**
5
+ * Composable for the app's prosary service: the formatting helpers
6
+ * (`number`, `date`, `time`, `relative`, `list`, `name`), the time zone and
7
+ * convention, and the raw state. Everything it reads is reactive, so a
8
+ * template that calls it re-renders when the locale, time zone or convention
9
+ * changes.
10
+ */
11
+ export declare const useProsary: () => AppProsary;
12
+ /**
13
+ * Composable for the app's messages as functions — the same object templates
14
+ * reach as `$t`: `t.greeting({ name })`, or by key, `t("greeting", { name })`.
15
+ * An `AppProsaryMessage` held as data goes in whole: `t(message)`. Each call formats in the locale active at that moment.
16
+ */
17
+ export declare const useT: () => AppProsaryResolver;
18
+ /**
19
+ * Composable for the locale: the active one, the ones the app is built for,
20
+ * and the switch. `setLocale` loads the locale's bundle before anything
21
+ * changes, so the page never renders a locale it does not hold.
22
+ */
23
+ export declare const useLocale: () => {
24
+ locale: ComputedRef<AppProsaryLocale>;
25
+ locales: readonly AppProsaryLocale[];
26
+ setLocale: SetLocale;
27
+ };
28
+ /**
29
+ * Composable for one content document, as its HTML syntax tree, in the
30
+ * active locale. The tree is fetched as the JSON file the build wrote for
31
+ * that locale — the server render includes it, and a locale switch fetches
32
+ * the other locale's copy.
33
+ *
34
+ * @param path - The document's path under the content directory.
35
+ */
36
+ export declare const useDocument: (path: AppProsaryDocument) => Promise<{
37
+ data: import("vue").Ref<Root | undefined, Root | undefined>;
38
+ }> & {
39
+ data: import("vue").Ref<Root | undefined, Root | undefined>;
40
+ };
@@ -0,0 +1,27 @@
1
+ import { CONTENT_URL } from "../constant.mjs";
2
+ import { computed } from "vue";
3
+ import { useAsyncData, useRequestFetch } from "#imports";
4
+ import { useNuxtApp } from "#app";
5
+ const useProsary = () => {
6
+ const { $prosary } = useNuxtApp();
7
+ return $prosary;
8
+ };
9
+ const useT = () => {
10
+ const { $t } = useNuxtApp();
11
+ return $t;
12
+ };
13
+ const useLocale = () => {
14
+ const { $prosary, $setLocale } = useNuxtApp();
15
+ return {
16
+ locale: computed(() => $prosary.config.locale),
17
+ locales: $prosary.locales(),
18
+ setLocale: $setLocale
19
+ };
20
+ };
21
+ const useDocument = (path) => {
22
+ const { $prosary } = useNuxtApp();
23
+ const request = useRequestFetch();
24
+ const file = path.replace(/\.md$/, ".json");
25
+ return useAsyncData(`prosary:document:${path}`, () => request(`${CONTENT_URL}/${$prosary.config.locale}/${file}`, { responseType: "json" }), { watch: [() => $prosary.config.locale] });
26
+ };
27
+ export { useDocument, useLocale, useProsary, useT };
@@ -0,0 +1,21 @@
1
+ import { Root } from "../_chunks/libs/@types/hast.mjs";
2
+ import { PropType } from "vue";
3
+ /**
4
+ * Renders a content document with the app's overrides: `ProsaryDocument`
5
+ * of `@prosary/vue`, given the component map built from the overrides
6
+ * directory. Takes the tree `useDocument` loads.
7
+ */
8
+ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
9
+ tree: {
10
+ type: PropType<Root>;
11
+ required: true;
12
+ };
13
+ }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
14
+ [key: string]: any;
15
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
16
+ tree: {
17
+ type: PropType<Root>;
18
+ required: true;
19
+ };
20
+ }>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
21
+ export { _default as default };
@@ -0,0 +1,15 @@
1
+ import { defineComponent, h } from "vue";
2
+ import { ProsaryDocument } from "@prosary/vue";
3
+ import { components } from "#build/prosary/components.mjs";
4
+ var document_default = defineComponent({
5
+ name: "Prose",
6
+ props: { tree: {
7
+ type: Object,
8
+ required: true
9
+ } },
10
+ setup: (props) => () => h(ProsaryDocument, {
11
+ tree: props.tree,
12
+ components
13
+ })
14
+ });
15
+ export { document_default as default };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Picks the locale an `Accept-Language` header asks for, from the ones the
3
+ * app is built for. The header's tags are tried in the order the visitor
4
+ * ranks them: an exact match first, then a locale in the same language
5
+ * (`pt-PT` is served `pt-BR` before a lower-ranked language is tried).
6
+ *
7
+ * @param header - The `Accept-Language` header, if the request sent one.
8
+ * @param locales - The locales the app is built for.
9
+ * @returns The best match, or `undefined` when nothing the visitor asks for
10
+ * is available.
11
+ */
12
+ export declare const negotiate: <L extends string>(header: string | undefined, locales: readonly L[]) => L | undefined;
@@ -0,0 +1,21 @@
1
+ const language = (tag) => {
2
+ return (tag.split("-")[0] ?? tag).toLowerCase();
3
+ };
4
+ const negotiate = (header, locales) => {
5
+ if (header === void 0) return;
6
+ const wanted = header.split(",").map((part) => {
7
+ const [tag = "", ...params] = part.trim().split(";");
8
+ const quality = params.map((param) => /^\s*q=([0-9.]+)\s*$/.exec(param)?.[1]).find((value) => value !== void 0);
9
+ return {
10
+ tag: tag.trim().toLowerCase(),
11
+ quality: quality === void 0 ? 1 : Number(quality)
12
+ };
13
+ }).filter(({ tag, quality }) => tag !== "" && tag !== "*" && quality > 0).sort((a, b) => b.quality - a.quality);
14
+ for (const { tag } of wanted) {
15
+ const exact = locales.find((locale) => locale.toLowerCase() === tag);
16
+ if (exact !== void 0) return exact;
17
+ const related = locales.find((locale) => language(locale) === language(tag));
18
+ if (related !== void 0) return related;
19
+ }
20
+ };
21
+ export { negotiate };
@@ -0,0 +1,22 @@
1
+ import { AppProsary, SetLocale } from "./types.mjs";
2
+ /**
3
+ * Nuxt plugin that builds the prosary service over reactive, per-request
4
+ * state and provides it as `$prosary`, with its messages as `$t` and the
5
+ * locale switch as `$setLocale`.
6
+ *
7
+ * The visitor's selection is held in {@link useState} so the browser starts
8
+ * from the locale the server rendered with; the plugin awaits that locale's
9
+ * bundle before the app renders, on both sides, so the first render and the
10
+ * hydration read the same messages. The active locale is mirrored onto the
11
+ * document root as `lang` and `dir`, and follows every switch.
12
+ */
13
+ declare const _default: import("#app").Plugin<{
14
+ prosary: AppProsary;
15
+ t: import("prosary").Resolver<import("#build/prosary/index.mjs").Contract>;
16
+ setLocale: SetLocale;
17
+ }> & import("#app").ObjectPlugin<{
18
+ prosary: AppProsary;
19
+ t: import("prosary").Resolver<import("#build/prosary/index.mjs").Contract>;
20
+ setLocale: SetLocale;
21
+ }>;
22
+ export { _default as default };
@@ -0,0 +1,29 @@
1
+ import { makeProsary } from "./client.mjs";
2
+ import { computed } from "vue";
3
+ import { useHead } from "#imports";
4
+ import { defineNuxtPlugin } from "#app";
5
+ const direction = (locale) => {
6
+ try {
7
+ const info = new Intl.Locale(locale);
8
+ return (info.getTextInfo?.() ?? info.textInfo)?.direction ?? "ltr";
9
+ } catch {
10
+ return "ltr";
11
+ }
12
+ };
13
+ var plugin_default = defineNuxtPlugin({
14
+ name: "prosary",
15
+ setup: async (nuxtApp) => {
16
+ const { service, setLocale } = await makeProsary(nuxtApp);
17
+ useHead({ htmlAttrs: {
18
+ lang: computed(() => service.config.locale),
19
+ dir: computed(() => direction(service.config.locale))
20
+ } });
21
+ await nuxtApp.callHook("prosary:ready", service);
22
+ return { provide: {
23
+ prosary: service,
24
+ t: service.createResolver(),
25
+ setLocale
26
+ } };
27
+ }
28
+ });
29
+ export { plugin_default as default };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Serves the content documents in development, from the server's own
3
+ * handler. The documents are public assets, which Nitro's dev server serves
4
+ * to the browser from outside the worker — so a page rendered on the server
5
+ * in development, fetching its document from inside the worker, would get a
6
+ * 404. This handler answers that fetch from the same directory, mounted as a
7
+ * server asset. A production build has no need of it: there the static
8
+ * handler serves the assets to every fetch.
9
+ */
10
+ declare const _default: (event: import("#imports").H3Event) => Promise<{} | undefined>;
11
+ export { _default as default };
@@ -0,0 +1,8 @@
1
+ import { createError, defineEventHandler, getRouterParam, useStorage } from "#imports";
2
+ var content_default = defineEventHandler(async (event) => {
3
+ const path = getRouterParam(event, "_") ?? "";
4
+ const document = await useStorage("assets:prosary").getItem(path);
5
+ if (document === null) throw createError({ statusCode: 404 });
6
+ return document;
7
+ });
8
+ export { content_default as default };