Switch to template i18n strategy

This commit is contained in:
Sam Becker 2025-05-11 17:16:12 -05:00
parent 0cffeeec1c
commit 878edc713d
8 changed files with 104 additions and 71 deletions

View File

@ -21,11 +21,11 @@ export const TEMPLATE_DESCRIPTION = 'Store photos with original camera data';
export const TEMPLATE_REPO_OWNER = 'sambecker';
export const TEMPLATE_REPO_NAME = 'exif-photo-blog';
export const TEMPLATE_REPO_BRANCH = 'main';
// eslint-disable-next-line max-len
export const TEMPLATE_REPO_URL = `https://github.com/${TEMPLATE_REPO_OWNER}/${TEMPLATE_REPO_NAME}`;
export const TEMPLATE_REPO_URL =
`https://github.com/${TEMPLATE_REPO_OWNER}/${TEMPLATE_REPO_NAME}`;
export const TEMPLATE_REPO_URL_FORK = `${TEMPLATE_REPO_URL}/fork`;
// eslint-disable-next-line max-len
export const TEMPLATE_REPO_URL_README = `${TEMPLATE_REPO_URL}?tab=readme-ov-file`;
export const TEMPLATE_REPO_URL_README =
`${TEMPLATE_REPO_URL}?tab=readme-ov-file`;
export const VERCEL_GIT_PROVIDER =
process.env.NEXT_PUBLIC_VERCEL_GIT_PROVIDER;

View File

