UNPKG

@intlify/h3

Version:
611 lines (604 loc) 20.6 kB
import { NOT_REOSLVED, createCoreContext, parseTranslateArgs, translate } from "@intlify/core"; import { getCookieLocale, getHeaderLanguage, getHeaderLanguages, getHeaderLocale, getHeaderLocale as getHeaderLocale$1, getHeaderLocales, getPathLocale, getQueryLocale, setCookieLocale, tryCookieLocale, tryHeaderLocale, tryHeaderLocales, tryPathLocale, tryQueryLocale } from "@intlify/utils"; //#region ../../node_modules/.pnpm/rou3@0.7.10/node_modules/rou3/dist/index.mjs const NullProtoObj = /* @__PURE__ */ (() => { const e = function() {}; return e.prototype = Object.create(null), Object.freeze(e.prototype), e; })(); //#endregion //#region ../../node_modules/.pnpm/srvx@0.9.6/node_modules/srvx/dist/_chunks/_inherit-D99WuBbX.mjs function lazyInherit(target, source, sourceKey) { for (const key of [...Object.getOwnPropertyNames(source), ...Object.getOwnPropertySymbols(source)]) { if (key === "constructor") continue; const targetDesc = Object.getOwnPropertyDescriptor(target, key); const desc = Object.getOwnPropertyDescriptor(source, key); let modified = false; if (desc.get) { modified = true; desc.get = targetDesc?.get || function() { return this[sourceKey][key]; }; } if (desc.set) { modified = true; desc.set = targetDesc?.set || function(value) { this[sourceKey][key] = value; }; } if (!targetDesc?.value && typeof desc.value === "function") { modified = true; desc.value = function(...args) { return this[sourceKey][key](...args); }; } if (modified) Object.defineProperty(target, key, desc); } } //#endregion //#region ../../node_modules/.pnpm/srvx@0.9.6/node_modules/srvx/dist/_chunks/call-BLKVUMn3.mjs /** * Fast Response for Node.js runtime * * It is faster because in most cases it doesn't create a full Response instance. */ const NodeResponse = /* @__PURE__ */ (() => { const NativeResponse = globalThis.Response; const STATUS_CODES = globalThis.process?.getBuiltinModule?.("node:http")?.STATUS_CODES || {}; class NodeResponse$1 { #body; #init; #headers; #response; constructor(body, init) { this.#body = body; this.#init = init; } static [Symbol.hasInstance](val) { return val instanceof NativeResponse; } get status() { return this.#response?.status || this.#init?.status || 200; } get statusText() { return this.#response?.statusText || this.#init?.statusText || STATUS_CODES[this.status] || ""; } get headers() { if (this.#response) return this.#response.headers; if (this.#headers) return this.#headers; const initHeaders = this.#init?.headers; return this.#headers = initHeaders instanceof Headers ? initHeaders : new Headers(initHeaders); } get ok() { if (this.#response) return this.#response.ok; const status = this.status; return status >= 200 && status < 300; } get _response() { if (this.#response) return this.#response; this.#response = new NativeResponse(this.#body, this.#headers ? { ...this.#init, headers: this.#headers } : this.#init); this.#init = void 0; this.#headers = void 0; this.#body = void 0; return this.#response; } _toNodeResponse() { const status = this.status; const statusText = this.statusText; let body; let contentType; let contentLength; if (this.#response) body = this.#response.body; else if (this.#body) if (this.#body instanceof ReadableStream) body = this.#body; else if (typeof this.#body === "string") { body = this.#body; contentType = "text/plain; charset=UTF-8"; contentLength = Buffer.byteLength(this.#body); } else if (this.#body instanceof ArrayBuffer) { body = Buffer.from(this.#body); contentLength = this.#body.byteLength; } else if (this.#body instanceof Uint8Array) { body = this.#body; contentLength = this.#body.byteLength; } else if (this.#body instanceof DataView) { body = Buffer.from(this.#body.buffer); contentLength = this.#body.byteLength; } else if (this.#body instanceof Blob) { body = this.#body.stream(); contentType = this.#body.type; contentLength = this.#body.size; } else if (typeof this.#body.pipe === "function") body = this.#body; else body = this._response.body; const headers = []; const initHeaders = this.#init?.headers; const headerEntries = this.#response?.headers || this.#headers || (initHeaders ? Array.isArray(initHeaders) ? initHeaders : initHeaders?.entries ? initHeaders.entries() : Object.entries(initHeaders).map(([k, v]) => [k.toLowerCase(), v]) : void 0); let hasContentTypeHeader; let hasContentLength; if (headerEntries) for (const [key, value] of headerEntries) { if (Array.isArray(value)) for (const v of value) headers.push([key, v]); else headers.push([key, value]); if (key === "content-type") hasContentTypeHeader = true; else if (key === "content-length") hasContentLength = true; } if (contentType && !hasContentTypeHeader) headers.push(["content-type", contentType]); if (contentLength && !hasContentLength) headers.push(["content-length", String(contentLength)]); this.#init = void 0; this.#headers = void 0; this.#response = void 0; this.#body = void 0; return { status, statusText, headers, body }; } } lazyInherit(NodeResponse$1.prototype, NativeResponse.prototype, "_response"); Object.setPrototypeOf(NodeResponse$1, NativeResponse); Object.setPrototypeOf(NodeResponse$1.prototype, NativeResponse.prototype); return NodeResponse$1; })(); //#endregion //#region ../../node_modules/.pnpm/h3@2.0.1-rc.5/node_modules/h3/dist/h3.mjs function definePlugin(def) { return ((opts) => (h3) => def(h3, opts)); } const kEventNS = "h3.internal.event."; const kEventRes = /* @__PURE__ */ Symbol.for(`${kEventNS}res`); const kEventResHeaders = /* @__PURE__ */ Symbol.for(`${kEventNS}res.headers`); const DISALLOWED_STATUS_CHARS = /[^\u0009\u0020-\u007E]/g; function sanitizeStatusMessage(statusMessage = "") { return statusMessage.replace(DISALLOWED_STATUS_CHARS, ""); } function sanitizeStatusCode(statusCode, defaultStatusCode = 200) { if (!statusCode) return defaultStatusCode; if (typeof statusCode === "string") statusCode = +statusCode; if (statusCode < 100 || statusCode > 599) return defaultStatusCode; return statusCode; } var HTTPError = class HTTPError$1 extends Error { get name() { return "HTTPError"; } status; statusText; headers; cause; data; body; unhandled; static isError(input) { return input instanceof Error && input?.name === "HTTPError"; } static status(status, statusText, details) { return new HTTPError$1({ ...details, statusText, status }); } constructor(arg1, arg2) { let messageInput; let details; if (typeof arg1 === "string") { messageInput = arg1; details = arg2; } else details = arg1; const status = sanitizeStatusCode(details?.status || (details?.cause)?.status || details?.status || details?.statusCode, 500); const statusText = sanitizeStatusMessage(details?.statusText || (details?.cause)?.statusText || details?.statusText || details?.statusMessage); const message = messageInput || details?.message || (details?.cause)?.message || details?.statusText || details?.statusMessage || [ "HTTPError", status, statusText ].filter(Boolean).join(" "); super(message, { cause: details }); this.cause = details; Error.captureStackTrace?.(this, this.constructor); this.status = status; this.statusText = statusText || void 0; const rawHeaders = details?.headers || (details?.cause)?.headers; this.headers = rawHeaders ? new Headers(rawHeaders) : void 0; this.unhandled = details?.unhandled ?? (details?.cause)?.unhandled ?? void 0; this.data = details?.data; this.body = details?.body; } get statusCode() { return this.status; } get statusMessage() { return this.statusText; } toJSON() { const unhandled = this.unhandled; return { status: this.status, statusText: this.statusText, unhandled, message: unhandled ? "HTTPError" : this.message, data: unhandled ? void 0 : this.data, ...unhandled ? void 0 : this.body }; } }; function isJSONSerializable(value, _type) { if (value === null || value === void 0) return true; if (_type !== "object") return _type === "boolean" || _type === "number" || _type === "string"; if (typeof value.toJSON === "function") return true; if (Array.isArray(value)) return true; if (typeof value.pipe === "function" || typeof value.pipeTo === "function") return false; if (value instanceof NullProtoObj) return true; const proto = Object.getPrototypeOf(value); return proto === Object.prototype || proto === null; } const kNotFound = /* @__PURE__ */ Symbol.for("h3.notFound"); const kHandled = /* @__PURE__ */ Symbol.for("h3.handled"); function toResponse(val, event, config = {}) { if (typeof val?.then === "function") return (val.catch?.((error) => error) || Promise.resolve(val)).then((resolvedVal) => toResponse(resolvedVal, event, config)); const response = prepareResponse(val, event, config); if (typeof response?.then === "function") return toResponse(response, event, config); const { onResponse: onResponse$1 } = config; return onResponse$1 ? Promise.resolve(onResponse$1(response, event)).then(() => response) : response; } var HTTPResponse = class { #headers; #init; body; constructor(body, init) { this.body = body; this.#init = init; } get status() { return this.#init?.status || 200; } get statusText() { return this.#init?.statusText || "OK"; } get headers() { return this.#headers ||= new Headers(this.#init?.headers); } }; function prepareResponse(val, event, config, nested) { if (val === kHandled) return new NodeResponse(null); if (val === kNotFound) val = new HTTPError({ status: 404, message: `Cannot find any route matching [${event.req.method}] ${event.url}` }); if (val && val instanceof Error) { const isHTTPError = HTTPError.isError(val); const error = isHTTPError ? val : new HTTPError(val); if (!isHTTPError) { error.unhandled = true; if (val?.stack) error.stack = val.stack; } if (error.unhandled && !config.silent) console.error(error); const { onError: onError$1 } = config; return onError$1 && !nested ? Promise.resolve(onError$1(error, event)).catch((error$1) => error$1).then((newVal) => prepareResponse(newVal ?? val, event, config, true)) : errorResponse(error, config.debug); } const preparedRes = event[kEventRes]; const preparedHeaders = preparedRes?.[kEventResHeaders]; if (!(val instanceof Response)) { const res = prepareResponseBody(val, event, config); const status = res.status || preparedRes?.status; return new NodeResponse(nullBody(event.req.method, status) ? null : res.body, { status, statusText: res.statusText || preparedRes?.statusText, headers: res.headers && preparedHeaders ? mergeHeaders$1(res.headers, preparedHeaders) : res.headers || preparedHeaders }); } if (!preparedHeaders || nested || !val.ok) return val; try { mergeHeaders$1(val.headers, preparedHeaders, val.headers); return val; } catch { return new NodeResponse(nullBody(event.req.method, val.status) ? null : val.body, { status: val.status, statusText: val.statusText, headers: mergeHeaders$1(val.headers, preparedHeaders) }); } } function mergeHeaders$1(base, overrides, target = new Headers(base)) { for (const [name, value] of overrides) if (name === "set-cookie") target.append(name, value); else target.set(name, value); return target; } const frozenHeaders = () => { throw new Error("Headers are frozen"); }; var FrozenHeaders = class extends Headers { constructor(init) { super(init); this.set = this.append = this.delete = frozenHeaders; } }; const emptyHeaders = /* @__PURE__ */ new FrozenHeaders({ "content-length": "0" }); const jsonHeaders = /* @__PURE__ */ new FrozenHeaders({ "content-type": "application/json;charset=UTF-8" }); function prepareResponseBody(val, event, config) { if (val === null || val === void 0) return { body: "", headers: emptyHeaders }; const valType = typeof val; if (valType === "string") return { body: val }; if (val instanceof Uint8Array) { event.res.headers.set("content-length", val.byteLength.toString()); return { body: val }; } if (val instanceof HTTPResponse || val?.constructor?.name === "HTTPResponse") return val; if (isJSONSerializable(val, valType)) return { body: JSON.stringify(val, void 0, config.debug ? 2 : void 0), headers: jsonHeaders }; if (valType === "bigint") return { body: val.toString(), headers: jsonHeaders }; if (val instanceof Blob) { const headers = new Headers({ "content-type": val.type, "content-length": val.size.toString() }); let filename = val.name; if (filename) { filename = encodeURIComponent(filename); headers.set("content-disposition", `filename="${filename}"; filename*=UTF-8''${filename}`); } return { body: val.stream(), headers }; } if (valType === "symbol") return { body: val.toString() }; if (valType === "function") return { body: `${val.name}()` }; return { body: val }; } function nullBody(method, status) { return method === "HEAD" || status === 100 || status === 101 || status === 102 || status === 204 || status === 205 || status === 304; } function errorResponse(error, debug) { return new NodeResponse(JSON.stringify({ ...error.toJSON(), stack: debug && error.stack ? error.stack.split("\n").map((l) => l.trim()) : void 0 }, void 0, debug ? 2 : void 0), { status: error.status, statusText: error.statusText, headers: error.headers ? mergeHeaders$1(jsonHeaders, error.headers) : new Headers(jsonHeaders) }); } function getEventContext(event) { if (event.context) return event.context; event.req.context ??= {}; return event.req.context; } function onRequest(hook) { return async function _onRequestMiddleware(event) { await hook(event); }; } function onResponse(hook) { return async function _onResponseMiddleware(event, next) { const response = await toResponse(await next(), event); return await hook(response, event) || response; }; } //#endregion //#region src/symbols.ts /** * defined symbols */ /** * @author kazuya kawaguchi (a.k.a. kazupon) * @license MIT */ const SYMBOL_INTLIFY = Symbol("__INTLIFY_H3__"); const SYMBOL_INTLIFY_LOCALE = Symbol("__INTLIFY_H3_LOCALE__"); //#endregion //#region src/index.ts /** * Internationalization middleware & utilities for h3 * * @module */ /** * @author kazuya kawaguchi (a.k.a. kazupon) * @license MIT */ /** * Internationalization plugin for H3 * * @example * ```ts * import { H3 } from 'h3' * import { intlify } from '@intlify/h3' * * const app = new H3({ * plugins: [ * intlify({ * messages: { * en: { * hello: 'Hello {name}!', * }, * ja: { * hello: 'こんにちは、{name}!', * }, * }, * // your locale detection logic here * locale: (event) => { * // ... * }, * }) * ] * }) */ const intlify = definePlugin((h3, options) => { const { onRequest: onRequest$1, onResponse: onResponse$1 } = defineIntlifyMiddleware(options); h3.use(onRequest$1); h3.use(onResponse$1); }); /** * Define internationalization middleware for H3 * * Define the middleware to be specified the bellows: * * - [`H3.use`]({@link https://h3.dev/guide/api/h3#h3use}) * * @example * * ```js * import { H3 } from 'h3' * import { defineIntlifyMiddleware } from '@intlify/h3' * * const intlifyMiddleware = defineIntlifyMiddleware({ * messages: { * en: { * hello: 'Hello {name}!', * }, * ja: { * hello: 'こんにちは、{name}!', * }, * }, * // your locale detection logic here * locale: (event) => { * // ... * }, * }) * * const app = new H3() * .use(intlifyMiddleware.onRequest) // register `onRequest` hook before your application middlewares * .use(intlifyMiddleware.onResponse) // register `onResponse` hook before your application middlewares * ``` * * @param options - An `i18n` options like vue-i18n [`createI18n`]({@link https://vue-i18n.intlify.dev/guide/#javascript}), which are passed to `createCoreContext` of `@intlify/core`, see about details [`CoreOptions` of `@intlify/core`](https://github.com/intlify/vue-i18n-next/blob/6a9947dd3e0fe90de7be9c87ea876b8779998de5/packages/core-base/src/context.ts#L196-L216) * * @returns A defined internationalization middleware, which is included `onRequest` and `onResponse` options of `H3` * * @internal */ function defineIntlifyMiddleware(options) { const intlify$1 = createCoreContext(options); const orgLocale = intlify$1.locale; let staticLocaleDetector = null; if (typeof orgLocale === "string") { console.warn(`'locale' option is static ${orgLocale} locale! you should specify dynamic locale detector function.`); staticLocaleDetector = () => orgLocale; } const getLocaleDetector = (event, intlify$2) => { return typeof orgLocale === "function" ? orgLocale.bind(null, event, intlify$2) : staticLocaleDetector == null ? detectLocaleFromAcceptLanguageHeader.bind(null, event) : staticLocaleDetector.bind(null, event, intlify$2); }; return { onRequest: onRequest((event) => { const context = getEventContext(event); context[SYMBOL_INTLIFY_LOCALE] = getLocaleDetector(event, intlify$1); intlify$1.locale = context[SYMBOL_INTLIFY_LOCALE]; context[SYMBOL_INTLIFY] = intlify$1; }), onResponse: onResponse((_, event) => { intlify$1.locale = orgLocale; const context = getEventContext(event); delete context[SYMBOL_INTLIFY]; delete context[SYMBOL_INTLIFY_LOCALE]; }) }; } /** * Locale detection with `Accept-Language` header * * @example * ```js * import { H3 } from 'h3' * import { defineIntlifyMiddleware, detectLocaleFromAcceptLanguageHeader } from '@intlify/h3' * * const intlifyMiddleware = defineIntlifyMiddleware({ * messages: { * en: { * hello: 'Hello {name}!', * }, * ja: { * hello: 'こんにちは、{name}!', * }, * }, * locale: detectLocaleFromAcceptLanguageHeader * }) * * const app = new H3() * .use(intlifyMiddleware.onRequest) * .use(intlifyMiddleware.onResponse) * ``` * * @param event - A H3 event * * @returns A locale string, which will be detected of **first** from `Accept-Language` header */ const detectLocaleFromAcceptLanguageHeader = (event) => getHeaderLocale$1(event.req).toString(); async function getLocaleAndEventContext(event) { const context = getEventContext(event); if (context[SYMBOL_INTLIFY] == null) throw new Error("plugin has not been initialized. Please check that the `intlify` plugin is installed correctly."); const localeDetector = context[SYMBOL_INTLIFY_LOCALE]; return [await localeDetector(event), context]; } /** * Use translation function in event handler * * @example * ```js * app.get( * '/', * async (event) => { * const t = await useTranslation(event) * return t('hello', { name: 'H3' }) * }, * ) * ``` * * @param event - A H3 event * * @returns Return a translation function, which can be translated with internationalization resource messages */ async function useTranslation(event) { const [locale, context] = await getLocaleAndEventContext(event); context[SYMBOL_INTLIFY].locale = locale; function translate$1(key, ...args) { const [_, options] = parseTranslateArgs(key, ...args); const [arg2] = args; const result = Reflect.apply(translate, null, [ context[SYMBOL_INTLIFY], key, arg2, { locale, ...options } ]); return NOT_REOSLVED === result ? key : result; } return translate$1; } /** * get a locale which is detected with locale detector. * * @description The locale obtainable via this function comes from the locale detector specified in the `locale` option of the {@link intlify} plugin. * * @example * ```js * app.get( * '/', * async (event) => { * const locale = await getDetectorLocale(event) * return `Current Locale: ${locale.language}` * }, * ) * ``` * @param event - A H3 event * * @returns Return an {@linkcode Intl.Locale} instance representing the detected locale */ async function getDetectorLocale(event) { const result = await getLocaleAndEventContext(event); return new Intl.Locale(result[0]); } //#endregion export { defineIntlifyMiddleware, detectLocaleFromAcceptLanguageHeader, getCookieLocale, getDetectorLocale, getHeaderLanguage, getHeaderLanguages, getHeaderLocale, getHeaderLocales, getPathLocale, getQueryLocale, intlify, setCookieLocale, tryCookieLocale, tryHeaderLocale, tryHeaderLocales, tryPathLocale, tryQueryLocale, useTranslation }; //# sourceMappingURL=index.mjs.map