diff --git a/.vscode/settings.json b/.vscode/settings.json index df4f6cbe..4840a4de 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,6 +13,8 @@ "Consolas", "CredentialsSignin", "datetime", + "depluralize", + "depluralizes", "Eterna", "exif", "exifr", diff --git a/__tests__/string.test.ts b/__tests__/string.test.ts index bbd33603..1885ad6d 100644 --- a/__tests__/string.test.ts +++ b/__tests__/string.test.ts @@ -1,4 +1,4 @@ -import { parameterize } from '@/utility/string'; +import { parameterize, depluralize } from '@/utility/string'; describe('String', () => { it('parameterizes', () => { @@ -10,4 +10,11 @@ describe('String', () => { expect(parameterize('"person\'s tag"')).toBe('persons-tag'); expect(parameterize('宿宿宿宿')).toBe('宿宿宿宿'); }); + it('depluralizes', () => { + expect(depluralize('lenses')).toBe('lens'); + expect(depluralize('cameras')).toBe('camera'); + expect(depluralize('tags')).toBe('tag'); + expect(depluralize('recipes')).toBe('recipe'); + expect(depluralize('films')).toBe('film'); + }); }); diff --git a/app/film/[simulation]/image/route.tsx b/app/film/[simulation]/image/route.tsx index 42d427c9..faad7909 100644 --- a/app/film/[simulation]/image/route.tsx +++ b/app/film/[simulation]/image/route.tsx @@ -9,20 +9,23 @@ import { FilmSimulation } from '@/simulation'; import { getIBMPlexMono } from '@/app/font'; import { ImageResponse } from 'next/og'; import { getImageResponseCacheControlHeaders } from '@/image-response/cache'; -import { GENERATE_STATIC_PARAMS_LIMIT } from '@/photo/db'; import { getUniqueFilmSimulations } from '@/photo/db/query'; -import { shouldGenerateStaticParamsForCategory } from '@/category/server'; +import { + shouldGenerateStaticParamsForCategory, + staticallyGenerateCategory, +} from '@/category/server'; export let generateStaticParams: (() => Promise<{ simulation: FilmSimulation }[]>) | undefined = undefined; if (shouldGenerateStaticParamsForCategory('films', 'image')) { - generateStaticParams = async () => { - const simulations = await getUniqueFilmSimulations(); - return simulations - .map(({ simulation }) => ({ simulation })) - .slice(0, GENERATE_STATIC_PARAMS_LIMIT); - }; + generateStaticParams = () => + staticallyGenerateCategory( + 'films', + 'image', + getUniqueFilmSimulations, + simulations => simulations.map(({ simulation }) => ({ simulation })), + ); } export async function GET( diff --git a/app/film/[simulation]/page.tsx b/app/film/[simulation]/page.tsx index c9ffb235..b1c892cc 100644 --- a/app/film/[simulation]/page.tsx +++ b/app/film/[simulation]/page.tsx @@ -7,8 +7,10 @@ import { Metadata } from 'next/types'; import { cache } from 'react'; import { PATH_ROOT } from '@/app/paths'; import { redirect } from 'next/navigation'; -import { shouldGenerateStaticParamsForCategory } from '@/category/server'; -import { GENERATE_STATIC_PARAMS_LIMIT } from '@/photo/db'; +import { + shouldGenerateStaticParamsForCategory, + staticallyGenerateCategory, +} from '@/category/server'; const getPhotosFilmSimulationDataCachedCached = cache(getPhotosFilmSimulationDataCached); @@ -17,12 +19,13 @@ export let generateStaticParams: (() => Promise<{ simulation: FilmSimulation }[]>) | undefined = undefined; if (shouldGenerateStaticParamsForCategory('films', 'page')) { - generateStaticParams = async () => { - const simulations = await getUniqueFilmSimulations(); - return simulations - .map(({ simulation }) => ({ simulation })) - .slice(0, GENERATE_STATIC_PARAMS_LIMIT); - }; + generateStaticParams = () => + staticallyGenerateCategory( + 'films', + 'page', + getUniqueFilmSimulations, + simulations => simulations.map(({ simulation }) => ({ simulation })), + ); } interface FilmSimulationProps { diff --git a/app/focal/[focal]/image/route.tsx b/app/focal/[focal]/image/route.tsx index 0427f2eb..f447f4cd 100644 --- a/app/focal/[focal]/image/route.tsx +++ b/app/focal/[focal]/image/route.tsx @@ -9,20 +9,24 @@ import { getImageResponseCacheControlHeaders } from '@/image-response/cache'; import FocalLengthImageResponse from '@/image-response/FocalLengthImageResponse'; import { formatFocalLength, getFocalLengthFromString } from '@/focal'; -import { GENERATE_STATIC_PARAMS_LIMIT } from '@/photo/db'; import { getUniqueFocalLengths } from '@/photo/db/query'; -import { shouldGenerateStaticParamsForCategory } from '@/category/server'; +import { + shouldGenerateStaticParamsForCategory, + staticallyGenerateCategory, +} from '@/category/server'; export let generateStaticParams: (() => Promise<{ focal: string }[]>) | undefined = undefined; if (shouldGenerateStaticParamsForCategory('focal-lengths', 'image')) { - generateStaticParams = async () => { - const focalLengths= await getUniqueFocalLengths(); - return focalLengths - .map(({ focal }) => ({ focal: formatFocalLength(focal)! })) - .slice(0, GENERATE_STATIC_PARAMS_LIMIT); - }; + generateStaticParams = () => + staticallyGenerateCategory( + 'focal-lengths', + 'image', + getUniqueFocalLengths, + focalLengths => focalLengths + .map(({ focal }) => ({ focal: formatFocalLength(focal)! })), + ); } export async function GET( diff --git a/app/focal/[focal]/page.tsx b/app/focal/[focal]/page.tsx index c63ada11..32656624 100644 --- a/app/focal/[focal]/page.tsx +++ b/app/focal/[focal]/page.tsx @@ -7,8 +7,10 @@ import { PATH_ROOT } from '@/app/paths'; import type { Metadata } from 'next'; import { redirect } from 'next/navigation'; import { cache } from 'react'; -import { shouldGenerateStaticParamsForCategory } from '@/category/server'; -import { GENERATE_STATIC_PARAMS_LIMIT } from '@/photo/db'; +import { + shouldGenerateStaticParamsForCategory, + staticallyGenerateCategory, +} from '@/category/server'; const getPhotosFocalDataCachedCached = cache((focal: number) => getPhotosFocalLengthDataCached({ @@ -20,12 +22,14 @@ export let generateStaticParams: (() => Promise<{ focal: string }[]>) | undefined = undefined; if (shouldGenerateStaticParamsForCategory('focal-lengths', 'page')) { - generateStaticParams = async () => { - const focalLengths = await getUniqueFocalLengths(); - return focalLengths - .map(({ focal }) => ({ focal: focal.toString() })) - .slice(0, GENERATE_STATIC_PARAMS_LIMIT); - }; + generateStaticParams = () => + staticallyGenerateCategory( + 'focal-lengths', + 'page', + getUniqueFocalLengths, + focalLengths => focalLengths + .map(({ focal }) => ({ focal: focal.toString() })), + ); } interface FocalLengthProps { diff --git a/app/lens/[make]/[model]/image/route.tsx b/app/lens/[make]/[model]/image/route.tsx index 14605013..4fccfbaf 100644 --- a/app/lens/[make]/[model]/image/route.tsx +++ b/app/lens/[make]/[model]/image/route.tsx @@ -6,7 +6,6 @@ import { import { getIBMPlexMono } from '@/app/font'; import { ImageResponse } from 'next/og'; import { getImageResponseCacheControlHeaders } from '@/image-response/cache'; -import { GENERATE_STATIC_PARAMS_LIMIT } from '@/photo/db'; import { getUniqueLenses } from '@/photo/db/query'; import { getLensFromParams, @@ -15,17 +14,22 @@ import { safelyGenerateLensStaticParams, } from '@/lens'; import LensImageResponse from '@/image-response/LensImageResponse'; -import { shouldGenerateStaticParamsForCategory } from '@/category/server'; +import { + shouldGenerateStaticParamsForCategory, + staticallyGenerateCategory, +} from '@/category/server'; export let generateStaticParams: (() => Promise) | undefined = undefined; if (shouldGenerateStaticParamsForCategory('lenses', 'image')) { - generateStaticParams = async () => { - const lenses = await getUniqueLenses(); - return safelyGenerateLensStaticParams(lenses) - .slice(0, GENERATE_STATIC_PARAMS_LIMIT); - }; + generateStaticParams = () => + staticallyGenerateCategory( + 'lenses', + 'image', + getUniqueLenses, + safelyGenerateLensStaticParams, + ); } export async function GET( diff --git a/app/lens/[make]/[model]/page.tsx b/app/lens/[make]/[model]/page.tsx index 638a6db5..647b83a6 100644 --- a/app/lens/[make]/[model]/page.tsx +++ b/app/lens/[make]/[model]/page.tsx @@ -11,8 +11,10 @@ import { LensProps, safelyGenerateLensStaticParams, } from '@/lens'; -import { GENERATE_STATIC_PARAMS_LIMIT } from '@/photo/db'; -import { shouldGenerateStaticParamsForCategory } from '@/category/server'; +import { + shouldGenerateStaticParamsForCategory, + staticallyGenerateCategory, +} from '@/category/server'; const getPhotosLensDataCachedCached = cache(( make: string | undefined, @@ -27,11 +29,13 @@ export let generateStaticParams: (() => Promise) | undefined = undefined; if (shouldGenerateStaticParamsForCategory('lenses', 'page')) { - generateStaticParams = async () => { - const lenses = await getUniqueLenses(); - return safelyGenerateLensStaticParams(lenses) - .slice(0, GENERATE_STATIC_PARAMS_LIMIT); - }; + generateStaticParams = () => + staticallyGenerateCategory( + 'lenses', + 'page', + getUniqueLenses, + safelyGenerateLensStaticParams, + ); } export async function generateMetadata({ diff --git a/app/recipe/[recipe]/image/route.tsx b/app/recipe/[recipe]/image/route.tsx index e6771065..7c9be7d9 100644 --- a/app/recipe/[recipe]/image/route.tsx +++ b/app/recipe/[recipe]/image/route.tsx @@ -6,21 +6,24 @@ import { import { getIBMPlexMono } from '@/app/font'; import { ImageResponse } from 'next/og'; import { getImageResponseCacheControlHeaders } from '@/image-response/cache'; -import { GENERATE_STATIC_PARAMS_LIMIT } from '@/photo/db'; import { getUniqueRecipes } from '@/photo/db/query'; import RecipeImageResponse from '@/image-response/RecipeImageResponse'; -import { shouldGenerateStaticParamsForCategory } from '@/category/server'; +import { + shouldGenerateStaticParamsForCategory, + staticallyGenerateCategory, +} from '@/category/server'; export let generateStaticParams: (() => Promise<{ recipe: string }[]>) | undefined = undefined; if (shouldGenerateStaticParamsForCategory('recipes', 'image')) { - generateStaticParams = async () => { - const recipes = await getUniqueRecipes(); - return recipes - .map(({ recipe }) => ({ recipe })) - .slice(0, GENERATE_STATIC_PARAMS_LIMIT); - }; + generateStaticParams = () => + staticallyGenerateCategory( + 'recipes', + 'image', + getUniqueRecipes, + recipes => recipes.map(({ recipe }) => ({ recipe })), + ); } export async function GET( diff --git a/app/recipe/[recipe]/page.tsx b/app/recipe/[recipe]/page.tsx index fe66b424..bdcc1079 100644 --- a/app/recipe/[recipe]/page.tsx +++ b/app/recipe/[recipe]/page.tsx @@ -7,8 +7,10 @@ import { cache } from 'react'; import { generateMetaForRecipe } from '@/recipe'; import RecipeOverview from '@/recipe/RecipeOverview'; import { getPhotosRecipeDataCached } from '@/recipe/data'; -import { shouldGenerateStaticParamsForCategory } from '@/category/server'; -import { GENERATE_STATIC_PARAMS_LIMIT } from '@/photo/db'; +import { + shouldGenerateStaticParamsForCategory, + staticallyGenerateCategory, +} from '@/category/server'; const getPhotosRecipeDataCachedCached = cache(getPhotosRecipeDataCached); @@ -16,12 +18,13 @@ export let generateStaticParams: (() => Promise<{ recipe: string }[]>) | undefined = undefined; if (shouldGenerateStaticParamsForCategory('recipes', 'page')) { - generateStaticParams = async () => { - const recipes = await getUniqueRecipes(); - return recipes - .map(({ recipe }) => ({ recipe })) - .slice(0, GENERATE_STATIC_PARAMS_LIMIT); - }; + generateStaticParams = () => + staticallyGenerateCategory( + 'recipes', + 'page', + getUniqueRecipes, + recipes => recipes.map(({ recipe }) => ({ recipe })), + ); } interface RecipeProps { diff --git a/app/tag/[tag]/image/route.tsx b/app/tag/[tag]/image/route.tsx index 0fdf0e8d..1dc33eda 100644 --- a/app/tag/[tag]/image/route.tsx +++ b/app/tag/[tag]/image/route.tsx @@ -7,20 +7,23 @@ import TagImageResponse from '@/image-response/TagImageResponse'; import { getIBMPlexMono } from '@/app/font'; import { ImageResponse } from 'next/og'; import { getImageResponseCacheControlHeaders } from '@/image-response/cache'; -import { GENERATE_STATIC_PARAMS_LIMIT } from '@/photo/db'; import { getUniqueTags } from '@/photo/db/query'; -import { shouldGenerateStaticParamsForCategory } from '@/category/server'; +import { + shouldGenerateStaticParamsForCategory, + staticallyGenerateCategory, +} from '@/category/server'; export let generateStaticParams: (() => Promise<{ tag: string }[]>) | undefined = undefined; if (shouldGenerateStaticParamsForCategory('tags', 'image')) { - generateStaticParams = async () => { - const tags = await getUniqueTags(); - return tags - .map(({ tag }) => ({ tag })) - .slice(0, GENERATE_STATIC_PARAMS_LIMIT); - }; + generateStaticParams = () => + staticallyGenerateCategory( + 'tags', + 'image', + getUniqueTags, + tags => tags.map(({ tag }) => ({ tag })), + ); } export async function GET( diff --git a/app/tag/[tag]/page.tsx b/app/tag/[tag]/page.tsx index 05daa1ec..a645a9a4 100644 --- a/app/tag/[tag]/page.tsx +++ b/app/tag/[tag]/page.tsx @@ -7,8 +7,10 @@ import { getPhotosTagDataCached } from '@/tag/data'; import type { Metadata } from 'next'; import { redirect } from 'next/navigation'; import { cache } from 'react'; -import { shouldGenerateStaticParamsForCategory } from '@/category/server'; -import { GENERATE_STATIC_PARAMS_LIMIT } from '@/photo/db'; +import { + shouldGenerateStaticParamsForCategory, + staticallyGenerateCategory, +} from '@/category/server'; const getPhotosTagDataCachedCached = cache((tag: string) => getPhotosTagDataCached({ tag, limit: INFINITE_SCROLL_GRID_INITIAL})); @@ -17,12 +19,13 @@ export let generateStaticParams: (() => Promise<{ tag: string }[]>) | undefined = undefined; if (shouldGenerateStaticParamsForCategory('tags', 'page')) { - generateStaticParams = async () => { - const tags = await getUniqueTags(); - return tags - .map(({ tag }) => ({ tag })) - .slice(0, GENERATE_STATIC_PARAMS_LIMIT); - }; + generateStaticParams = () => + staticallyGenerateCategory( + 'tags', + 'page', + getUniqueTags, + tags => tags.map(({ tag }) => ({ tag })), + ); } interface TagProps { diff --git a/src/category/server.ts b/src/category/server.ts index bd22dac2..46e49c87 100644 --- a/src/category/server.ts +++ b/src/category/server.ts @@ -7,7 +7,7 @@ import { STATICALLY_OPTIMIZED_PHOTO_CATEGORY_OG_IMAGES, } from '@/app/config'; import { GENERATE_STATIC_PARAMS_LIMIT } from '@/photo/db'; -import { pluralize } from '@/utility/string'; +import { depluralize, pluralize } from '@/utility/string'; type StaticOutput = 'page' | 'image'; @@ -30,9 +30,8 @@ export const staticallyGenerateCategory = async ( const data = (await getData()).slice(0, GENERATE_STATIC_PARAMS_LIMIT); if (IS_BUILDING) { - console.log( - `Statically generating ${key} (${pluralize(data.length, type)})`, - ); + const meta = pluralize(data.length, `${depluralize(key)} ${type}`); + console.log(`Statically generating ${meta}`); } return formatData(data); diff --git a/src/utility/string.ts b/src/utility/string.ts index a85598b0..a438a26c 100644 --- a/src/utility/string.ts +++ b/src/utility/string.ts @@ -47,6 +47,12 @@ export const pluralize = ( ) => `${count} ${count === 1 ? singular : plural ?? `${singular}s`}`; +export const depluralize = (string: string) => + // Handle plurals like "lenses" + /ses$/i.test(string) + ? string.replace(/es$/i, '') + : string.replace(/s$/i, ''); + export const formatCountDescriptive = ( count: number, verb = 'found',