Finalize first i18n implementation
This commit is contained in:
parent
927b4b85b5
commit
cfcff69b95
@ -26,6 +26,7 @@ import IconFavs from '@/components/icons/IconFavs';
|
||||
import IconEdit from '@/components/icons/IconEdit';
|
||||
import { photoNeedsToBeSynced } from '@/photo/sync';
|
||||
import { KEY_COMMANDS } from '@/photo/key-commands';
|
||||
import { APP_TEXT } from '@/app/config';
|
||||
|
||||
export default function AdminPhotoMenu({
|
||||
photo,
|
||||
@ -48,7 +49,7 @@ export default function AdminPhotoMenu({
|
||||
|
||||
const sectionMain = useMemo(() => {
|
||||
const items: ComponentProps<typeof MoreMenuItem>[] = [{
|
||||
label: 'Edit',
|
||||
label: APP_TEXT.admin.edit,
|
||||
icon: <IconEdit
|
||||
size={15}
|
||||
className="translate-x-[0.5px]"
|
||||
@ -58,7 +59,7 @@ export default function AdminPhotoMenu({
|
||||
}];
|
||||
if (includeFavorite) {
|
||||
items.push({
|
||||
label: isFav ? 'Unfavorite' : 'Favorite',
|
||||
label: isFav ? APP_TEXT.admin.unfavorite : APP_TEXT.admin.favorite,
|
||||
icon: <IconFavs
|
||||
size={14}
|
||||
className="translate-x-[-1px] translate-y-[0.5px]"
|
||||
@ -76,7 +77,7 @@ export default function AdminPhotoMenu({
|
||||
});
|
||||
}
|
||||
items.push({
|
||||
label: 'Download',
|
||||
label: APP_TEXT.admin.download,
|
||||
icon: <MdOutlineFileDownload
|
||||
size={17}
|
||||
className="translate-x-[-1px]"
|
||||
@ -86,9 +87,9 @@ export default function AdminPhotoMenu({
|
||||
...showKeyCommands && { keyCommand: KEY_COMMANDS.download },
|
||||
});
|
||||
items.push({
|
||||
label: 'Sync',
|
||||
label: APP_TEXT.admin.sync,
|
||||
labelComplex: <span className="inline-flex items-center gap-2">
|
||||
<span>Sync</span>
|
||||
<span>{APP_TEXT.admin.sync}</span>
|
||||
{photoNeedsToBeSynced(photo) &&
|
||||
<InsightsIndicatorDot
|
||||
colorOverride="blue"
|
||||
@ -115,7 +116,7 @@ export default function AdminPhotoMenu({
|
||||
]);
|
||||
|
||||
const sectionDelete: ComponentProps<typeof MoreMenuItem>[] = useMemo(() => [{
|
||||
label: 'Delete',
|
||||
label: APP_TEXT.admin.delete,
|
||||
icon: <BiTrash
|
||||
size={15}
|
||||
className="translate-x-[-1px]"
|
||||
|
||||
@ -9,6 +9,7 @@ import {
|
||||
absolutePathForCamera,
|
||||
absolutePathForCameraImage,
|
||||
} from '@/app/paths';
|
||||
import { APP_TEXT } from '@/app/config';
|
||||
|
||||
// Meta functions moved to separate file to avoid
|
||||
// dependencies (camelcase-keys) found in photo/index.ts
|
||||
@ -19,8 +20,9 @@ export const titleForCamera = (
|
||||
photos: Photo[],
|
||||
explicitCount?: number,
|
||||
) => [
|
||||
'Shot on',
|
||||
APP_TEXT.category.cameraShare(
|
||||
formatCameraText(cameraFromPhoto(photos[0], camera)),
|
||||
),
|
||||
photoQuantityText(explicitCount ?? photos.length),
|
||||
].join(' ');
|
||||
|
||||
@ -28,10 +30,9 @@ export const shareTextForCamera = (
|
||||
camera: Camera,
|
||||
photos: Photo[],
|
||||
) =>
|
||||
[
|
||||
'Photos shot on',
|
||||
APP_TEXT.category.cameraShare(
|
||||
formatCameraText(cameraFromPhoto(photos[0], camera)),
|
||||
].join(' ');
|
||||
);
|
||||
|
||||
export const descriptionForCameraPhotos = (
|
||||
photos: Photo[],
|
||||
|
||||
@ -3,6 +3,7 @@ import LoaderButton from './primitives/LoaderButton';
|
||||
import clsx from 'clsx/lite';
|
||||
import { toastSuccess } from '@/toast';
|
||||
import { ComponentProps } from 'react';
|
||||
import { APP_TEXT } from '@/app/config';
|
||||
|
||||
export default function CopyButton({
|
||||
label,
|
||||
@ -29,7 +30,7 @@ export default function CopyButton({
|
||||
onClick={text
|
||||
? () => {
|
||||
navigator.clipboard.writeText(text);
|
||||
toastSuccess(`${label} copied to clipboard`);
|
||||
toastSuccess(APP_TEXT.misc.copyPhrase(label));
|
||||
}
|
||||
: undefined}
|
||||
styleAs="link"
|
||||
|
||||
@ -9,6 +9,7 @@ import { FiUploadCloud } from 'react-icons/fi';
|
||||
import { MAX_IMAGE_SIZE } from '@/platforms/next-image';
|
||||
import ProgressButton from './primitives/ProgressButton';
|
||||
import { useAppState } from '@/state/AppState';
|
||||
import { APP_TEXT } from '@/app/config';
|
||||
|
||||
export default function ImageInput({
|
||||
ref: inputRefExternal,
|
||||
@ -84,9 +85,13 @@ export default function ImageInput({
|
||||
>
|
||||
{isUploading
|
||||
? filesLength > 1
|
||||
? `Uploading ${fileUploadIndex + 1} of ${filesLength}`
|
||||
: 'Uploading'
|
||||
: 'Upload Photos'}
|
||||
? APP_TEXT.utility.paginate(
|
||||
fileUploadIndex + 1,
|
||||
filesLength,
|
||||
APP_TEXT.admin.uploading,
|
||||
)
|
||||
: APP_TEXT.admin.uploading
|
||||
: APP_TEXT.admin.uploadPhotos}
|
||||
</ProgressButton>}
|
||||
<input
|
||||
ref={inputRef}
|
||||
|
||||
@ -19,6 +19,7 @@ import {
|
||||
} from '@/utility/string';
|
||||
import { AnnotatedTag } from '@/photo/form';
|
||||
import PhotoFilmIcon from './PhotoFilmIcon';
|
||||
import { APP_TEXT } from '@/app/config';
|
||||
|
||||
export type FilmWithCount = {
|
||||
film: string
|
||||
@ -67,7 +68,7 @@ export const titleForFilm = (
|
||||
export const shareTextForFilm = (
|
||||
film: string,
|
||||
) =>
|
||||
`Photos shot on ${labelForFilm(film).large}`;
|
||||
APP_TEXT.category.filmShare(labelForFilm(film).large);
|
||||
|
||||
export const descriptionForFilmPhotos = (
|
||||
photos: Photo[],
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
absolutePathForFocalLength,
|
||||
absolutePathForFocalLengthImage,
|
||||
} from '@/app/paths';
|
||||
import { APP_TEXT } from '@/app/config';
|
||||
|
||||
export type FocalLengths = {
|
||||
focal: number
|
||||
@ -31,12 +32,12 @@ export const titleForFocalLength = (
|
||||
photos: Photo[],
|
||||
explicitCount?: number,
|
||||
) => [
|
||||
`${formatFocalLength(focal)} Focal Length`,
|
||||
APP_TEXT.category.focalLengthTitle(formatFocalLengthSafe(focal)),
|
||||
photoQuantityText(explicitCount ?? photos.length),
|
||||
].join(' ');
|
||||
|
||||
export const shareTextFocalLength = (focal: number) =>
|
||||
`Photos shot at ${formatFocalLength(focal)}`;
|
||||
APP_TEXT.category.focalLengthShare(formatFocalLengthSafe(focal));
|
||||
|
||||
export const descriptionForFocalLengthPhotos = (
|
||||
photos: Photo[],
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { I18NDeepPartial } from '..';
|
||||
import { ptBR } from 'date-fns/locale';
|
||||
|
||||
const TEXT: I18NDeepPartial = {
|
||||
photo: {
|
||||
@ -7,21 +8,30 @@ const TEXT: I18NDeepPartial = {
|
||||
taken: 'Capturado',
|
||||
created: 'Criado',
|
||||
updated: 'Atualizado',
|
||||
copied: 'Link para foto copiado',
|
||||
},
|
||||
category: {
|
||||
camera: 'Câmera',
|
||||
cameraPlural: 'Câmeras',
|
||||
cameraTitle: (camera: string) => `Tirado com ${camera}`,
|
||||
cameraShare: (camera: string) => `Fotos tiradas com ${camera}`,
|
||||
lens: 'Lente',
|
||||
lensPlural: 'Lentes',
|
||||
tag: 'Tag',
|
||||
tagPlural: 'Tags',
|
||||
tagged: 'Marcado',
|
||||
taggedPhotos: 'Fotos Marcadas',
|
||||
taggedPhrase: (tag: string) => `Fotos marcadas com '${tag}'`,
|
||||
taggedFavs: 'Fotos Favoritas',
|
||||
recipe: 'Receita',
|
||||
recipePlural: 'Receitas',
|
||||
recipeShare: (recipe: string) => `Fotos da receita ${recipe}`,
|
||||
film: 'Filme',
|
||||
filmPlural: 'Filmes',
|
||||
filmShare: (film: string) => `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}`,
|
||||
},
|
||||
nav: {
|
||||
home: 'Início',
|
||||
@ -62,6 +72,7 @@ const TEXT: I18NDeepPartial = {
|
||||
uploadPhotos: 'Enviar Fotos',
|
||||
upload: 'Enviar',
|
||||
uploadPlural: 'Envios',
|
||||
uploading: 'Enviando',
|
||||
updates: 'Atualizações',
|
||||
managePhotos: 'Gerenciar Fotos',
|
||||
manageCameras: 'Gerenciar Câmeras',
|
||||
@ -73,10 +84,29 @@ const TEXT: I18NDeepPartial = {
|
||||
batchExitEdit: 'Sair da Edição em Lote',
|
||||
appInsights: 'Insights do App',
|
||||
appConfig: 'Configuração do App',
|
||||
edit: 'Editar',
|
||||
favorite: 'Favoritar',
|
||||
unfavorite: 'Remover dos Favoritos',
|
||||
download: 'Baixar',
|
||||
sync: 'Sincronizar',
|
||||
delete: 'Excluir',
|
||||
deleteConfirm: (photoTitle: string) =>
|
||||
`Tem certeza que deseja excluir "${photoTitle}"?`,
|
||||
},
|
||||
misc: {
|
||||
repo: 'Feito com',
|
||||
copyPhrase: (label: string) => `${label} copiado`,
|
||||
},
|
||||
utility: {
|
||||
paginate: (
|
||||
index: number,
|
||||
count: number,
|
||||
action?: string,
|
||||
) => action
|
||||
? `${action} ${index} de ${count}`
|
||||
: `${index} de ${count}`,
|
||||
},
|
||||
dateLocale: ptBR,
|
||||
};
|
||||
|
||||
export default TEXT;
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { enUS } from 'date-fns/locale';
|
||||
|
||||
const TEXT = {
|
||||
photo: {
|
||||
photo: 'Photo',
|
||||
@ -5,21 +7,30 @@ const TEXT = {
|
||||
taken: 'Taken',
|
||||
created: 'Created',
|
||||
updated: 'Updated',
|
||||
copied: 'Link to photo copied',
|
||||
},
|
||||
category: {
|
||||
camera: 'Camera',
|
||||
cameraPlural: 'Cameras',
|
||||
cameraTitle: (camera: string) => `Shot on ${camera}`,
|
||||
cameraShare: (camera: string) => `Photos shot on ${camera}`,
|
||||
lens: 'Lens',
|
||||
lensPlural: 'Lenses',
|
||||
tag: 'Tag',
|
||||
tagPlural: 'Tags',
|
||||
tagged: 'Tagged Photos',
|
||||
taggedPhotos: 'Tagged Photos',
|
||||
taggedPhrase: (tag: string) => `Photos tagged '${tag}'`,
|
||||
taggedFavs: 'Favorite Photos',
|
||||
recipe: 'Recipe',
|
||||
recipePlural: 'Recipes',
|
||||
recipeShare: (recipe: string) => `${recipe} recipe photos`,
|
||||
film: 'Film',
|
||||
filmPlural: 'Films',
|
||||
filmShare: (film: string) => `Photos shot on ${film}`,
|
||||
focalLength: 'Focal Length',
|
||||
focalLengthPlural: 'Focal Lengths',
|
||||
focalLengthTitle: (focal: string) => `Focal Length ${focal}`,
|
||||
focalLengthShare: (focal: string) => `Photos shot at ${focal}`,
|
||||
},
|
||||
nav: {
|
||||
home: 'Home',
|
||||
@ -60,6 +71,7 @@ const TEXT = {
|
||||
uploadPhotos: 'Upload Photos',
|
||||
upload: 'Upload',
|
||||
uploadPlural: 'Uploads',
|
||||
uploading: 'Uploading',
|
||||
updates: 'Updates',
|
||||
managePhotos: 'Manage Photos',
|
||||
manageCameras: 'Manage Cameras',
|
||||
@ -71,17 +83,29 @@ const TEXT = {
|
||||
batchExitEdit: 'Exit Batch Edit',
|
||||
appInsights: 'App Insights',
|
||||
appConfig: 'App Configuration',
|
||||
edit: 'Edit',
|
||||
favorite: 'Favorite',
|
||||
unfavorite: 'Unfavorite',
|
||||
download: 'Download',
|
||||
sync: 'Sync',
|
||||
delete: 'Delete',
|
||||
deleteConfirm: (photoTitle: string) =>
|
||||
`Are you sure you want to delete "${photoTitle}?"`,
|
||||
},
|
||||
misc: {
|
||||
repo: 'Made with',
|
||||
copyPhrase: (label: string) => `${label} copied`,
|
||||
},
|
||||
utility: {
|
||||
paginate: (
|
||||
index: number,
|
||||
count: number,
|
||||
verb?: string,
|
||||
) => verb
|
||||
? `${verb} ${index} of ${count}`
|
||||
action?: string,
|
||||
) => action
|
||||
? `${action} ${index} of ${count}`
|
||||
: `${index} of ${count}`,
|
||||
},
|
||||
dateLocale: enUS,
|
||||
};
|
||||
|
||||
export default TEXT;
|
||||
|
||||
@ -9,6 +9,7 @@ import {
|
||||
absolutePathForLens,
|
||||
absolutePathForLensImage,
|
||||
} from '@/app/paths';
|
||||
import { APP_TEXT } from '@/app/config';
|
||||
|
||||
// Meta functions moved to separate file to avoid
|
||||
// dependencies (camelcase-keys) found in photo/index.ts
|
||||
@ -19,7 +20,7 @@ export const titleForLens = (
|
||||
photos: Photo[],
|
||||
explicitCount?: number,
|
||||
) => [
|
||||
'Lens:',
|
||||
`${APP_TEXT.category.lens}:`,
|
||||
formatLensText(lensFromPhoto(photos[0], lens)),
|
||||
photoQuantityText(explicitCount ?? photos.length),
|
||||
].join(' ');
|
||||
@ -29,7 +30,7 @@ export const shareTextForLens = (
|
||||
photos: Photo[],
|
||||
) =>
|
||||
[
|
||||
'Lens:',
|
||||
`${APP_TEXT.category.lens}:`,
|
||||
formatLensText(lensFromPhoto(photos[0], lens)),
|
||||
].join(' ');
|
||||
|
||||
|
||||
@ -155,9 +155,13 @@ export default function PhotoHeader({
|
||||
}} />}
|
||||
</>
|
||||
: <ResponsiveText
|
||||
shortText={APP_TEXT.paginate(paginationIndex, paginationCount)}
|
||||
shortText={APP_TEXT.utility.paginate(
|
||||
paginationIndex,
|
||||
paginationCount,
|
||||
entityVerb,
|
||||
)}
|
||||
>
|
||||
{APP_TEXT.paginate(
|
||||
{APP_TEXT.utility.paginate(
|
||||
paginationIndex,
|
||||
paginationCount,
|
||||
entityVerb)}
|
||||
|
||||
@ -172,7 +172,7 @@ export const photoStatsAsString = (photo: Photo) => [
|
||||
].join(' ');
|
||||
|
||||
export const descriptionForPhoto = (photo: Photo) =>
|
||||
photo.takenAtNaiveFormatted?.toUpperCase();
|
||||
formatDate({ date: photo.takenAt }).toLocaleUpperCase();
|
||||
|
||||
export const getPreviousPhoto = (photo: Photo, photos: Photo[]) => {
|
||||
const index = photos.findIndex(p => p.id === photo.id);
|
||||
@ -251,7 +251,7 @@ export const photoQuantityText = (
|
||||
: `${count} ${photoLabelForCount(count, capitalize)}`;
|
||||
|
||||
export const deleteConfirmationTextForPhoto = (photo: Photo) =>
|
||||
`Are you sure you want to delete "${titleForPhoto(photo)}?"`;
|
||||
APP_TEXT.admin.deleteConfirm(titleForPhoto(photo));
|
||||
|
||||
export type PhotoDateRange = { start: string, end: string };
|
||||
|
||||
@ -265,9 +265,10 @@ export const descriptionForPhotoSet = (
|
||||
dateBased
|
||||
? dateRangeForPhotos(photos, explicitDateRange).description.toUpperCase()
|
||||
: [
|
||||
explicitCount ?? photos.length,
|
||||
descriptor,
|
||||
photoLabelForCount(explicitCount ?? photos.length, false),
|
||||
explicitCount ?? photos.length, (
|
||||
descriptor ||
|
||||
photoLabelForCount(explicitCount ?? photos.length, false)
|
||||
),
|
||||
].join(' ');
|
||||
|
||||
const sortPhotosByDateNonDestructively = (
|
||||
|
||||
@ -19,6 +19,7 @@ import { TbChecklist } from 'react-icons/tb';
|
||||
import CopyButton from '@/components/CopyButton';
|
||||
import { labelForFilm } from '@/film';
|
||||
import PhotoRecipe from './PhotoRecipe';
|
||||
import { APP_TEXT } from '@/app/config';
|
||||
|
||||
export default function PhotoRecipeOverlay({
|
||||
ref,
|
||||
@ -138,7 +139,7 @@ export default function PhotoRecipeOverlay({
|
||||
'text-black/40 active:text-black/75',
|
||||
'hover:text-black/40',
|
||||
)}
|
||||
tooltip="Copy recipe text"
|
||||
tooltip={APP_TEXT.tooltip.recipeCopy}
|
||||
tooltipColor="frosted"
|
||||
/>
|
||||
<span>
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
} from '@/utility/string';
|
||||
import { FujifilmRecipe } from '@/platforms/fujifilm/recipe';
|
||||
import { labelForFilm } from '@/film';
|
||||
import { APP_TEXT } from '@/app/config';
|
||||
|
||||
export type RecipeWithCount = {
|
||||
recipe: string
|
||||
@ -32,12 +33,12 @@ export const titleForRecipe = (
|
||||
photos:Photo[] = [],
|
||||
explicitCount?: number,
|
||||
) => [
|
||||
`Recipe: ${formatRecipe(recipe)}`,
|
||||
`${APP_TEXT.category.recipe}: ${formatRecipe(recipe)}`,
|
||||
photoQuantityText(explicitCount ?? photos.length),
|
||||
].join(' ');
|
||||
|
||||
export const shareTextForRecipe = (recipe: string) =>
|
||||
`${formatRecipe(recipe)} recipe photos`;
|
||||
APP_TEXT.category.recipeShare(formatRecipe(recipe));
|
||||
|
||||
export const descriptionForRecipePhotos = (
|
||||
photos: Photo[] = [],
|
||||
|
||||
@ -8,7 +8,7 @@ import { ReactNode, useEffect } from 'react';
|
||||
import { shortenUrl } from '@/utility/url';
|
||||
import { toastSuccess } from '@/toast';
|
||||
import { PiXLogo } from 'react-icons/pi';
|
||||
import { SHOW_SOCIAL } from '@/app/config';
|
||||
import { APP_TEXT, SHOW_SOCIAL } from '@/app/config';
|
||||
import { generateXPostText } from '@/utility/social';
|
||||
import { useAppState } from '@/state/AppState';
|
||||
import useOnPathChange from '@/utility/useOnPathChange';
|
||||
@ -96,7 +96,7 @@ export default function ShareModal({
|
||||
<BiCopy size={18} />,
|
||||
() => {
|
||||
navigator.clipboard.writeText(pathShare);
|
||||
toastSuccess('Link to photo copied');
|
||||
toastSuccess(APP_TEXT.photo.copied);
|
||||
},
|
||||
true,
|
||||
)}
|
||||
|
||||
@ -26,7 +26,7 @@ export default function TagHeader({
|
||||
entity={isTagFavs(tag)
|
||||
? <FavsTag contrast="high" />
|
||||
: <PhotoTag tag={tag} contrast="high" />}
|
||||
entityVerb={APP_TEXT.category.tagged}
|
||||
entityVerb={APP_TEXT.category.taggedPhotos}
|
||||
entityDescription={descriptionForTaggedPhotos(photos, undefined, count)}
|
||||
photos={photos}
|
||||
selectedPhoto={selectedPhoto}
|
||||
|
||||
@ -16,6 +16,7 @@ import {
|
||||
formatCountDescriptive,
|
||||
} from '@/utility/string';
|
||||
import { sortCategoryByCount } from '@/category';
|
||||
import { APP_TEXT } from '@/app/config';
|
||||
|
||||
// Reserved tags
|
||||
export const TAG_FAVS = 'favs';
|
||||
@ -48,7 +49,9 @@ export const titleForTag = (
|
||||
].join(' ');
|
||||
|
||||
export const shareTextForTag = (tag: string) =>
|
||||
isTagFavs(tag) ? 'Favorite photos' : `Photos tagged '${formatTag(tag)}'`;
|
||||
isTagFavs(tag)
|
||||
? APP_TEXT.category.taggedFavs
|
||||
: APP_TEXT.category.taggedPhrase(formatTag(tag));
|
||||
|
||||
export const sortTagsArray = (
|
||||
tags: string[],
|
||||
@ -95,7 +98,7 @@ export const descriptionForTaggedPhotos = (
|
||||
) =>
|
||||
descriptionForPhotoSet(
|
||||
photos,
|
||||
'tagged',
|
||||
APP_TEXT.category.taggedPhotos,
|
||||
dateBased,
|
||||
explicitCount,
|
||||
explicitDateRange,
|
||||
@ -139,5 +142,6 @@ export const convertTagsForForm = (tags: Tags = []) =>
|
||||
.map(({ tag, count }) => ({
|
||||
value: tag,
|
||||
annotation: formatCount(count),
|
||||
annotationAria: formatCountDescriptive(count, 'tagged'),
|
||||
annotationAria:
|
||||
formatCountDescriptive(count, APP_TEXT.category.taggedPhotos),
|
||||
}));
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { parseISO, parse, format } from 'date-fns';
|
||||
import { formatInTimeZone } from 'date-fns-tz';
|
||||
import { Timezone } from './timezone';
|
||||
import { APP_TEXT } from '@/app/config';
|
||||
|
||||
const DATE_STRING_FORMAT_TINY = 'dd MMM yy';
|
||||
const DATE_STRING_FORMAT_TINY_PLACEHOLDER = '00 000 00';
|
||||
@ -65,8 +66,10 @@ export const formatDate = ({
|
||||
return showPlaceholder
|
||||
? placeholderString
|
||||
: timezone
|
||||
? formatInTimeZone(date, timezone, formatString)
|
||||
: format(date, formatString);
|
||||
? formatInTimeZone(
|
||||
date, timezone, formatString, { locale: APP_TEXT.dateLocale },
|
||||
)
|
||||
: format(date, formatString, { locale: APP_TEXT.dateLocale });
|
||||
};
|
||||
|
||||
export const formatDateFromPostgresString = (date: string, length?: Length) =>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user