@intlify/h3
Version:
Internationalization for H3
611 lines (604 loc) • 20.6 kB
JavaScript
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