Simplify I18N api

This commit is contained in:
Sam Becker 2025-05-12 17:46:22 -05:00
parent 7c6f4bfdeb
commit cd69e6e3ab
15 changed files with 63 additions and 66 deletions

View File

@ -9,7 +9,7 @@ import {
absolutePathForCamera,
absolutePathForCameraImage,
} from '@/app/paths';
import { I18NState } from '@/i18n/state';
import { AppTextState } from '@/i18n/state';
// Meta functions moved to separate file to avoid
// dependencies (camelcase-keys) found in photo/index.ts
@ -18,7 +18,7 @@ import { I18NState } from '@/i18n/state';
export const titleForCamera = (
camera: Camera,
photos: Photo[],
appText: I18NState,
appText: AppTextState,
explicitCount?: number,
) => [
appText.category.cameraTitle(
@ -30,7 +30,7 @@ export const titleForCamera = (
export const shareTextForCamera = (
camera: Camera,
photos: Photo[],
appText: I18NState,
appText: AppTextState,
) =>
appText.category.cameraShare(
formatCameraText(cameraFromPhoto(photos[0], camera)),
@ -38,7 +38,7 @@ export const shareTextForCamera = (
export const descriptionForCameraPhotos = (
photos: Photo[],
appText: I18NState,
appText: AppTextState,
dateBased?: boolean,
explicitCount?: number,
explicitDateRange?: PhotoDateRange,
@ -55,7 +55,7 @@ export const descriptionForCameraPhotos = (
export const generateMetaForCamera = (
camera: Camera,
photos: Photo[],
appText: I18NState,
appText: AppTextState,
explicitCount?: number,
explicitDateRange?: PhotoDateRange,
) => ({

View File

@ -19,7 +19,7 @@ import {
} from '@/utility/string';
import { AnnotatedTag } from '@/photo/form';
import PhotoFilmIcon from './PhotoFilmIcon';
import { I18NState } from '@/i18n/state';
import { AppTextState } from '@/i18n/state';
export type FilmWithCount = {
film: string
@ -59,7 +59,7 @@ export const sortFilmsWithCount = (
export const titleForFilm = (
film: string,
photos: Photo[],
appText: I18NState,
appText: AppTextState,
explicitCount?: number,
) => [
labelForFilm(film).large,
@ -68,13 +68,13 @@ export const titleForFilm = (
export const shareTextForFilm = (
film: string,
appText: I18NState,
appText: AppTextState,
) =>
appText.category.filmShare(labelForFilm(film).large);
export const descriptionForFilmPhotos = (
photos: Photo[],
appText: I18NState,
appText: AppTextState,
dateBased?: boolean,
explicitCount?: number,
explicitDateRange?: PhotoDateRange,
@ -91,7 +91,7 @@ export const descriptionForFilmPhotos = (
export const generateMetaForFilm = (
film: string,
photos: Photo[],
appText: I18NState,
appText: AppTextState,
explicitCount?: number,
explicitDateRange?: PhotoDateRange,
) => ({

View File

@ -8,7 +8,7 @@ import {
absolutePathForFocalLength,
absolutePathForFocalLengthImage,
} from '@/app/paths';
import { I18NState } from '@/i18n/state';
import { AppTextState } from '@/i18n/state';
export type FocalLengths = {
focal: number
@ -30,7 +30,7 @@ export const formatFocalLengthSafe = (focal = 0) =>
export const titleForFocalLength = (
focal: number,
photos: Photo[],
appText: I18NState,
appText: AppTextState,
explicitCount?: number,
) => [
appText.category.focalLengthTitle(formatFocalLengthSafe(focal)),
@ -39,13 +39,13 @@ export const titleForFocalLength = (
export const shareTextFocalLength = (
focal: number,
appText: I18NState,
appText: AppTextState,
) =>
appText.category.focalLengthShare(formatFocalLengthSafe(focal));
export const descriptionForFocalLengthPhotos = (
photos: Photo[],
appText: I18NState,
appText: AppTextState,
dateBased?: boolean,
explicitCount?: number,
explicitDateRange?: PhotoDateRange,
@ -62,7 +62,7 @@ export const descriptionForFocalLengthPhotos = (
export const generateMetaForFocalLength = (
focal: number,
photos: Photo[],
appText: I18NState,
appText: AppTextState,
explicitCount?: number,
explicitDateRange?: PhotoDateRange,
) => ({

View File

@ -10,8 +10,8 @@ export const LOCALE_TEXT: 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),
'pt-br': () => import('./locales/pt-br').then(m => m.default),
'pt-pt': () => import('./locales/pt-pt').then(m => m.default),
};
export const getTextForLocale = async (

View File

@ -1,7 +1,6 @@
import { I18NDeepPartial } from '..';
const TEXT: I18NDeepPartial = {
locale: 'pt-br',
photo: {
photo: 'Foto',
photoPlural: 'Fotos',

View File

@ -1,7 +1,6 @@
import { I18NDeepPartial } from '..';
const TEXT: I18NDeepPartial = {
locale: 'pt-pt',
photo: {
photo: 'Fotografia',
photoPlural: 'Fotografias',

View File

@ -1,5 +1,4 @@
const TEXT = {
locale: 'en-us',
photo: {
photo: 'Photo',
photoPlural: 'Photos',

View File

@ -3,7 +3,7 @@
import { ReactNode } from 'react';
import { AppTextContext } from './client';
import { I18N } from '..';
import { generateI18NState } from '.';
import { generateAppTextState } from '.';
export default function AppTextProviderClient({
children,
@ -13,7 +13,7 @@ export default function AppTextProviderClient({
value: I18N
}) {
return (
<AppTextContext.Provider value={generateI18NState(value)}>
<AppTextContext.Provider value={generateAppTextState(value)}>
{children}
</AppTextContext.Provider>
);

View File

@ -1,9 +1,9 @@
'use client';
import { createContext, use } from 'react';
import { generateI18NState } from '.';
import { generateAppTextState } from '.';
import US_EN from '../locales/us-en';
export const AppTextContext = createContext(generateI18NState(US_EN));
export const AppTextContext = createContext(generateAppTextState(US_EN));
export const useAppText = () => use(AppTextContext);

View File

@ -1,45 +1,45 @@
import { I18N } from '..';
export type I18NState = ReturnType<typeof generateI18NState>;
export type AppTextState = ReturnType<typeof generateAppTextState>;
export const generateI18NState = (i18nText: I18N) => {
export const generateAppTextState = (i18n: I18N) => {
return {
...i18nText,
...i18n,
category: {
...i18nText.category,
...i18n.category,
cameraTitle: (camera: string) =>
i18nText.category.cameraTitle.replace('{{camera}}', camera),
i18n.category.cameraTitle.replace('{{camera}}', camera),
cameraShare: (camera: string) =>
i18nText.category.cameraShare.replace('{{camera}}', camera),
i18n.category.cameraShare.replace('{{camera}}', camera),
taggedPhrase: (tag: string) =>
i18nText.category.taggedPhrase.replace('{{tag}}', tag),
i18n.category.taggedPhrase.replace('{{tag}}', tag),
recipeShare: (recipe: string) =>
i18nText.category.recipeShare.replace('{{recipe}}', recipe),
i18n.category.recipeShare.replace('{{recipe}}', recipe),
filmShare: (film: string) =>
i18nText.category.filmShare.replace('{{film}}', film),
i18n.category.filmShare.replace('{{film}}', film),
focalLengthTitle: (focal: string) =>
i18nText.category.focalLengthTitle.replace('{{focal}}', focal),
i18n.category.focalLengthTitle.replace('{{focal}}', focal),
focalLengthShare: (focal: string) =>
i18nText.category.focalLengthShare.replace('{{focal}}', focal),
i18n.category.focalLengthShare.replace('{{focal}}', focal),
},
admin: {
...i18nText.admin,
...i18n.admin,
deleteConfirm: (photoTitle: string) =>
i18nText.admin.deleteConfirm.replace('{{photoTitle}}', photoTitle),
i18n.admin.deleteConfirm.replace('{{photoTitle}}', photoTitle),
},
misc: {
...i18nText.misc,
...i18n.misc,
copyPhrase: (label: string) =>
i18nText.misc.copyPhrase.replace('{{label}}', label),
i18n.misc.copyPhrase.replace('{{label}}', label),
},
utility: {
...i18nText.utility,
...i18n.utility,
paginate: (index: number, count: number) =>
i18nText.utility.paginate
i18n.utility.paginate
.replace('{{index}}', index.toString())
.replace('{{count}}', count.toString()),
paginateAction: (index: number, count: number, action: string) =>
i18nText.utility.paginateAction
i18n.utility.paginateAction
.replace('{{index}}', index.toString())
.replace('{{count}}', count.toString())
.replace('{{action}}', action),

View File

@ -1,6 +1,6 @@
import { APP_LOCALE } from '@/app/config';
import { getTextForLocale } from '..';
import { generateI18NState } from '.';
import { generateAppTextState } from '.';
export const getAppText = async () =>
getTextForLocale(APP_LOCALE).then(generateI18NState);
export const getAppText = () =>
getTextForLocale(APP_LOCALE).then(generateAppTextState);

View File

@ -9,7 +9,7 @@ import {
absolutePathForLens,
absolutePathForLensImage,
} from '@/app/paths';
import { I18NState } from '@/i18n/state';
import { AppTextState } from '@/i18n/state';
// Meta functions moved to separate file to avoid
// dependencies (camelcase-keys) found in photo/index.ts
@ -18,7 +18,7 @@ import { I18NState } from '@/i18n/state';
export const titleForLens = (
lens: Lens,
photos: Photo[],
appText: I18NState,
appText: AppTextState,
explicitCount?: number,
) => [
`${appText.category.lens}:`,
@ -29,7 +29,7 @@ export const titleForLens = (
export const shareTextForLens = (
lens: Lens,
photos: Photo[],
appText: I18NState,
appText: AppTextState,
) =>
[
`${appText.category.lens}:`,
@ -38,7 +38,7 @@ export const shareTextForLens = (
export const descriptionForLensPhotos = (
photos: Photo[],
appText: I18NState,
appText: AppTextState,
dateBased?: boolean,
explicitCount?: number,
explicitDateRange?: PhotoDateRange,
@ -55,7 +55,7 @@ export const descriptionForLensPhotos = (
export const generateMetaForLens = (
lens: Lens,
photos: Photo[],
appText: I18NState,
appText: AppTextState,
explicitCount?: number,
explicitDateRange?: PhotoDateRange,
) => ({

View File

@ -24,7 +24,7 @@ import type { Metadata } from 'next';
import { FujifilmRecipe } from '@/platforms/fujifilm/recipe';
import { FujifilmSimulation } from '@/platforms/fujifilm/simulation';
import { PhotoSyncStatus, generatePhotoSyncStatus } from './sync';
import { I18NState } from '@/i18n/state';
import { AppTextState } from '@/i18n/state';
// INFINITE SCROLL: FEED
export const INFINITE_SCROLL_FEED_INITIAL =
@ -234,7 +234,7 @@ export const altTextForPhoto = (photo: Photo) =>
export const photoLabelForCount = (
count: number,
appText: I18NState,
appText: AppTextState,
_capitalize = true,
) => {
const label = count === 1
@ -247,7 +247,7 @@ export const photoLabelForCount = (
export const photoQuantityText = (
count: number,
appText: I18NState,
appText: AppTextState,
includeParentheses = true,
capitalize?: boolean,
) =>
@ -257,7 +257,7 @@ export const photoQuantityText = (
export const deleteConfirmationTextForPhoto = (
photo: Photo,
appText: I18NState,
appText: AppTextState,
) =>
appText.admin.deleteConfirm(titleForPhoto(photo));
@ -265,7 +265,7 @@ export type PhotoDateRange = { start: string, end: string };
export const descriptionForPhotoSet = (
photos:Photo[] = [],
appText: I18NState,
appText: AppTextState,
descriptor?: string,
dateBased?: boolean,
explicitCount?: number,

View File

@ -8,7 +8,7 @@ import {
} from '@/utility/string';
import { FujifilmRecipe } from '@/platforms/fujifilm/recipe';
import { labelForFilm } from '@/film';
import { I18NState } from '@/i18n/state';
import { AppTextState } from '@/i18n/state';
export type RecipeWithCount = {
recipe: string
@ -31,7 +31,7 @@ export const formatRecipe = (recipe?: string) =>
export const titleForRecipe = (
recipe: string,
photos:Photo[] = [],
appText: I18NState,
appText: AppTextState,
explicitCount?: number,
) => [
`${appText.category.recipe}: ${formatRecipe(recipe)}`,
@ -40,13 +40,13 @@ export const titleForRecipe = (
export const shareTextForRecipe = (
recipe: string,
appText: I18NState,
appText: AppTextState,
) =>
appText.category.recipeShare(formatRecipe(recipe));
export const descriptionForRecipePhotos = (
photos: Photo[] = [],
appText: I18NState,
appText: AppTextState,
dateBased?: boolean,
explicitCount?: number,
explicitDateRange?: PhotoDateRange,
@ -145,7 +145,7 @@ export const generateRecipeText = (
export const generateMetaForRecipe = (
recipe: string,
photos: Photo[],
appText: I18NState,
appText: AppTextState,
explicitCount?: number,
explicitDateRange?: PhotoDateRange,
) => ({

View File

@ -16,7 +16,7 @@ import {
formatCountDescriptive,
} from '@/utility/string';
import { sortCategoryByCount } from '@/category';
import { I18NState } from '@/i18n/state';
import { AppTextState } from '@/i18n/state';
// Reserved tags
export const TAG_FAVS = 'favs';
@ -42,7 +42,7 @@ export const getValidationMessageForTags = (tags?: string) => {
export const titleForTag = (
tag: string,
photos:Photo[] = [],
appText: I18NState,
appText: AppTextState,
explicitCount?: number,
) => [
formatTag(tag),
@ -51,7 +51,7 @@ export const titleForTag = (
export const shareTextForTag = (
tag: string,
appText: I18NState,
appText: AppTextState,
) =>
isTagFavs(tag)
? appText.category.taggedFavs
@ -96,7 +96,7 @@ export const sortTagsObjectWithoutFavs = (tags: Tags) =>
export const descriptionForTaggedPhotos = (
photos: Photo[] = [],
appText: I18NState,
appText: AppTextState,
dateBased?: boolean,
explicitCount?: number,
explicitDateRange?: PhotoDateRange,
@ -113,7 +113,7 @@ export const descriptionForTaggedPhotos = (
export const generateMetaForTag = (
tag: string,
photos: Photo[],
appText: I18NState,
appText: AppTextState,
explicitCount?: number,
explicitDateRange?: PhotoDateRange,
) => ({
@ -151,7 +151,7 @@ export const addHiddenToTags = (tags: Tags, photosCountHidden = 0) =>
export const convertTagsForForm = (
tags: Tags = [],
appText: I18NState,
appText: AppTextState,
) =>
sortTagsObjectWithoutFavs(tags)
.map(({ tag, count }) => ({