Skip to content

Commit

Permalink
Release
Browse files Browse the repository at this point in the history
  • Loading branch information
eolme committed May 1, 2022
1 parent 29b6549 commit 1023218
Show file tree
Hide file tree
Showing 13 changed files with 356 additions and 110 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# i18nano [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/eolme/next-sw/blob/master/LICENSE) [![BundlePhobia](https://img.shields.io/bundlephobia/minzip/i18nano)](https://bundlephobia.com/package/i18nano) [![BundlePhobia](https://img.shields.io/bundlephobia/min/i18nano)](https://bundlephobia.com/package/i18nano)
# i18nano [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/eolme/i18nano/blob/master/LICENSE) [![BundlePhobia](https://img.shields.io/bundlephobia/minzip/i18nano)](https://bundlephobia.com/package/i18nano) [![BundlePhobia](https://img.shields.io/bundlephobia/min/i18nano)](https://bundlephobia.com/package/i18nano) [![Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen)](https://github.com/eolme/i18nano/blob/master/tests)

> Internationalization for the react is done simply.
Expand Down Expand Up @@ -84,7 +84,7 @@ export const LanguageChange = () => {

## Concurrent features

If you use react 18 it is recommended to use `unstable_transition`.
If you use react 18 it is recommended to use `transition`.
Then when you switch languages, the last downloaded translation will be displayed instead of the loader.

## Split
Expand Down
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const config = {
resolver: 'jest-ts-webcompat-resolver',
testEnvironment: 'node',
testMatch: [
'**/tests/*.ts'
'**/tests/*.spec.ts'
],
collectCoverage: true,
collectCoverageFrom: [
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "i18nano",
"version": "1.2.0",
"version": "2.0.0",
"source": "./src/index.ts",
"main": "./lib/index.cjs",
"module": "./lib/index.js",
Expand Down Expand Up @@ -51,6 +51,8 @@
"eslint-plugin-jest-formatting": "^3.1.0",
"jest": "^28.0.3",
"jest-esbuild": "^0.2.6",
"jest-mock": "^28.0.2",
"jest-resolve": "^28.0.3",
"jest-ts-webcompat-resolver": "^1.0.0",
"nanobundle": "^0.0.27",
"react": "^18.1.0",
Expand Down
12 changes: 12 additions & 0 deletions src/compat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { PropsWithChildren, ComponentType as _ComponentType, FC as _FC } from 'react';

type PropsWithoutChildren<P> = P extends any ? ('children' extends keyof P ? Pick<P, Exclude<keyof P, 'children'>> : P) : P;

// eslint-disable-next-line @typescript-eslint/ban-types
export type FC<P = {}> = _FC<PropsWithoutChildren<P>>;

// eslint-disable-next-line @typescript-eslint/ban-types
export type FCC<P = {}> = _FC<PropsWithChildren<P>>;

// TODO: maybe incompatible
export type { _ComponentType as ComponentType };
37 changes: 20 additions & 17 deletions src/react.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ComponentType, FC } from 'react';
import type { ComponentType, FC, FCC } from './compat.js';
import type {
TranslationChange,
TranslationChangeProps,
Expand Down Expand Up @@ -60,11 +60,11 @@ const lookupValue = (path: string, values: TranslationValues): string => {
return get(values, path, GET_OPTIONS);
};

const lookup = (path: string, values: TranslationValues, lang: TranslationValues) => {
const lookup = (path: string, values: TranslationValues | null, lang: TranslationValues) => {
let resolved = lookupValue(path, lang);

for (const key in values) {
resolved = resolved.replace(`{{${key}}}`, lookupValue(key, values));
if (values !== null) {
resolved = resolved.replace(/{{(.+?)}}/g, (_, key) => lookupValue(key, values));
}

return resolved;
Expand All @@ -78,16 +78,16 @@ const TranslationChangeContext = React.createContext<TranslationChange>({
preload: noop
});

export const TranslationProvider: FC<TranslationProviderProps> = ({
language = 'en',
export const TranslationProvider: FCC<TranslationProviderProps> = ({
language,
preloadLanguage = true,

fallback = language,
preloadFallback = false,

translations = {},
translations,

unstable_transition = false,
transition = false,

children
}) => {
Expand All @@ -106,24 +106,24 @@ export const TranslationProvider: FC<TranslationProviderProps> = ({
suspendPreload(translations[next], keyed(next));
};

if (preloadLanguage && language !== lang) {
if (preloadLanguage && language === lang) {
preload(lang);
}

if (preloadFallback && language !== fallback) {
preload(fallback);
}

const transition = unstable_transition ? startTransition : invoke;
const withTransition = transition ? startTransition : invoke;
const change = (next: string) => {
setCurrent(next);

transition(() => {
withTransition(() => {
setLanguage(next);
});
};

const t = React.useCallback((path: string, values: TranslationValues = {}) => {
const t = React.useCallback<TranslationFunction>((path, values = null) => {
let result = EMPTY;

if (lang in translations) {
Expand Down Expand Up @@ -182,7 +182,7 @@ export const useTranslationChange = () => {
* @see https://reactjs.org/docs/concurrent-mode-suspense.html
*/
export const withTranslation = <P>(Component: ComponentType<P & TranslationFunctionProps>) => {
const WithTranslation: FC<P> = (props) => {
const WithTranslation: FCC<P> = (props) => {
const t = useTranslation();

return React.createElement(Component, Object.assign({}, props, { t }));
Expand All @@ -192,7 +192,7 @@ export const withTranslation = <P>(Component: ComponentType<P & TranslationFunct
};

export const withTranslationChange = <P>(Component: ComponentType<P & TranslationChangeProps>) => {
const WithTranslationChange: FC<P> = (props) => {
const WithTranslationChange: FCC<P> = (props) => {
const translation = useTranslationChange();

return React.createElement(Component, Object.assign({}, props, { translation }));
Expand All @@ -210,7 +210,10 @@ export const withTranslationChange = <P>(Component: ComponentType<P & Translatio
* @see {@link Translation}
*/
// @ts-expect-error DefinitelyTyped issue
export const TranslationRender: FC<TranslationProps> = ({ path, values }) => {
export const TranslationRender: FC<TranslationProps> = ({
path,
values = null
}) => {
const t = useTranslation();

return t(path, values);
Expand All @@ -225,10 +228,10 @@ export const TranslationRender: FC<TranslationProps> = ({ path, values }) => {
*
* @see {@link TranslationRender}
*/
export const Translation: FC<TranslationProps> = ({
export const Translation: FCC<TranslationProps> = ({
children = null,
path,
values
values = null
}) => {
return React.createElement(
React.Suspense,
Expand Down
12 changes: 7 additions & 5 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ export type TranslationValues = {
[key: string | number]: string | TranslationValues;
} | Array<string | TranslationValues>;

export type TranslationFunction = (path: string, values?: TranslationValues) => string;
export type TranslationFunction = (path: string, values?: TranslationValues | null | undefined) => string;

export type TranslationFunctionProps = {
t: TranslationFunction;
};
Expand All @@ -13,23 +14,24 @@ export type TranslationChange = Readonly<{
change: (next: string) => void;
preload: (next: string) => void;
}>;

export type TranslationChangeProps = {
translation: TranslationChange;
};

export type TranslationProps = {
path: string;
values?: TranslationValues;
values?: TranslationValues | null | undefined;
};

export type TranslationProviderProps = {
language?: string;
language: string;
preloadLanguage?: boolean;

fallback?: string;
preloadFallback?: boolean;

translations?: Record<string, () => Promise<TranslationValues>>;
translations: Record<string, () => Promise<TranslationValues>>;

unstable_transition?: boolean;
transition?: boolean;
};
93 changes: 9 additions & 84 deletions tests/all.spec.ts → tests/change.spec.ts
Original file line number Diff line number Diff line change
@@ -1,92 +1,17 @@
import React from 'react';
import renderer from 'react-test-renderer';
import { waitForSuspense } from './suspense';

import * as Module from '../src';
import { waitForSuspense } from './suspense.js';

const TRANSLATIONS = {
ru: async () => ({
ru: 'ru'
}),
it: async () => ({
it: 'it'
})
};

const TRANSLATIONS_KEYS = Object.keys(TRANSLATIONS);

const SUSPENSE = 'suspense';

const NOOP = () => {
// Noop
};

describe('all spec', () => {
it('renders correctly', () => {
expect.assertions(1);

const component = renderer.create(
React.createElement(Module.TranslationProvider)
);

expect(component.toTree()).toMatchObject({
nodeType: 'component',
type: Module.TranslationProvider,
props: {},
instance: null,
rendered: null
});
});

it('renders correctly children', () => {
expect.assertions(1);

const component = renderer.create(
React.createElement(
Module.TranslationProvider,
null,
React.createElement('div', {
'data-children': true
})
)
);

expect(component.toTree()!.rendered).toMatchObject({
nodeType: 'host',
type: 'div',
props: {
'data-children': true
},
instance: null,
rendered: []
});
});

it.each([
['language', TRANSLATIONS_KEYS[0], 'lang', TRANSLATIONS_KEYS[0]],
['translations', TRANSLATIONS, 'all', TRANSLATIONS_KEYS]
])('handles prop "%s" correctly', (prop, value, by, match) => {
expect.assertions(1);

const component = renderer.create(
React.createElement(
Module.TranslationProvider,
{
[prop]: value
},

// @ts-expect-error DefinitelyTyped issue
React.createElement(() => {
const translation: Record<string, unknown> = Module.useTranslationChange();

return translation[by];
})
)
);

expect(component.toJSON()).toStrictEqual(match);
});
import {
Module,
NOOP,
SUSPENSE,
TRANSLATIONS,
TRANSLATIONS_KEYS
} from './shared.js';

describe('change', () => {
it.each([
[TRANSLATIONS_KEYS[0], TRANSLATIONS_KEYS[0]],
[TRANSLATIONS_KEYS[0], TRANSLATIONS_KEYS[1]],
Expand Down
68 changes: 68 additions & 0 deletions tests/components.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React from 'react';
import renderer from 'react-test-renderer';

import { waitForSuspense } from './suspense.js';

import {
DEFAULT_PROPS,
Module,
NOOP,
SUSPENSE
} from './shared.js';

describe('components', () => {
it('component TranslationRender', async () => {
expect.assertions(2);

const component = renderer.create(
React.createElement(
Module.TranslationProvider,
DEFAULT_PROPS,
React.createElement(
React.Suspense,
{
fallback: SUSPENSE
},
React.createElement(
Module.TranslationRender,
{
path: DEFAULT_PROPS.language
}
)
)
)
);

expect(component.toJSON()).toBe(SUSPENSE);

await renderer.act(NOOP);
await waitForSuspense(NOOP);

expect(component.toJSON()).toBe(DEFAULT_PROPS.language);
});

it('component Translation', async () => {
expect.assertions(2);

const component = renderer.create(
React.createElement(
Module.TranslationProvider,
DEFAULT_PROPS,
React.createElement(
Module.Translation,
{
path: DEFAULT_PROPS.language
},
SUSPENSE
)
)
);

expect(component.toJSON()).toBe(SUSPENSE);

await renderer.act(NOOP);
await waitForSuspense(NOOP);

expect(component.toJSON()).toBe(DEFAULT_PROPS.language);
});
});
Loading

0 comments on commit 1023218

Please sign in to comment.