Skip to content

Commit

Permalink
feat: allow SSR and SSG with getLocaleProps (#25)
Browse files Browse the repository at this point in the history
  • Loading branch information
QuiiBz authored Aug 17, 2022
1 parent a7e7674 commit cbe3ff3
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 29 deletions.
10 changes: 4 additions & 6 deletions examples/next/locales/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { createI18n } from 'next-international';
import type Locale from './en';

export const { useI18n, I18nProvider, useChangeLocale, defineLocale, getLocaleStaticProps } = createI18n<typeof Locale>(
{
en: () => import('./en'),
fr: () => import('./fr'),
},
);
export const { useI18n, I18nProvider, useChangeLocale, defineLocale, getLocaleProps } = createI18n<typeof Locale>({
en: () => import('./en'),
fr: () => import('./fr'),
});
5 changes: 3 additions & 2 deletions examples/next/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -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();
Expand All @@ -9,6 +9,7 @@ const Home = () => {

return (
<div>
<h1>SSG</h1>
<p>Hello: {t('hello')}</p>
<p>
Hello:{' '}
Expand Down Expand Up @@ -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;
48 changes: 48 additions & 0 deletions examples/next/pages/ssr.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div>
<h1>SSR</h1>
<p>Hello: {t('hello')}</p>
<p>
Hello:{' '}
{t('welcome', {
name: 'John',
})}
</p>
<p>
Hello:{' '}
{t('about.you', {
age: '23',
name: 'Doe',
})}
</p>
<p>{t2('test')}</p>
<p>
{t2('param', {
param: 'test',
})}
</p>
<p>{t2('and.more.test')}</p>
<p>{t('missing.translation.in.fr')}</p>
<button type="button" onClick={() => changeLocale('en')}>
EN
</button>
<button type="button" onClick={() => changeLocale('fr')}>
FR
</button>
</div>
);
};

// Comment this to disable SSR of initial locale
export const getServerSideProps: GetServerSideProps = getLocaleProps();

export default Home;
26 changes: 20 additions & 6 deletions packages/next-international/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import type Locale from './en'
export const {
useI18n,
I18nProvider,
getLocaleStaticProps,
getLocaleProps,
} = createI18n<typeof Locale>({
en: () => import('./en'),
fr: () => import('./fr'),
Expand Down Expand Up @@ -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 {
...
Expand Down Expand Up @@ -197,7 +211,7 @@ import type Locale from './en.json'
export const {
useI18n,
I18nProvider,
getLocaleStaticProps,
getLocaleProps,
} = createI18n<typeof Locale>({
en: () => import('./en.json'),
fr: () => import('./fr.json'),
Expand Down Expand Up @@ -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`:

Expand Down
3 changes: 3 additions & 0 deletions packages/next-international/__tests__/create-i18n.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof import('./utils/en').default>({
const { getLocaleProps } = createI18n<typeof import('./utils/en').default>({
en: () => import('./utils/en'),
fr: () => import('./utils/fr'),
});

const props = await getLocaleStaticProps()({
const props = await getLocaleProps()({
locales: ['en', 'fr'],
});

Expand All @@ -24,12 +24,12 @@ describe('getLocaleStaticProps', () => {
});

it('should return default locale', async () => {
const { getLocaleStaticProps } = createI18n<typeof import('./utils/en').default>({
const { getLocaleProps } = createI18n<typeof import('./utils/en').default>({
en: () => import('./utils/en'),
fr: () => import('./utils/fr'),
});

const props = await getLocaleStaticProps()({
const props = await getLocaleProps()({
locale: 'en',
defaultLocale: 'en',
locales: ['en', 'fr'],
Expand All @@ -43,12 +43,12 @@ describe('getLocaleStaticProps', () => {
});

it('should return default locale with existing getStaticProps', async () => {
const { getLocaleStaticProps } = createI18n<typeof import('./utils/en').default>({
const { getLocaleProps } = createI18n<typeof import('./utils/en').default>({
en: () => import('./utils/en'),
fr: () => import('./utils/fr'),
});

const props = await getLocaleStaticProps(() => ({
const props = await getLocaleProps(() => ({
props: {
hello: 'world',
},
Expand Down
Original file line number Diff line number Diff line change
@@ -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<T>(initialGetStaticProps?: GetStaticProps<T>): GetStaticProps<T> {
return async context => {
const initialResult = await initialGetStaticProps?.(context);
export function createGetLocaleProps(locales: Locales) {
return function getLocaleProps<T, GetProps extends GetStaticProps<T> | GetServerSideProps<T>>(
initialGetProps?: GetProps,
) {
return async (context: any) => {
const initialResult = await initialGetProps?.(context);

// No current locales means that `defaultLocale` does not exists
if (!context.locale) {
Expand Down
9 changes: 6 additions & 3 deletions packages/next-international/src/i18n/create-i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -13,13 +13,16 @@ export function createI18n<Locale extends BaseLocale>(locales: Locales) {
const useI18n = createUsei18n(I18nContext);
const useChangeLocale = createUseChangeLocale<typeof locales>();
const defineLocale = createDefineLocale<Locale>();
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,
};
}

0 comments on commit cbe3ff3

Please sign in to comment.