From cbe3ff3b1433fab357988a31c9a10f34a1dd3f6b Mon Sep 17 00:00:00 2001 From: Tom Lienard Date: Wed, 17 Aug 2022 19:15:17 +0200 Subject: [PATCH] feat: allow SSR and SSG with getLocaleProps (#25) --- examples/next/locales/index.ts | 10 ++-- examples/next/pages/index.tsx | 5 +- examples/next/pages/ssr.tsx | 48 +++++++++++++++++++ packages/next-international/README.md | 26 +++++++--- .../__tests__/create-i18n.test.ts | 3 ++ ...props.test.ts => get-locale-props.test.ts} | 14 +++--- .../i18n/create-get-locale-static-props.ts | 12 +++-- .../src/i18n/create-i18n.ts | 9 ++-- 8 files changed, 98 insertions(+), 29 deletions(-) create mode 100644 examples/next/pages/ssr.tsx rename packages/next-international/__tests__/{get-locale-static-props.test.ts => get-locale-props.test.ts} (74%) diff --git a/examples/next/locales/index.ts b/examples/next/locales/index.ts index 9dc8199..3913047 100644 --- a/examples/next/locales/index.ts +++ b/examples/next/locales/index.ts @@ -1,9 +1,7 @@ import { createI18n } from 'next-international'; import type Locale from './en'; -export const { useI18n, I18nProvider, useChangeLocale, defineLocale, getLocaleStaticProps } = createI18n( - { - en: () => import('./en'), - fr: () => import('./fr'), - }, -); +export const { useI18n, I18nProvider, useChangeLocale, defineLocale, getLocaleProps } = createI18n({ + en: () => import('./en'), + fr: () => import('./fr'), +}); diff --git a/examples/next/pages/index.tsx b/examples/next/pages/index.tsx index 5e6321e..91a5aec 100644 --- a/examples/next/pages/index.tsx +++ b/examples/next/pages/index.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { GetStaticProps } from 'next'; -import { getLocaleStaticProps, useChangeLocale, useI18n } from '../locales'; +import { getLocaleProps, useChangeLocale, useI18n } from '../locales'; const Home = () => { const { t, scopedT } = useI18n(); @@ -9,6 +9,7 @@ const Home = () => { return (
+

SSG

Hello: {t('hello')}

Hello:{' '} @@ -42,6 +43,6 @@ const Home = () => { }; // Comment this to disable SSR of initial locale -export const getStaticProps: GetStaticProps = getLocaleStaticProps(); +export const getStaticProps: GetStaticProps = getLocaleProps(); export default Home; diff --git a/examples/next/pages/ssr.tsx b/examples/next/pages/ssr.tsx new file mode 100644 index 0000000..8c01e94 --- /dev/null +++ b/examples/next/pages/ssr.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { GetServerSideProps } from 'next'; +import { getLocaleProps, useChangeLocale, useI18n } from '../locales'; + +const Home = () => { + const { t, scopedT } = useI18n(); + const changeLocale = useChangeLocale(); + const t2 = scopedT('scope.more'); + + return ( +

+

SSR

+

Hello: {t('hello')}

+

+ Hello:{' '} + {t('welcome', { + name: 'John', + })} +

+

+ Hello:{' '} + {t('about.you', { + age: '23', + name: 'Doe', + })} +

+

{t2('test')}

+

+ {t2('param', { + param: 'test', + })} +

+

{t2('and.more.test')}

+

{t('missing.translation.in.fr')}

+ + +
+ ); +}; + +// Comment this to disable SSR of initial locale +export const getServerSideProps: GetServerSideProps = getLocaleProps(); + +export default Home; diff --git a/packages/next-international/README.md b/packages/next-international/README.md index 49f03ab..65f8064 100644 --- a/packages/next-international/README.md +++ b/packages/next-international/README.md @@ -49,7 +49,7 @@ import type Locale from './en' export const { useI18n, I18nProvider, - getLocaleStaticProps, + getLocaleProps, } = createI18n({ en: () => import('./en'), fr: () => import('./fr'), @@ -81,14 +81,28 @@ function App({ Component, pageProps }) { } ``` -4. Add `getLocaleStaticProps` to your pages, or wrap your existing `getStaticProps` (this will allows SSR locales, see [Load initial locales client-side](#load-initial-locales-client-side) if you want to load the initial locale client-side): +4. Add `getLocaleProps` to your pages, or wrap your existing `getStaticProps` (this will allows SSR locales, see [Load initial locales client-side](#load-initial-locales-client-side) if you want to load the initial locale client-side): ```ts // locales/index.tsx -export const getStaticProps = getLocaleStaticProps() +export const getStaticProps = getLocalProps() // or with an existing `getStaticProps` function: -export const getStaticProps = getLocaleStaticProps((ctx) => { +export const getStaticProps = getLocaleProps((ctx) => { + // your existing code + return { + ... + } +}) +``` + +If you already have `getServerSideProps` on this page, you can't use `getStaticProps`. In this case, you can still use `getLocaleProps` the same way: + +```ts +export const getServerSideProps = getLocalProps() + +// or with an existing `getServerSideProps` function: +export const getServerSideProps = getLocaleProps((ctx) => { // your existing code return { ... @@ -197,7 +211,7 @@ import type Locale from './en.json' export const { useI18n, I18nProvider, - getLocaleStaticProps, + getLocaleProps, } = createI18n({ en: () => import('./en.json'), fr: () => import('./fr.json'), @@ -229,7 +243,7 @@ export const { > **Warning**: This should not be used unless you know what you're doing and what that implies. -If for x reason you don't want to SSR the initial locale, you can load it on the client. Simply remove the `getLocaleStaticProps` from your pages. +If for x reason you don't want to SSR the initial locale, you can load it on the client. Simply remove the `getLocaleProps` from your pages. You can also provide a fallback component while waiting for the initial locale to load inside `I18nProvider`: diff --git a/packages/next-international/__tests__/create-i18n.test.ts b/packages/next-international/__tests__/create-i18n.test.ts index 9d40fd1..5dd3ed5 100644 --- a/packages/next-international/__tests__/create-i18n.test.ts +++ b/packages/next-international/__tests__/create-i18n.test.ts @@ -10,6 +10,9 @@ describe('createI18n', () => { expect(result.getLocaleStaticProps).toBeDefined(); expect(result.getLocaleStaticProps).toBeInstanceOf(Function); + expect(result.getLocaleProps).toBeDefined(); + expect(result.getLocaleProps).toBeInstanceOf(Function); + expect(result.useChangeLocale).toBeDefined(); expect(result.useChangeLocale).toBeInstanceOf(Function); diff --git a/packages/next-international/__tests__/get-locale-static-props.test.ts b/packages/next-international/__tests__/get-locale-props.test.ts similarity index 74% rename from packages/next-international/__tests__/get-locale-static-props.test.ts rename to packages/next-international/__tests__/get-locale-props.test.ts index b624961..cd96a30 100644 --- a/packages/next-international/__tests__/get-locale-static-props.test.ts +++ b/packages/next-international/__tests__/get-locale-props.test.ts @@ -2,15 +2,15 @@ import { describe, expect, it, vi } from 'vitest'; import { createI18n } from '../src'; import en from './utils/en'; -describe('getLocaleStaticProps', () => { +describe('getLocaleProps', () => { it('should error if locale is not defined', async () => { const spy = vi.spyOn(console, 'error').mockImplementation(() => null); - const { getLocaleStaticProps } = createI18n({ + const { getLocaleProps } = createI18n({ en: () => import('./utils/en'), fr: () => import('./utils/fr'), }); - const props = await getLocaleStaticProps()({ + const props = await getLocaleProps()({ locales: ['en', 'fr'], }); @@ -24,12 +24,12 @@ describe('getLocaleStaticProps', () => { }); it('should return default locale', async () => { - const { getLocaleStaticProps } = createI18n({ + const { getLocaleProps } = createI18n({ en: () => import('./utils/en'), fr: () => import('./utils/fr'), }); - const props = await getLocaleStaticProps()({ + const props = await getLocaleProps()({ locale: 'en', defaultLocale: 'en', locales: ['en', 'fr'], @@ -43,12 +43,12 @@ describe('getLocaleStaticProps', () => { }); it('should return default locale with existing getStaticProps', async () => { - const { getLocaleStaticProps } = createI18n({ + const { getLocaleProps } = createI18n({ en: () => import('./utils/en'), fr: () => import('./utils/fr'), }); - const props = await getLocaleStaticProps(() => ({ + const props = await getLocaleProps(() => ({ props: { hello: 'world', }, diff --git a/packages/next-international/src/i18n/create-get-locale-static-props.ts b/packages/next-international/src/i18n/create-get-locale-static-props.ts index e37e81b..2bdc93e 100644 --- a/packages/next-international/src/i18n/create-get-locale-static-props.ts +++ b/packages/next-international/src/i18n/create-get-locale-static-props.ts @@ -1,11 +1,13 @@ import type { Locales } from '../types'; -import type { GetStaticProps } from 'next'; +import type { GetStaticProps, GetServerSideProps } from 'next'; import { error } from '../helpers/log'; -export function createGetLocaleStaticProps(locales: Locales) { - return function getLocaleStaticProps(initialGetStaticProps?: GetStaticProps): GetStaticProps { - return async context => { - const initialResult = await initialGetStaticProps?.(context); +export function createGetLocaleProps(locales: Locales) { + return function getLocaleProps | GetServerSideProps>( + initialGetProps?: GetProps, + ) { + return async (context: any) => { + const initialResult = await initialGetProps?.(context); // No current locales means that `defaultLocale` does not exists if (!context.locale) { diff --git a/packages/next-international/src/i18n/create-i18n.ts b/packages/next-international/src/i18n/create-i18n.ts index 047a9ff..61232ef 100644 --- a/packages/next-international/src/i18n/create-i18n.ts +++ b/packages/next-international/src/i18n/create-i18n.ts @@ -2,7 +2,7 @@ import React, { createContext } from 'react'; import type { Locales, LocaleContext } from '../types'; import type { BaseLocale } from 'international-types'; import { createDefineLocale } from './create-define-locale'; -import { createGetLocaleStaticProps } from './create-get-locale-static-props'; +import { createGetLocaleProps } from './create-get-locale-static-props'; import { createI18nProvider } from './create-i18n-provider'; import { createUseChangeLocale } from './create-use-change-locale'; import { createUsei18n } from './create-use-i18n'; @@ -13,13 +13,16 @@ export function createI18n(locales: Locales) { const useI18n = createUsei18n(I18nContext); const useChangeLocale = createUseChangeLocale(); const defineLocale = createDefineLocale(); - const getLocaleStaticProps = createGetLocaleStaticProps(locales); + const getLocaleProps = createGetLocaleProps(locales); return { useI18n, I18nProvider, useChangeLocale, defineLocale, - getLocaleStaticProps, + // We keep `getLocaleStaticProps` to keep backward compatibility, + // but it should be removed in the next major version. + getLocaleStaticProps: getLocaleProps, + getLocaleProps, }; }