Skip to content

Commit

Permalink
feat: Initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
jungpaeng committed Oct 1, 2023
1 parent 0cd2807 commit b770c6f
Show file tree
Hide file tree
Showing 202 changed files with 4,226 additions and 88 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
dist

node_modules
.yarn/*
!.yarn/cache
!.yarn/patches
Expand Down
2,203 changes: 2,173 additions & 30 deletions .pnp.cjs

Large diffs are not rendered by default.

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added .yarn/cache/fsevents-patch-21ad2b1333-8.zip
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
17 changes: 15 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,15 @@
"clean": "rimraf dist",
"build": "yarn clean && yarn build:js && yarn build:dts",
"build:dts": "tsc --emitDeclarationOnly",
"build:js": "node esbuild.config.cjs"
"build:js": "node esbuild.config.cjs",
"test": "yarn vitest"
},
"dependencies": {
"intl-messageformat": "^10.5.3"
},
"devDependencies": {
"@testing-library/react": "^14.0.0",
"@types/react": "^18.2.23",
"@typescript-eslint/eslint-plugin": "^6.7.3",
"@typescript-eslint/parser": "^6.7.3",
"esbuild": "^0.19.4",
Expand All @@ -24,8 +30,15 @@
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-unused-imports": "^3.0.0",
"jsdom": "^22.1.0",
"prettier": "^3.0.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"rimraf": "^5.0.5",
"typescript": "^5.2.2"
"typescript": "^5.2.2",
"vitest": "^0.34.6"
},
"peerDependencies": {
"react": "^18.0.0"
}
}
1 change: 0 additions & 1 deletion src/index.d.ts

This file was deleted.

10 changes: 10 additions & 0 deletions src/lit-intl.context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react';

import { type IntlMessage } from './types/intl-message';

export type LitIntlContextValue = {
message: IntlMessage;
locale?: string;
};

export const LitIntlContext = React.createContext<LitIntlContextValue | undefined>(undefined);
7 changes: 7 additions & 0 deletions src/lit-intl.provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { LitIntlContext, type LitIntlContextValue } from './lit-intl.context';

type LitIntlProviderProps = React.PropsWithChildren<LitIntlContextValue>;

export function LitIntlProvider({ children, locale, message }: LitIntlProviderProps) {
return <LitIntlContext.Provider value={{ message, locale }}>{children}</LitIntlContext.Provider>;
}
3 changes: 3 additions & 0 deletions src/types/intl-message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export type IntlMessage = {
[id: string]: IntlMessage | string;
};
5 changes: 5 additions & 0 deletions src/types/translation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type React from 'react';

import { type PrimitiveType } from 'intl-messageformat';

export type TranslationValue = Record<string, PrimitiveType | React.ReactNode>;
69 changes: 69 additions & 0 deletions src/use-translation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React from 'react';

import { LitIntlContext } from './lit-intl.context';
import { type IntlMessage } from './types/intl-message';
import { type TranslationValue } from './types/translation';

import IntlMessageFormat from 'intl-messageformat';

function resolvePath(messages: IntlMessage, idPath: string) {
let message = messages;

idPath.split('.').forEach((part) => {
const next = message[part] as IntlMessage;

if (part == null || next == null) {
throw new Error(
`Could not resolve \`${idPath}\` in \`${JSON.stringify(messages, null, 2)}\`.`,
);
}

message = next;
});

return message;
}

export function useTranslation(path?: string) {
const context = React.useContext(LitIntlContext);
const cachedFormatByLocaleRef = React.useRef<Record<string, Record<string, IntlMessageFormat>>>(
{},
);

if (context == null) {
throw new Error();
}

const { message: allMessage, locale = 'not-locale' } = context;
const intlMessage = React.useMemo(() => {
if (path == null) return allMessage;
return resolvePath(allMessage, path);
}, [allMessage, path]);

if (intlMessage == null) {
throw new Error();
}

return (idPath: string, value?: TranslationValue) => {
const cachedFormatByLocale = cachedFormatByLocaleRef.current;

let messageFormat: IntlMessageFormat;
if (cachedFormatByLocale[locale]?.[idPath] != null) {
messageFormat = cachedFormatByLocale[locale][idPath];
} else {
const message = resolvePath(intlMessage, idPath);

if (typeof message == 'object') {
throw new Error();
}

messageFormat = new IntlMessageFormat(message, locale);
if (!cachedFormatByLocale[locale]) {
cachedFormatByLocale[locale] = {};
}
cachedFormatByLocale[locale][idPath] = messageFormat;
}

return messageFormat.format(value);
};
}
4 changes: 4 additions & 0 deletions test/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { cleanup } from '@testing-library/react';
import { afterEach } from 'vitest';

afterEach(cleanup);
41 changes: 41 additions & 0 deletions test/use-translation.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { LitIntlProvider } from '../src/lit-intl.provider';
import { type IntlMessage } from '../src/types/intl-message';
import { type TranslationValue } from '../src/types/translation';
import { useTranslation } from '../src/use-translation';

import { render, screen } from '@testing-library/react';
import { it } from 'vitest';

const message: IntlMessage = {
Basic: {
Hello: 'Hello',
'Hello {name}': 'Hello {name}',
},
};

function Provider({ children }: React.PropsWithChildren) {
return <LitIntlProvider message={message}>{children}</LitIntlProvider>;
}

function renderMessage(message: string, values?: TranslationValue) {
function Component() {
const t = useTranslation('Basic');
return <>{t(message, values)}</>;
}

return render(
<Provider>
<Component />
</Provider>,
);
}

it('should be print Hello', () => {
renderMessage('Hello');
screen.getByText('Hello');
});

it('should be print Hello with name value', () => {
renderMessage('Hello {name}', { name: 'world' });
screen.getByText('Hello world');
});
4 changes: 3 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
{
"compilerOptions": {
"moduleResolution": "node",
"moduleResolution": "bundler",
"target": "es6",
"module": "esnext",
"lib": ["dom", "esnext"],
"outDir": "dist",
"jsx": "react-jsx",
"strict": true,
"sourceMap": true,
"declaration": true,
Expand Down
5 changes: 5 additions & 0 deletions vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { defineConfig } from 'vitest/config';

export default defineConfig({
test: { environment: 'jsdom', setupFiles: './test/setup.ts' },
});
Loading

0 comments on commit b770c6f

Please sign in to comment.