Build-time tree-shaking for date-fns locales (#264)
Dynamically import date-fns locales for turbopack (local) and webpack (production)
This commit is contained in:
parent
d74ee39f11
commit
4946b0d262
@ -1,6 +1,7 @@
|
||||
import { removeUrlProtocol } from '@/utility/url';
|
||||
import type { NextConfig } from 'next';
|
||||
import { RemotePattern } from 'next/dist/shared/lib/image-config';
|
||||
import path from 'path';
|
||||
|
||||
const VERCEL_BLOB_STORE_ID = process.env.BLOB_READ_WRITE_TOKEN?.match(
|
||||
/^vercel_blob_rw_([a-z0-9]+)_[a-z0-9]+$/i,
|
||||
@ -40,12 +41,28 @@ if (HOSTNAME_AWS_S3) {
|
||||
remotePatterns.push(generateRemotePattern(HOSTNAME_AWS_S3));
|
||||
}
|
||||
|
||||
const LOCALE = process.env.NEXT_PUBLIC_LOCALE || 'en-us';
|
||||
const LOCALE_ALIAS = './date-fns-locale-alias';
|
||||
const LOCALE_DYNAMIC = `i18n/locales/${LOCALE}`;
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
images: {
|
||||
imageSizes: [200],
|
||||
remotePatterns,
|
||||
minimumCacheTTL: 31536000,
|
||||
},
|
||||
turbopack: {
|
||||
resolveAlias: {
|
||||
[LOCALE_ALIAS]: `@/${LOCALE_DYNAMIC}`,
|
||||
},
|
||||
},
|
||||
webpack: (config) => {
|
||||
config.resolve.alias = {
|
||||
...config.resolve.alias,
|
||||
[LOCALE_ALIAS]: path.resolve(__dirname, `src/${LOCALE_DYNAMIC}`),
|
||||
};
|
||||
return config;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = process.env.ANALYZE === 'true'
|
||||
|
||||
2
pnpm-lock.yaml
generated
2
pnpm-lock.yaml
generated
@ -8638,7 +8638,7 @@ snapshots:
|
||||
|
||||
postcss@8.4.31:
|
||||
dependencies:
|
||||
nanoid: 3.3.8
|
||||
nanoid: 3.3.11
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
|
||||
2
src/i18n/date-fns-locale-alias.ts
Normal file
2
src/i18n/date-fns-locale-alias.ts
Normal file
@ -0,0 +1,2 @@
|
||||
// Dynamically resolves in next.config.ts
|
||||
export { enUS as default } from 'date-fns/locale/en-US';
|
||||
@ -1,7 +1,7 @@
|
||||
import EN_US from './locales/en-us';
|
||||
import { TEXT as EN_US } from './locales/en-us';
|
||||
import { setDefaultOptions } from 'date-fns';
|
||||
import { enUS, id, ptBR, pt, zhCN } from 'date-fns/locale';
|
||||
import { APP_LOCALE } from '@/app/config';
|
||||
// Dynamically resolves in next.config.ts
|
||||
import locale from './date-fns-locale-alias';
|
||||
|
||||
export type I18N = typeof EN_US;
|
||||
|
||||
@ -10,38 +10,29 @@ export type I18NDeepPartial = {
|
||||
}
|
||||
|
||||
/**
|
||||
* Translation steps for contributors:
|
||||
* 1. Create new file in `src/i18n/locales` modeled on `en-us.ts`.
|
||||
* 2. Add import to `localeTextImports`
|
||||
* 3. Add date-fn locale to `getDateFnLocale`
|
||||
* TRANSLATION STEPS FOR CONTRIBUTORS:
|
||||
* 1. Create new file in `src/i18n/locales` modeled on `en-us.ts`—
|
||||
* MAKE SURE to export a default date-fns locale
|
||||
* 3. Add import to `LOCALE_TEXT_IMPORTS`
|
||||
* 4. Test locally
|
||||
* 5. Add translation/credit to `README.md` Supported Languages
|
||||
* 4. Add translation/credit to `README.md` Supported Languages
|
||||
*/
|
||||
|
||||
const localeTextImports: Record<
|
||||
const LOCALE_TEXT_IMPORTS: Record<
|
||||
string,
|
||||
() => Promise<I18NDeepPartial | undefined>
|
||||
> = {
|
||||
'pt-br': () => import('./locales/pt-br').then(m => m.default),
|
||||
'pt-pt': () => import('./locales/pt-pt').then(m => m.default),
|
||||
'id-id': () => import('./locales/id-id').then(m => m.default),
|
||||
'zh-cn': () => import('./locales/zh-cn').then(m => m.default),
|
||||
};
|
||||
|
||||
const getDateFnLocale = (locale: string) => {
|
||||
switch (locale) {
|
||||
case 'id-id': return id;
|
||||
case 'pt-pt': return pt;
|
||||
case 'pt-br': return ptBR;
|
||||
case 'zh-cn': return zhCN;
|
||||
default: return enUS;
|
||||
}
|
||||
'pt-br': () => import('./locales/pt-br').then(m => m.TEXT),
|
||||
'pt-pt': () => import('./locales/pt-pt').then(m => m.TEXT),
|
||||
'id-id': () => import('./locales/id-id').then(m => m.TEXT),
|
||||
'zh-cn': () => import('./locales/zh-cn').then(m => m.TEXT),
|
||||
};
|
||||
|
||||
export const getTextForLocale = async (locale: string): Promise<I18N> => {
|
||||
const text = EN_US;
|
||||
|
||||
Object.entries(await localeTextImports[locale.toLocaleLowerCase()]?.() ?? {})
|
||||
Object.entries(
|
||||
await LOCALE_TEXT_IMPORTS[locale.toLocaleLowerCase()]?.() ?? {},
|
||||
)
|
||||
.forEach(([key, value]) => {
|
||||
// Fall back to English for missing keys
|
||||
text[key as keyof I18N] = {
|
||||
@ -53,5 +44,4 @@ export const getTextForLocale = async (locale: string): Promise<I18N> => {
|
||||
return text;
|
||||
};
|
||||
|
||||
export const setDefaultDateFnLocale = () =>
|
||||
setDefaultOptions({ locale: getDateFnLocale(APP_LOCALE) });
|
||||
export const setDefaultDateFnLocale = () => setDefaultOptions({ locale });
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
const TEXT = {
|
||||
export { enUS as default } from 'date-fns/locale/en-US';
|
||||
|
||||
export const TEXT = {
|
||||
photo: {
|
||||
photo: 'Photo',
|
||||
photoPlural: 'Photos',
|
||||
@ -114,5 +116,3 @@ const TEXT = {
|
||||
paginateAction: '{{action}} {{index}} of {{count}}',
|
||||
},
|
||||
};
|
||||
|
||||
export default TEXT;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { I18NDeepPartial } from '..';
|
||||
export { id as default } from 'date-fns/locale/id';
|
||||
|
||||
const TEXT: I18NDeepPartial = {
|
||||
export const TEXT: I18NDeepPartial = {
|
||||
photo: {
|
||||
photo: 'Foto',
|
||||
photoPlural: 'Foto',
|
||||
@ -115,5 +116,3 @@ const TEXT: I18NDeepPartial = {
|
||||
paginateAction: '{{action}} {{index}} dari {{count}}',
|
||||
},
|
||||
};
|
||||
|
||||
export default TEXT;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { I18NDeepPartial } from '..';
|
||||
export { ptBR as default } from 'date-fns/locale/pt-BR';
|
||||
|
||||
const TEXT: I18NDeepPartial = {
|
||||
export const TEXT: I18NDeepPartial = {
|
||||
photo: {
|
||||
photo: 'Foto',
|
||||
photoPlural: 'Fotos',
|
||||
@ -116,5 +117,3 @@ const TEXT: I18NDeepPartial = {
|
||||
paginateAction: '{{action}} {{index}} de {{count}}',
|
||||
},
|
||||
};
|
||||
|
||||
export default TEXT;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { I18NDeepPartial } from '..';
|
||||
export { pt as default } from 'date-fns/locale/pt';
|
||||
|
||||
const TEXT: I18NDeepPartial = {
|
||||
export const TEXT: I18NDeepPartial = {
|
||||
photo: {
|
||||
photo: 'Fotografia',
|
||||
photoPlural: 'Fotografias',
|
||||
@ -116,5 +117,3 @@ const TEXT: I18NDeepPartial = {
|
||||
paginateAction: '{{action}} {{index}} de {{count}}',
|
||||
},
|
||||
};
|
||||
|
||||
export default TEXT;
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
const TEXT = {
|
||||
import { I18NDeepPartial } from '..';
|
||||
export { zhCN as default } from 'date-fns/locale/zh-CN';
|
||||
|
||||
export const TEXT: I18NDeepPartial = {
|
||||
photo: {
|
||||
photo: '照片',
|
||||
photoPlural: '照片',
|
||||
@ -113,5 +116,3 @@ const TEXT = {
|
||||
paginateAction: '{{action}} 第 {{index}} 页,共 {{count}} 页',
|
||||
},
|
||||
};
|
||||
|
||||
export default TEXT;
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
import { createContext, use } from 'react';
|
||||
import { generateAppTextState } from '.';
|
||||
import EN_US from '../locales/en-us';
|
||||
import { TEXT as EN_US } from '../locales/en-us';
|
||||
|
||||
export const AppTextContext = createContext(generateAppTextState(EN_US));
|
||||
|
||||
|
||||
@ -1,10 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"target": "ES2019",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
@ -22,18 +19,9 @@
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
"@/*": ["./src/*"],
|
||||
},
|
||||
"target": "ES2019"
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user