Skip to content

Commit

Permalink
refactor: i18n APIs return promises
Browse files Browse the repository at this point in the history
  • Loading branch information
QuiiBz committed Mar 4, 2024
1 parent 0fb0938 commit 63737f4
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 65 deletions.
11 changes: 6 additions & 5 deletions examples/app/app/[locale]/client-component.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
'use client';

import React from 'react';
import { useChangeLocale, useI18n, useLocale, useScopedI18n } from '@/locales';
// @ts-expect-error - missing import
import React, { use } from 'react';
import { useChangeLocale, getI18n, getLocale, getScopedI18n } from '@/locales';

export function ClientComponent() {
const t = useI18n();
const scopedT = useScopedI18n('hello');
const locale = useLocale();
const t = use(getI18n());
const scopedT = use(getScopedI18n('hello'));
const locale = getLocale();
const changeLocale = useChangeLocale();

return (
Expand Down
15 changes: 9 additions & 6 deletions examples/app/app/[locale]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React from 'react';
import { useI18n, useLocale, useScopedI18n } from '@/locales';
import { getI18n, getLocale, getScopedI18n } from '@/locales';
import { ClientComponent } from './client-component';
import { Suspense } from 'react';

export default function Home() {
const t = useI18n();
const scopedT = useScopedI18n('hello');
const locale = useLocale();
export default async function Home() {
const t = await getI18n();
const scopedT = await getScopedI18n('hello');
const locale = getLocale();

return (
<div>
Expand All @@ -23,7 +24,9 @@ export default function Home() {
</p>
<p>Current locale: {locale}</p>
<hr />
<ClientComponent />
<Suspense>
<ClientComponent />
</Suspense>
</div>
);
}
2 changes: 1 addition & 1 deletion examples/app/locales/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createI18n } from 'next-international/app';
// import en from './en';

export const { useI18n, useScopedI18n, generateI18nStaticParams, useLocale, useChangeLocale } = createI18n(
export const { getI18n, getScopedI18n, generateI18nStaticParams, getLocale, useChangeLocale } = createI18n(
{
en: () => import('./en'),
fr: () => import('./fr'),
Expand Down
47 changes: 18 additions & 29 deletions packages/next-international/src/app/app/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,16 @@ import type {
GenerateI18nStaticParams,
I18nConfig,
UseChangeLocale,
UseI18n,
GetI18n,
UseLocale,
UseScopedI18n,
GetScopedI18n,
} from './types';
import { SEGMENT_NAME } from './constants';
import { useParams, useRouter, usePathname, useSearchParams } from 'next/navigation';
// @ts-expect-error - no types
import { use } from 'react';
import { createT } from './utils';

function useLocaleCache(config: I18nConfig) {
function getLocaleCache(config: I18nConfig) {
// eslint-disable-next-line react-hooks/rules-of-hooks
const params = useParams();
const locale = params[config.segmentName ?? SEGMENT_NAME];

Expand All @@ -31,24 +30,17 @@ export function createI18n<Locales extends LocalesObject, Locale extends LocaleT
): CreateI18n<Locales, Locale> {
const localesCache = new Map<string, Record<string, unknown>>();

const useI18n: UseI18n<Locale> = () => {
const locale = useLocaleCache(config);
const data = localesCache.get(locale) ?? use(locales[locale]()).default;

if (!localesCache.has(locale)) {
localesCache.set(locale, data);
}
const getI18n: GetI18n<Locale> = async () => {
const locale = getLocaleCache(config);
const data = localesCache.get(locale) ?? (await locales[locale]()).default;

// @ts-expect-error - no types
return (key, ...params) => createT(locale, data, undefined, key, ...params);
};

const useScopedI18n: UseScopedI18n<Locale> = scope => {
const locale = useLocaleCache(config);
const data = localesCache.get(locale) ?? use(locales[locale]()).default;

if (!localesCache.has(locale)) {
localesCache.set(locale, data);
}
const getScopedI18n: GetScopedI18n<Locale> = async scope => {
const locale = getLocaleCache(config);
const data = localesCache.get(locale) ?? (await locales[locale]()).default;

// @ts-expect-error - no types
return (key, ...params) => createT(locale, data, scope, key, ...params);
Expand All @@ -58,13 +50,13 @@ export function createI18n<Locales extends LocalesObject, Locale extends LocaleT
return Object.keys(locales).map(locale => ({ [config.segmentName ?? SEGMENT_NAME]: locale }));
};

const useLocale: UseLocale<Locales> = () => {
return useLocaleCache(config);
const getLocale: UseLocale<Locales> = () => {
return getLocaleCache(config);
};

const useChangeLocale: UseChangeLocale<Locales> = changeLocaleConfig => {
const router = useRouter();
const currentLocale = useLocaleCache(config);
const currentLocale = getLocaleCache(config);
const path = usePathname();
// We call the hook conditionally to avoid always opting out of Static Rendering.
// eslint-disable-next-line react-hooks/rules-of-hooks
Expand All @@ -87,23 +79,20 @@ export function createI18n<Locales extends LocalesObject, Locale extends LocaleT
if (newLocale === currentLocale) return;

locales[newLocale]().then(module => {
localesCache.set(newLocale as string, module.default);

const finalLocale = newLocale as string;
localesCache.set(finalLocale, module.default);

localesCache.set(finalLocale, module.default);
document.cookie = `locale=${finalLocale};`;

router.push(`/${newLocale as string}${pathWithoutLocale}${finalSearchParams}`);
});
};
};

return {
useI18n,
useScopedI18n,
getI18n,
getScopedI18n,
generateI18nStaticParams,
useLocale,
getLocale,
useChangeLocale,
};
}
30 changes: 15 additions & 15 deletions packages/next-international/src/app/app/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@ import type {
GenerateI18nStaticParams,
I18nConfig,
UseChangeLocale,
UseI18n,
GetI18n,
UseLocale,
UseScopedI18n,
GetScopedI18n,
} from './types';
import { SEGMENT_NAME } from './constants';
// @ts-expect-error - no types
import { cache, use } from 'react';
import { cache } from 'react';
import { staticGenerationAsyncStorage } from 'next/dist/client/components/static-generation-async-storage.external';
import { createT } from './utils';

const useLocaleCache = cache(() => {
const getLocaleCache = cache(() => {
const store = staticGenerationAsyncStorage.getStore();
const url = store?.urlPathname;

Expand Down Expand Up @@ -44,16 +44,16 @@ export function createI18n<Locales extends LocalesObject, Locale extends LocaleT
locales: Locales,
config: I18nConfig = {},
): CreateI18n<Locales, Locale> {
const useI18n: UseI18n<Locale> = () => {
const locale = useLocaleCache();
const data = use(locales[locale]()).default;
const getI18n: GetI18n<Locale> = async () => {
const locale = getLocaleCache();
const data = (await locales[locale]()).default;

return (key, ...params) => createT(locale, data, undefined, key, ...params);
};

const useScopedI18n: UseScopedI18n<Locale> = scope => {
const locale = useLocaleCache();
const data = use(locales[locale]()).default;
const getScopedI18n: GetScopedI18n<Locale> = async scope => {
const locale = getLocaleCache();
const data = (await locales[locale]()).default;

// @ts-expect-error - no types
return (key, ...params) => createT(locale, data, scope, key, ...params);
Expand All @@ -63,8 +63,8 @@ export function createI18n<Locales extends LocalesObject, Locale extends LocaleT
return Object.keys(locales).map(locale => ({ [config.segmentName ?? SEGMENT_NAME]: locale }));
};

const useLocale: UseLocale<Locales> = () => {
return useLocaleCache();
const getLocale: UseLocale<Locales> = () => {
return getLocaleCache();
};

const useChangeLocale: UseChangeLocale<Locales> = () => {
Expand All @@ -74,10 +74,10 @@ export function createI18n<Locales extends LocalesObject, Locale extends LocaleT
};

return {
useI18n,
useScopedI18n,
getI18n,
getScopedI18n,
generateI18nStaticParams,
useLocale,
getLocale,
useChangeLocale,
};
}
19 changes: 10 additions & 9 deletions packages/next-international/src/app/app/types.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import type { Keys, LocaleType, LocalesObject, Params, Scopes } from 'international-types';
import type { ReactNode } from 'react';

export type UseI18n<Locale extends LocaleType> = () => <Key extends Keys<Locale, undefined>>(
key: Key,
...params: Params<Locale, undefined, Key>
) => string | ReactNode[];
export type GetI18n<Locale extends LocaleType> = () => Promise<
<Key extends Keys<Locale, undefined>>(key: Key, ...params: Params<Locale, undefined, Key>) => string | ReactNode[]
>;

export type UseScopedI18n<Locale extends LocaleType> = <Scope extends Scopes<Locale>>(
export type GetScopedI18n<Locale extends LocaleType> = <Scope extends Scopes<Locale>>(
scope: Scope,
) => <Key extends Keys<Locale, Scope>>(key: Key, ...params: Params<Locale, Scope, Key>) => string | ReactNode[];
) => Promise<
<Key extends Keys<Locale, Scope>>(key: Key, ...params: Params<Locale, Scope, Key>) => string | ReactNode[]
>;

export type GenerateI18nStaticParams = () => Array<Record<string, string>>;

Expand All @@ -30,10 +31,10 @@ export type UseChangeLocale<Locales extends LocalesObject> = (
) => (locale: keyof Locales) => void;

export type CreateI18n<Locales extends LocalesObject, Locale extends LocaleType> = {
useI18n: UseI18n<Locale>;
useScopedI18n: UseScopedI18n<Locale>;
getI18n: GetI18n<Locale>;
getScopedI18n: GetScopedI18n<Locale>;
generateI18nStaticParams: GenerateI18nStaticParams;
useLocale: UseLocale<Locales>;
getLocale: UseLocale<Locales>;
useChangeLocale: UseChangeLocale<Locales>;
};

Expand Down

0 comments on commit 63737f4

Please sign in to comment.