diff --git a/next.config.ts b/next.config.ts index 367c0ca6..a01450e5 100644 --- a/next.config.ts +++ b/next.config.ts @@ -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' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 349838eb..536ebd09 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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 diff --git a/src/i18n/date-fns-locale-alias.ts b/src/i18n/date-fns-locale-alias.ts new file mode 100644 index 00000000..1889ca32 --- /dev/null +++ b/src/i18n/date-fns-locale-alias.ts @@ -0,0 +1,2 @@ +// Dynamically resolves in next.config.ts +export { enUS as default } from 'date-fns/locale/en-US'; diff --git a/src/i18n/index.ts b/src/i18n/index.ts index b39b9793..3a32059a 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -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 > = { - '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 => { 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 => { return text; }; -export const setDefaultDateFnLocale = () => - setDefaultOptions({ locale: getDateFnLocale(APP_LOCALE) }); +export const setDefaultDateFnLocale = () => setDefaultOptions({ locale }); diff --git a/src/i18n/locales/en-us.ts b/src/i18n/locales/en-us.ts index f546f8a4..d5b6b69d 100644 --- a/src/i18n/locales/en-us.ts +++ b/src/i18n/locales/en-us.ts @@ -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; diff --git a/src/i18n/locales/id-id.ts b/src/i18n/locales/id-id.ts index 057e6e10..c67c6fea 100644 --- a/src/i18n/locales/id-id.ts +++ b/src/i18n/locales/id-id.ts @@ -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; diff --git a/src/i18n/locales/pt-br.ts b/src/i18n/locales/pt-br.ts index 045ae0e4..8117a6fc 100644 --- a/src/i18n/locales/pt-br.ts +++ b/src/i18n/locales/pt-br.ts @@ -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; diff --git a/src/i18n/locales/pt-pt.ts b/src/i18n/locales/pt-pt.ts index 20b5c9ef..64254654 100644 --- a/src/i18n/locales/pt-pt.ts +++ b/src/i18n/locales/pt-pt.ts @@ -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; diff --git a/src/i18n/locales/zh-cn.ts b/src/i18n/locales/zh-cn.ts index 9984714b..a0d994b0 100644 --- a/src/i18n/locales/zh-cn.ts +++ b/src/i18n/locales/zh-cn.ts @@ -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; diff --git a/src/i18n/state/client.ts b/src/i18n/state/client.ts index ca02cbb5..1d0280c9 100644 --- a/src/i18n/state/client.ts +++ b/src/i18n/state/client.ts @@ -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)); diff --git a/tsconfig.json b/tsconfig.json index 3459bff5..a2bf9c14 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -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"] }