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