Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
412 changes: 12 additions & 400 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions packages/body/src/helpers/parse-json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,14 @@ export async function parseJsonBody(
} catch (e) {
throw createError({
statusCode: 400,
statusMessage: `invalid JSON: ${e instanceof Error ? e.message : 'parse error'}`,
message: `invalid JSON: ${e instanceof Error ? e.message : 'parse error'}`,
});
}

if (options.strict !== false && !isObject(parsed) && !Array.isArray(parsed)) {
throw createError({
statusCode: 400,
statusMessage: 'request body must be an object or array in strict mode',
message: 'request body must be an object or array in strict mode',
});
}

Expand Down
2 changes: 1 addition & 1 deletion packages/body/src/helpers/parse-url-encoded.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export async function parseUrlEncodedBody(
if (count >= limit) {
throw createError({
statusCode: 413,
statusMessage: 'too many parameters',
message: 'too many parameters',
});
}

Expand Down
2 changes: 1 addition & 1 deletion packages/body/src/helpers/read-raw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export async function readRequestBodyRaw(
await reader.cancel();
throw createError({
statusCode: 413,
statusMessage: 'request entity too large',
message: 'request entity too large',
});
}

Expand Down
2 changes: 1 addition & 1 deletion packages/body/src/helpers/read-stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function readRequestBodyStream(
if (contentLength && Number.parseInt(contentLength, 10) > limit) {
throw createError({
statusCode: 413,
statusMessage: 'request entity too large',
message: 'request entity too large',
});
}
}
Expand Down
4 changes: 2 additions & 2 deletions packages/body/src/utils/decompress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export function createDecompressor(encoding: string): DecompressionStream {
if (!format) {
throw createError({
statusCode: 415,
statusMessage: `unsupported content encoding: ${encoding}`,
message: `unsupported content encoding: ${encoding}`,
});
}

Expand All @@ -25,7 +25,7 @@ export function createDecompressor(encoding: string): DecompressionStream {
} catch {
throw createError({
statusCode: 415,
statusMessage: `content encoding "${encoding}" is not supported by this runtime`,
message: `content encoding "${encoding}" is not supported by this runtime`,
});
}
}
6 changes: 2 additions & 4 deletions packages/i18n/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,11 @@
"homepage": "https://github.com/routup/plugins#readme",
"peerDependencies": {
"ilingo": "^3.2.0",
"routup": "^4.0.1"
"routup": "^5.0.0-beta.3"
},
"devDependencies": {
"@types/supertest": "^7.2.0",
"ilingo": "^5.0.0",
"routup": "^4.0.1",
"supertest": "^7.1.4"
"routup": "^5.0.0-beta.3"
},
"gitHead": "94d729e309c1eec0401afb4d8083f65ce3aa8e0b",
"publishConfig": {
Expand Down
6 changes: 2 additions & 4 deletions packages/i18n/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
// todo: this should be a symbol
export const REQUEST_INSTANCE_SYMBOL = 'ILINGO_INSTANCE';
// todo: this should be a symbol
export const REQUEST_LOCALE_SYMBOL = 'ILINGO_LOCALE';
export const REQUEST_INSTANCE_SYMBOL = Symbol.for('ReqI18nInstance');
export const REQUEST_LOCALE_SYMBOL = Symbol.for('ReqI18nLocale');
16 changes: 7 additions & 9 deletions packages/i18n/src/module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Ilingo } from 'ilingo';
import type { Plugin } from 'routup';
import { coreHandler, getRequestAcceptableLanguage, setRequestEnv } from 'routup';
import { defineCoreHandler, getRequestAcceptableLanguage } from 'routup';
import { REQUEST_INSTANCE_SYMBOL, REQUEST_LOCALE_SYMBOL } from './constants';
import type { LocaleReqFn, Options } from './types';

Expand Down Expand Up @@ -46,24 +46,22 @@ export function i18n(input?: Options | Ilingo) : Plugin {
instance = new Ilingo();
}

router.use(coreHandler(async (req, res, next) => {
router.use(defineCoreHandler(async (event) => {
const locales = await resolveLocales(instance);

// todo: key should be symbol
setRequestEnv(req, REQUEST_INSTANCE_SYMBOL, instance);
event.store[REQUEST_INSTANCE_SYMBOL] = instance;

let reqLocale : string | undefined;
if (typeof locale === 'function') {
reqLocale = await locale(req);
reqLocale = await locale(event);
}
if (typeof reqLocale === 'undefined') {
reqLocale = getRequestAcceptableLanguage(req, locales);
reqLocale = getRequestAcceptableLanguage(event, locales);
}

// todo: key should be symbol
setRequestEnv(req, REQUEST_LOCALE_SYMBOL, reqLocale);
event.store[REQUEST_LOCALE_SYMBOL] = reqLocale;

next();
return event.next();
}));
},
};
Expand Down
6 changes: 3 additions & 3 deletions packages/i18n/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type {
ConfigInput,
ConfigInput,
GetContext,
} from 'ilingo';
import type { Request } from 'routup';
import type { IRoutupEvent } from 'routup';

export type LocaleReqFn = (req: Request) => Promise<string | undefined> | string | undefined;
export type LocaleReqFn = (event: IRoutupEvent) => Promise<string | undefined> | string | undefined;

export type Options = Omit<ConfigInput, 'locale'> & {
locale?: string | LocaleReqFn
Expand Down
22 changes: 14 additions & 8 deletions packages/i18n/src/use-translator.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
import type { DotKey, GetContext } from 'ilingo';
import type { GetContext } from 'ilingo';
import { Ilingo } from 'ilingo';
import type { Request } from 'routup';
import { useRequestEnv } from 'routup';
import type { IRoutupEvent } from 'routup';
import { createError } from 'routup';
import { REQUEST_INSTANCE_SYMBOL, REQUEST_LOCALE_SYMBOL } from './constants';
import type { Translator } from './types';

export function useTranslator(req: Request) : Translator {
const reqInstance = useRequestEnv(req, REQUEST_INSTANCE_SYMBOL);
export function useTranslator(event: IRoutupEvent) : Translator {
const reqInstance = event.store[REQUEST_INSTANCE_SYMBOL];
if (!(reqInstance instanceof Ilingo)) {
throw new Error('The i18n plugin is not installed...');
throw createError({
statusCode: 500,
message: 'The i18n plugin is not installed.',
});
}

const reqLocale = useRequestEnv(req, REQUEST_LOCALE_SYMBOL);
const reqLocale = event.store[REQUEST_LOCALE_SYMBOL];
if (typeof reqLocale !== 'undefined' && typeof reqLocale !== 'string') {
throw new Error('The i18n locale must either be of type string or undefined.');
throw createError({
statusCode: 500,
message: 'The i18n locale must either be of type string or undefined.',
});
}

return (ctx: GetContext) => {
Expand Down
70 changes: 28 additions & 42 deletions packages/i18n/test/unit/module.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { describe, expect, it } from 'vitest';
import { MemoryStore } from 'ilingo';
import {
HeaderName,
Router,
coreHandler,
createNodeDispatcher,
Router,
defineCoreHandler,
} from 'routup';
import supertest from 'supertest';
import { i18n, useTranslator } from '../../src';

function createTestRequest(url: string, options?: RequestInit): Request {
const fullUrl = url.startsWith('http') ? url : `http://localhost${url}`;
return new Request(fullUrl, options);
}

describe('src/module', () => {
it('should translate text', async () => {
const router = new Router();
Expand All @@ -21,26 +23,20 @@ describe('src/module', () => {
});
router.use(i18n({ store }));

router.get('/', coreHandler(async (req) => {
const translator = useTranslator(req);
router.get('/', defineCoreHandler(async (event) => {
const translator = useTranslator(event);
return translator({ group: 'app', key: 'key' });
}));

const server = supertest(createNodeDispatcher(router));

let response = await server
.get('/')
.set(HeaderName.ACCEPT_LANGUAGE, 'de-CH,de-DE;q=0.9,de;q=0.8,en-US;q=0.7,en;q=0.6');
let response = await router.fetch(createTestRequest('/', { headers: { 'accept-language': 'de-CH,de-DE;q=0.9,de;q=0.8,en-US;q=0.7,en;q=0.6' } }));

expect(response.statusCode).toEqual(200);
expect(response.text).toEqual('Hallo Welt!');
expect(response.status).toEqual(200);
expect(await response.text()).toEqual('Hallo Welt!');

response = await server
.get('/')
.set(HeaderName.ACCEPT_LANGUAGE, 'en-US;q=0.9,en-GB;q=0.8');
response = await router.fetch(createTestRequest('/', { headers: { 'accept-language': 'en-US;q=0.9,en-GB;q=0.8' } }));

expect(response.statusCode).toEqual(200);
expect(response.text).toEqual('Hello world!');
expect(response.status).toEqual(200);
expect(await response.text()).toEqual('Hello world!');
});

it('should translate text with params', async () => {
Expand All @@ -54,8 +50,8 @@ describe('src/module', () => {
});
router.use(i18n({ store }));

router.get('/', coreHandler(async (req) => {
const translator = useTranslator(req);
router.get('/', defineCoreHandler(async (event) => {
const translator = useTranslator(event);

return translator({
group: 'app',
Expand All @@ -64,21 +60,15 @@ describe('src/module', () => {
});
}));

const server = supertest(createNodeDispatcher(router));
let response = await router.fetch(createTestRequest('/', { headers: { 'accept-language': 'de-CH,de-DE;q=0.9,de;q=0.8,en-US;q=0.7,en;q=0.6' } }));

let response = await server
.get('/')
.set(HeaderName.ACCEPT_LANGUAGE, 'de-CH,de-DE;q=0.9,de;q=0.8,en-US;q=0.7,en;q=0.6');
expect(response.status).toEqual(200);
expect(await response.text()).toEqual('Hallo, mein Name ist Peter');

expect(response.statusCode).toEqual(200);
expect(response.text).toEqual('Hallo, mein Name ist Peter');
response = await router.fetch(createTestRequest('/', { headers: { 'accept-language': 'en-US;q=0.9,en-GB;q=0.8' } }));

response = await server
.get('/')
.set(HeaderName.ACCEPT_LANGUAGE, 'en-US;q=0.9,en-GB;q=0.8');

expect(response.statusCode).toEqual(200);
expect(response.text).toEqual('Hello, my name is Peter');
expect(response.status).toEqual(200);
expect(await response.text()).toEqual('Hello, my name is Peter');
});

it('should work with custom locale fn', async () => {
Expand All @@ -96,18 +86,14 @@ describe('src/module', () => {
store,
}));

router.get('/', coreHandler(async (req) => {
const translator = useTranslator(req);
router.get('/', defineCoreHandler(async (event) => {
const translator = useTranslator(event);
return translator({ group: 'app', key: 'key' });
}));

const server = supertest(createNodeDispatcher(router));

const response = await server
.get('/')
.set(HeaderName.ACCEPT_LANGUAGE, 'de-CH,de-DE;q=0.9,de;q=0.8,en-US;q=0.7,en;q=0.6');
const response = await router.fetch(createTestRequest('/', { headers: { 'accept-language': 'de-CH,de-DE;q=0.9,de;q=0.8,en-US;q=0.7,en;q=0.6' } }));

expect(response.statusCode).toEqual(200);
expect(response.text).toEqual('Hello world!');
expect(response.status).toEqual(200);
expect(await response.text()).toEqual('Hello world!');
});
});
Loading