@ -631,7 +631,7 @@ export default function CommandKClient({
key={heading}
heading={<div className={clsx(
'flex items-center',
'px-2 pt-1 pb-1.5',
'px-2 py-1',
'text-xs font-medium text-dim tracking-wider',
isPending && 'opacity-20',
)}>

View File

@ -85,7 +85,7 @@ export default function ImageInput({
>
{isUploading
? filesLength > 1
? APP_TEXT.utility.paginate(
? APP_TEXT.utility.paginateAction(
fileUploadIndex + 1,
filesLength,
APP_TEXT.admin.uploading,

View File

@ -1,6 +1,7 @@
import US_EN from './locales/us-en';
import PT_BR from './locales/pt-br';
import PT_PT from './locales/pt-pt';
import { enUS, ptBR, pt } from 'date-fns/locale';
export type I18N = typeof US_EN;
@ -8,6 +9,60 @@ export type I18NDeepPartial = {
[key in keyof I18N]?: Partial<I18N[key]>;
}
const getDateFnLocale = (locale: string) => {
switch (locale) {
case 'pt-pt': return pt;
case 'pt-br': return ptBR;
default: return enUS;
}
};
const generateI18NWithFunctions = (i18nText: I18N) => {
return {
...i18nText,
category: {
...i18nText.category,
cameraTitle: (camera: string) =>
i18nText.category.cameraTitle.replace('{{camera}}', camera),
cameraShare: (camera: string) =>
i18nText.category.cameraShare.replace('{{camera}}', camera),
taggedPhrase: (tag: string) =>
i18nText.category.taggedPhrase.replace('{{tag}}', tag),
recipeShare: (recipe: string) =>
i18nText.category.recipeShare.replace('{{recipe}}', recipe),
filmShare: (film: string) =>
i18nText.category.filmShare.replace('{{film}}', film),
focalLengthTitle: (focal: string) =>
i18nText.category.focalLengthTitle.replace('{{focal}}', focal),
focalLengthShare: (focal: string) =>
i18nText.category.focalLengthShare.replace('{{focal}}', focal),
},
admin: {
...i18nText.admin,
deleteConfirm: (photoTitle: string) =>
i18nText.admin.deleteConfirm.replace('{{photoTitle}}', photoTitle),
},
misc: {
...i18nText.misc,
copyPhrase: (label: string) =>
i18nText.misc.copyPhrase.replace('{{label}}', label),
},
utility: {
...i18nText.utility,
paginate: (index: number, count: number) =>
i18nText.utility.paginate
.replace('{{index}}', index.toString())
.replace('{{count}}', count.toString()),
paginateAction: (index: number, count: number, action: string) =>
i18nText.utility.paginateAction
.replace('{{index}}', index.toString())
.replace('{{count}}', count.toString())
.replace('{{action}}', action),
},
dateLocale: getDateFnLocale(i18nText.locale),
};
};
export const LOCALE_TEXT: Record<string, I18NDeepPartial | undefined> = {
'pt-br': PT_BR,
'pt-pt': PT_PT,
@ -15,17 +70,17 @@ export const LOCALE_TEXT: Record<string, I18NDeepPartial | undefined> = {
export const getTextForLocale = (
locale = '',
): I18N => {
) => {
const text = US_EN;
Object.entries(LOCALE_TEXT[locale.toLocaleLowerCase()] ?? {})
.forEach(([key, value]) => {
// Fall back to English for missing keys
text[key as keyof I18N] = {
...text[key as keyof I18N],
...value,
} as any;
...text[key as keyof I18N] as any,
...value as any,
};
});
return text;
return generateI18NWithFunctions(text);
};

View File

@ -1,7 +1,7 @@
import { I18NDeepPartial } from '..';
import { ptBR } from 'date-fns/locale';
const TEXT: I18NDeepPartial = {
locale: 'pt-br',
photo: {
photo: 'Foto',
photoPlural: 'Fotos',
@ -13,25 +13,25 @@ const TEXT: I18NDeepPartial = {
category: {
camera: 'Câmera',
cameraPlural: 'Câmeras',
cameraTitle: (camera: string) => `Tirado com ${camera}`,
cameraShare: (camera: string) => `Fotos tiradas com ${camera}`,
cameraTitle: 'Tirado com {{camera}}',
cameraShare: 'Fotos tiradas com {{camera}}',
lens: 'Lente',
lensPlural: 'Lentes',
tag: 'Tag',
tagPlural: 'Tags',
taggedPhotos: 'Fotos marcadas',
taggedPhrase: (tag: string) => `Fotos marcadas com '${tag}'`,
taggedPhrase: 'Fotos marcadas com {{tag}}',
taggedFavs: 'Fotos favoritas',
recipe: 'Receita',
recipePlural: 'Receitas',
recipeShare: (recipe: string) => `Fotos da receita ${recipe}`,
recipeShare: 'Fotos da receita {{recipe}}',
film: 'Filme',
filmPlural: 'Filmes',
filmShare: (film: string) => `Fotos tiradas com ${film}`,
filmShare: 'Fotos tiradas com {{film}}',
focalLength: 'Distância focal',
focalLengthPlural: 'Distâncias focais',
focalLengthTitle: (focal: string) => `Distância focal ${focal}`,
focalLengthShare: (focal: string) => `Fotos tiradas em ${focal}`,
focalLengthTitle: 'Distância focal {{focal}}',
focalLengthShare: 'Fotos tiradas em {{focal}}',
},
nav: {
home: 'Início',
@ -93,8 +93,7 @@ const TEXT: I18NDeepPartial = {
download: 'Baixar',
sync: 'Sincronizar',
delete: 'Excluir',
deleteConfirm: (photoTitle: string) =>
`Tem certeza de que deseja excluir "${photoTitle}"?`,
deleteConfirm: 'Tem certeza de que deseja excluir "{{photoTitle}}"?',
},
onboarding: {
setupComplete: 'Configuração concluída!',
@ -109,18 +108,12 @@ const TEXT: I18NDeepPartial = {
finishing: 'Finalizando ...',
uploading: 'Enviando',
repo: 'Feito com',
copyPhrase: (label: string) => `${label} copiado`,
copyPhrase: '{{label}} copiado',
},
utility: {
paginate: (
index: number,
count: number,
action?: string,
) => action
? `${action} ${index} de ${count}`
: `${index} de ${count}`,
paginate: '{{index}} de {{count}}',
paginateAction: '{{action}} {{index}} de {{count}}',
},
dateLocale: ptBR,
};
export default TEXT;

View File

@ -1,7 +1,7 @@
import { I18NDeepPartial } from '..';
import { pt } from 'date-fns/locale';
const TEXT: I18NDeepPartial = {
locale: 'pt-pt',
photo: {
photo: 'Fotografia',
photoPlural: 'Fotografias',
@ -13,25 +13,25 @@ const TEXT: I18NDeepPartial = {
category: {
camera: 'Máquina Fotográfica',
cameraPlural: 'Máquinas Fotográficas',
cameraTitle: (camera: string) => `Tirado com ${camera}`,
cameraShare: (camera: string) => `Fotografias tiradas com ${camera}`,
cameraTitle: 'Tirado com {{camera}}',
cameraShare: 'Fotografias tiradas com {{camera}}',
lens: 'Objetiva',
lensPlural: 'Objetivas',
tag: 'Etiqueta',
tagPlural: 'Etiquetas',
taggedPhotos: 'Fotografias etiquetadas',
taggedPhrase: (tag: string) => `Fotos etiquetadas com '${tag}'`,
taggedPhrase: 'Fotos etiquetadas com {{tag}}',
taggedFavs: 'Fotografias favoritas',
recipe: 'Receita',
recipePlural: 'Receitas',
recipeShare: (recipe: string) => `Fotografias da receita ${recipe}`,
recipeShare: 'Fotografias da receita {{recipe}}',
film: 'Filme fotográfico',
filmPlural: 'Filmes fotográficos',
filmShare: (film: string) => `Fotografias tiradas com ${film}`,
filmShare: 'Fotografias tiradas com {{film}}',
focalLength: 'Distância focal',
focalLengthPlural: 'Distâncias focais',
focalLengthTitle: (focal: string) => `Distância focal ${focal}`,
focalLengthShare: (focal: string) => `Fotos tiradas em ${focal}`,
focalLengthTitle: 'Distância focal {{focal}}',
focalLengthShare: 'Fotos tiradas em {{focal}}',
},
nav: {
home: 'Início',
@ -93,8 +93,7 @@ const TEXT: I18NDeepPartial = {
download: 'Descarregar',
sync: 'Sincronizar',
delete: 'Excluir',
deleteConfirm: (photoTitle: string) =>
`Tens certeza de que deseja excluir "${photoTitle}"?`,
deleteConfirm: 'Tens certeza de que deseja excluir "{{photoTitle}}"?',
},
onboarding: {
setupComplete: 'Configuração concluída!',
@ -109,18 +108,12 @@ const TEXT: I18NDeepPartial = {
finishing: 'A finalizar ...',
uploading: 'A enviar',
repo: 'Feito com',
copyPhrase: (label: string) => `${label} copiado`,
copyPhrase: '{{label}} copiado',
},
utility: {
paginate: (
index: number,
count: number,
action?: string,
) => action
? `${action} ${index} de ${count}`
: `${index} de ${count}`,
paginate: '{{index}} de {{count}}',
paginateAction: '{{action}} {{index}} de {{count}}',
},
dateLocale: pt,
};
export default TEXT;

View File

@ -1,6 +1,5 @@
import { enUS } from 'date-fns/locale';
const TEXT = {
locale: 'en-us',
photo: {
photo: 'Photo',
photoPlural: 'Photos',
@ -12,25 +11,25 @@ const TEXT = {
category: {
camera: 'Camera',
cameraPlural: 'Cameras',
cameraTitle: (camera: string) => `Shot on ${camera}`,
cameraShare: (camera: string) => `Photos shot on ${camera}`,
cameraTitle: 'Shot on {{camera}}',
cameraShare: 'Photos shot on {{camera}}',
lens: 'Lens',
lensPlural: 'Lenses',
tag: 'Tag',
tagPlural: 'Tags',
taggedPhotos: 'Tagged Photos',
taggedPhrase: (tag: string) => `Photos tagged '${tag}'`,
taggedPhrase: 'Photos tagged {{tag}}',
taggedFavs: 'Favorite Photos',
recipe: 'Recipe',
recipePlural: 'Recipes',
recipeShare: (recipe: string) => `${recipe} recipe photos`,
recipeShare: '{{recipe}} recipe photos',
film: 'Film',
filmPlural: 'Films',
filmShare: (film: string) => `Photos shot on ${film}`,
filmShare: 'Photos shot on {{film}}',
focalLength: 'Focal Length',
focalLengthPlural: 'Focal Lengths',
focalLengthTitle: (focal: string) => `Focal Length ${focal}`,
focalLengthShare: (focal: string) => `Photos shot at ${focal}`,
focalLengthTitle: 'Focal Length {{focal}}',
focalLengthShare: 'Photos shot at {{focal}}',
},
nav: {
home: 'Home',
@ -92,8 +91,7 @@ const TEXT = {
download: 'Download',
sync: 'Sync',
delete: 'Delete',
deleteConfirm: (photoTitle: string) =>
`Are you sure you want to delete "${photoTitle}?"`,
deleteConfirm: 'Are you sure you want to delete "{{photoTitle}}?"',
},
onboarding: {
setupComplete: 'Setup Complete!',
@ -108,18 +106,12 @@ const TEXT = {
finishing: 'Finishing ...',
uploading: 'Uploading',
repo: 'Made with',
copyPhrase: (label: string) => `${label} copied`,
copyPhrase: '{{label}} copied',
},
utility: {
paginate: (
index: number,
count: number,
action?: string,
) => action
? `${action} ${index} of ${count}`
: `${index} of ${count}`,
paginate: '{{index}} of {{count}}',
paginateAction: '{{action}} {{index}} of {{count}}',
},
dateLocale: enUS,
};
export default TEXT;

View File

@ -155,13 +155,13 @@ export default function PhotoHeader({
}} />}
</>
: <ResponsiveText
shortText={APP_TEXT.utility.paginate(
shortText={APP_TEXT.utility.paginateAction(
paginationIndex,
paginationCount,
entityVerb,
)}
>
{APP_TEXT.utility.paginate(
{APP_TEXT.utility.paginateAction(
paginationIndex,
paginationCount,
entityVerb)}