diff --git a/interface/app/$libraryId/Explorer/Inspector/index.tsx b/interface/app/$libraryId/Explorer/Inspector/index.tsx index 5864e3ddf..0a19539e5 100644 --- a/interface/app/$libraryId/Explorer/Inspector/index.tsx +++ b/interface/app/$libraryId/Explorer/Inspector/index.tsx @@ -100,6 +100,7 @@ export const Inspector = forwardRef( explorerStore.showMoreInfo = false; }, [pathname]); + const { t } = useLocale(); return (
(
{!isNonEmpty(selectedItems) ? (
- Nothing selected + {t('nothing_selected')}
) : selectedItems.length === 1 ? ( @@ -342,7 +343,7 @@ export const SingleItemMetadata = ({ item }: { item: ExplorerItem }) => { onClick={() => { if (fullPath) { navigator.clipboard.writeText(fullPath); - toast.info('Copied path to clipboard'); + toast.info(t('path_copied_to_clipboard_title')); } }} /> diff --git a/interface/app/$libraryId/Explorer/store.ts b/interface/app/$libraryId/Explorer/store.ts index a50865499..008f9ae29 100644 --- a/interface/app/$libraryId/Explorer/store.ts +++ b/interface/app/$libraryId/Explorer/store.ts @@ -9,6 +9,7 @@ import { type ExplorerSettings, type Ordering } from '@sd/client'; +import i18n from '~/app/I18n'; import { DEFAULT_LIST_VIEW_ICON_SIZE, @@ -150,24 +151,24 @@ export function isCut(item: ExplorerItem, cutCopyState: CutCopyState) { } export const filePathOrderingKeysSchema = z.union([ - z.literal('name').describe('Name'), - z.literal('sizeInBytes').describe('Size'), - z.literal('dateModified').describe('Date Modified'), - z.literal('dateIndexed').describe('Date Indexed'), - z.literal('dateCreated').describe('Date Created'), - z.literal('object.dateAccessed').describe('Date Accessed'), - z.literal('object.mediaData.epochTime').describe('Date Taken') + z.literal('name').describe(i18n.t('name')), + z.literal('sizeInBytes').describe(i18n.t('size')), + z.literal('dateModified').describe(i18n.t('date_modified')), + z.literal('dateIndexed').describe(i18n.t('date_indexed')), + z.literal('dateCreated').describe(i18n.t('date_created')), + z.literal('object.dateAccessed').describe(i18n.t('date_accessed')), + z.literal('object.mediaData.epochTime').describe(i18n.t('date_taken')) ]); export const objectOrderingKeysSchema = z.union([ - z.literal('dateAccessed').describe('Date Accessed'), - z.literal('kind').describe('Kind'), - z.literal('mediaData.epochTime').describe('Date Taken') + z.literal('dateAccessed').describe(i18n.t('date_accessed')), + z.literal('kind').describe(i18n.t('kind')), + z.literal('mediaData.epochTime').describe(i18n.t('date_taken')) ]); export const nonIndexedPathOrderingSchema = z.union([ - z.literal('name').describe('Name'), - z.literal('sizeInBytes').describe('Size'), - z.literal('dateCreated').describe('Date Created'), - z.literal('dateModified').describe('Date Modified') + z.literal('name').describe(i18n.t('name')), + z.literal('sizeInBytes').describe(i18n.t('size')), + z.literal('dateCreated').describe(i18n.t('date_created')), + z.literal('dateModified').describe(i18n.t('date_modified')) ]); diff --git a/interface/app/$libraryId/Explorer/util.ts b/interface/app/$libraryId/Explorer/util.ts index babaaef9e..e8c1dfa09 100644 --- a/interface/app/$libraryId/Explorer/util.ts +++ b/interface/app/$libraryId/Explorer/util.ts @@ -71,65 +71,66 @@ export function loadDayjsLocale(language: string) { // Generate list of localized formats available in the app export function generateLocaleDateFormats(language: string) { language = language.replace('_', '-'); + const defaultDate = '01/01/2024 23:19'; const DATE_FORMATS = [ { value: 'L', - label: dayjs().locale(language).format('L') + label: dayjs(defaultDate).locale(language).format('L') }, { - value: 'L LT', - label: dayjs().locale(language).format('L LT') + value: 'L, LT', + label: dayjs(defaultDate).locale(language).format('L, LT') + }, + { + value: 'll', + label: dayjs(defaultDate).locale(language).format('ll') }, - // { - // value: 'll', - // label: dayjs().locale(language).format('ll') - // }, { value: 'LL', - label: dayjs().locale(language).format('LL') + label: dayjs(defaultDate).locale(language).format('LL') + }, + { + value: 'lll', + label: dayjs(defaultDate).locale(language).format('lll') }, - // { - // value: 'lll', - // label: dayjs().locale(language).format('lll') - // }, { value: 'LLL', - label: dayjs().locale(language).format('LLL') + label: dayjs(defaultDate).locale(language).format('LLL') }, { value: 'llll', - label: dayjs().locale(language).format('llll') + label: dayjs(defaultDate).locale(language).format('llll') } ]; if (language === 'en') { const additionalFormats = [ { value: 'DD/MM/YYYY', - label: dayjs().locale('en').format('DD/MM/YYYY') + label: dayjs(defaultDate).locale('en').format('DD/MM/YYYY') }, { value: 'DD/MM/YYYY HH:mm', - label: dayjs().locale('en').format('DD/MM/YYYY HH:mm') - }, - // { - // value: 'D MMM YYYY', - // label: dayjs().locale('en').format('D MMM YYYY') - // }, - { - value: 'D MMMM YYYY', - label: dayjs().locale('en').format('D MMMM YYYY') - }, - // { - // value: 'D MMM YYYY HH:mm', - // label: dayjs().locale('en').format('D MMM YYYY HH:mm') - // }, - { - value: 'D MMMM YYYY HH:mm', - label: dayjs().locale('en').format('D MMMM YYYY HH:mm') + label: dayjs(defaultDate).locale('en').format('DD/MM/YYYY HH:mm') }, { - value: 'ddd, D MMM YYYY HH:mm', - label: dayjs().locale('en').format('ddd, D MMMM YYYY HH:mm') + value: 'D MMM, YYYY', + label: dayjs(defaultDate).locale('en').format('D MMM, YYYY') + }, + { + value: 'D MMMM, YYYY', + label: dayjs(defaultDate).locale('en').format('D MMMM, YYYY') + }, + { + value: 'D MMM, YYYY HH:mm', + label: dayjs(defaultDate).locale('en').format('D MMM, YYYY HH:mm') + }, + { + value: 'D MMMM, YYYY HH:mm', + label: dayjs(defaultDate).locale('en').format('D MMMM, YYYY HH:mm') + }, + { + value: 'ddd, D MMM, YYYY HH:mm', + label: dayjs(defaultDate).locale('en').format('ddd, D MMMM, YYYY HH:mm') } ]; return DATE_FORMATS.concat(additionalFormats); diff --git a/interface/app/$libraryId/overview/LibraryStats.tsx b/interface/app/$libraryId/overview/LibraryStats.tsx index b70191c1a..5257fee75 100644 --- a/interface/app/$libraryId/overview/LibraryStats.tsx +++ b/interface/app/$libraryId/overview/LibraryStats.tsx @@ -3,7 +3,7 @@ import clsx from 'clsx'; import { useEffect, useState } from 'react'; import { byteSize, Statistics, useLibraryContext, useLibraryQuery } from '@sd/client'; import { Tooltip } from '@sd/ui'; -import { useCounter } from '~/hooks'; +import { useCounter, useLocale } from '~/hooks'; interface StatItemProps { title: string; @@ -12,25 +12,6 @@ interface StatItemProps { info?: string; } -const StatItemNames: Partial> = { - total_bytes_capacity: 'Total capacity', - preview_media_bytes: 'Preview media', - library_db_size: 'Index size', - total_bytes_free: 'Free space', - total_bytes_used: 'Total used space' -}; - -const StatDescriptions: Partial> = { - total_bytes_capacity: - 'The total capacity of all nodes connected to the library. May show incorrect values during alpha.', - preview_media_bytes: 'The total size of all preview media files, such as thumbnails.', - library_db_size: 'The size of the library database.', - total_bytes_free: 'Free space available on all nodes connected to the library.', - total_bytes_used: 'Total space used on all nodes connected to the library.' -}; - -const displayableStatItems = Object.keys(StatItemNames) as unknown as keyof typeof StatItemNames; - let mounted = false; const StatItem = (props: StatItemProps) => { @@ -92,6 +73,27 @@ const LibraryStats = () => { if (!stats.isLoading) mounted = true; }); + const { t } = useLocale(); + + const StatItemNames: Partial> = { + total_bytes_capacity: t('total_bytes_capacity'), + preview_media_bytes: t('preview_media_bytes'), + library_db_size: t('library_db_size'), + total_bytes_free: t('total_bytes_free'), + total_bytes_used: t('total_bytes_used') + }; + + const StatDescriptions: Partial> = { + total_bytes_capacity: t('total_bytes_capacity_description'), + preview_media_bytes: t('preview_media_bytes_description'), + library_db_size: t('library_db_size_description'), + total_bytes_free: t('total_bytes_free_description'), + total_bytes_used: t('total_bytes_used_description') + }; + + const displayableStatItems = Object.keys( + StatItemNames + ) as unknown as keyof typeof StatItemNames; return (
diff --git a/interface/app/$libraryId/overview/NewCard.tsx b/interface/app/$libraryId/overview/NewCard.tsx index fd708490e..52c3f9fbf 100644 --- a/interface/app/$libraryId/overview/NewCard.tsx +++ b/interface/app/$libraryId/overview/NewCard.tsx @@ -1,6 +1,7 @@ // import { X } from '@phosphor-icons/react'; import clsx from 'clsx'; import { Icon, IconName } from '~/components'; +import { useLocale } from '~/hooks'; type NewCardProps = | { @@ -30,6 +31,7 @@ export default function NewCard({ button, className }: NewCardProps) { + const { t } = useLocale(); return (
- {buttonText ? buttonText : 'Coming Soon'} + {buttonText ? buttonText : t('coming_soon')} )}
diff --git a/interface/app/$libraryId/overview/StatCard.tsx b/interface/app/$libraryId/overview/StatCard.tsx index eed8357fc..ccc2097d6 100644 --- a/interface/app/$libraryId/overview/StatCard.tsx +++ b/interface/app/$libraryId/overview/StatCard.tsx @@ -3,7 +3,7 @@ import { useEffect, useMemo, useState } from 'react'; import { byteSize } from '@sd/client'; import { Button, Card, CircularProgress, tw } from '@sd/ui'; import { Icon } from '~/components'; -import { useIsDark } from '~/hooks'; +import { useIsDark, useLocale } from '~/hooks'; type StatCardProps = { name: string; @@ -40,6 +40,8 @@ const StatCard = ({ icon, name, connectionType, ...stats }: StatCardProps) => { return Math.floor((usedSpaceSpace.value / totalSpace.value) * 100); }, [mounted, totalSpace, usedSpaceSpace]); + const { t } = useLocale(); + return (
@@ -69,13 +71,13 @@ const StatCard = ({ icon, name, connectionType, ...stats }: StatCardProps) => { {name} {freeSpace.value} - {freeSpace.unit} free of {totalSpace.value} + {freeSpace.unit} {t('free_of')} {totalSpace.value} {totalSpace.unit}
- {connectionType || 'Local'} + {connectionType || t('local')}
{/* @@ -408,7 +412,7 @@ function createBooleanFilter( export const filterRegistry = [ createInOrNotInFilter({ - name: 'Location', + name: i18n.t('location'), icon: Folder, // Phosphor folder icon extract: (arg) => { if ('filePath' in arg && 'locations' in arg.filePath) return arg.filePath.locations; @@ -443,7 +447,7 @@ export const filterRegistry = [ ) }), createInOrNotInFilter({ - name: 'Tags', + name: i18n.t('tags'), icon: CircleDashed, extract: (arg) => { if ('object' in arg && 'tags' in arg.object) return arg.object.tags; @@ -479,7 +483,7 @@ export const filterRegistry = [

- You have not created any tags + {i18n.t('no_tags')}

)} @@ -491,7 +495,7 @@ export const filterRegistry = [ } }), createInOrNotInFilter({ - name: 'Kind', + name: i18n.t('kind'), icon: Cube, extract: (arg) => { if ('object' in arg && 'kind' in arg.object) return arg.object.kind; @@ -527,7 +531,7 @@ export const filterRegistry = [ ) }), createTextMatchFilter({ - name: 'Name', + name: i18n.t('name'), icon: Textbox, extract: (arg) => { if ('filePath' in arg && 'name' in arg.filePath) return arg.filePath.name; @@ -537,7 +541,7 @@ export const filterRegistry = [ Render: ({ filter, search }) => }), createInOrNotInFilter({ - name: 'Extension', + name: i18n.t('extension'), icon: Textbox, extract: (arg) => { if ('filePath' in arg && 'extension' in arg.filePath) return arg.filePath.extension; @@ -554,7 +558,7 @@ export const filterRegistry = [ Render: ({ filter, search }) => }), createBooleanFilter({ - name: 'Hidden', + name: i18n.t('hidden'), icon: SelectionSlash, extract: (arg) => { if ('filePath' in arg && 'hidden' in arg.filePath) return arg.filePath.hidden; @@ -590,7 +594,7 @@ export const filterRegistry = [ Render: ({ filter, search }) => }) // createInOrNotInFilter({ - // name: 'Label', + // name: i18n.t('label'), // icon: Tag, // extract: (arg) => { // if ('object' in arg && 'labels' in arg.object) return arg.object.labels; @@ -625,7 +629,7 @@ export const filterRegistry = [ // idk how to handle this rn since include_descendants is part of 'path' now // // createFilter({ - // name: 'WithDescendants', + // name: i18n.t('with_descendants'), // icon: SelectionSlash, // conditions: filterTypeCondition.trueOrFalse, // setCondition: (args, condition: 'true' | 'false') => { diff --git a/interface/app/$libraryId/search/SearchBar.tsx b/interface/app/$libraryId/search/SearchBar.tsx index f301397ea..b875887ac 100644 --- a/interface/app/$libraryId/search/SearchBar.tsx +++ b/interface/app/$libraryId/search/SearchBar.tsx @@ -1,10 +1,10 @@ -import { SearchFilterArgs } from '@sd/client'; -import { Input, ModifierKeys, Shortcut } from '@sd/ui'; import { useCallback, useEffect, useRef, useState } from 'react'; import { useLocation, useNavigate } from 'react-router'; import { createSearchParams } from 'react-router-dom'; import { useDebouncedCallback } from 'use-debounce'; -import { useOperatingSystem } from '~/hooks'; +import { SearchFilterArgs } from '@sd/client'; +import { Input, ModifierKeys, Shortcut } from '@sd/ui'; +import { useLocale, useOperatingSystem } from '~/hooks'; import { keybindForOs } from '~/util/keybinds'; import { useSearchContext } from './context'; @@ -70,16 +70,19 @@ export default ({ redirectToSearch, defaultFilters, defaultTarget }: Props) => { const updateDebounce = useDebouncedCallback((value: string) => { search.setSearch?.(value); if (redirectToSearch) { - navigate({ - pathname: '../search', - search: createSearchParams({ - search: value - }).toString() - }, { - state: { - focusSearch: true + navigate( + { + pathname: '../search', + search: createSearchParams({ + search: value + }).toString() + }, + { + state: { + focusSearch: true + } } - }); + ); } }, 300); @@ -94,10 +97,12 @@ export default ({ redirectToSearch, defaultFilters, defaultTarget }: Props) => { search.setTarget?.(undefined); } + const { t } = useLocale(); + return ( { @@ -118,7 +120,7 @@ export const SearchOptions = ({ search.target === 'paths' ? 'bg-app-box' : 'hover:bg-app-box/50' )} > - Paths + {t('paths')} search.setTarget?.('objects')} @@ -126,7 +128,7 @@ export const SearchOptions = ({ search.target === 'objects' ? 'bg-app-box' : 'hover:bg-app-box/50' )} > - Objects + {t('objects')} )} @@ -221,6 +223,8 @@ function AddFilterButton() { [searchQuery] ); + const { t } = useLocale(); + return ( <> {registerFilters} @@ -234,7 +238,7 @@ function AddFilterButton() { trigger={ } > @@ -245,7 +249,7 @@ function AddFilterButton() { autoComplete="off" autoCorrect="off" variant="transparent" - placeholder="Filter..." + placeholder={`${t('filter')}...`} /> {searchQuery === '' ? ( @@ -274,6 +278,8 @@ function SaveSearchButton() { const saveSearch = useLibraryMutation('search.saved.create'); + const { t } = useLocale(); + return ( - Save Search + {t('save_search')} } > @@ -310,7 +316,7 @@ function SaveSearchButton() { onChange={(e) => setName(e.target.value)} autoFocus variant="default" - placeholder="Name" + placeholder={t('name')} className="w-[130px]" /> diff --git a/interface/app/$libraryId/search/util.tsx b/interface/app/$libraryId/search/util.tsx index 8d94bde38..3bf56d1e7 100644 --- a/interface/app/$libraryId/search/util.tsx +++ b/interface/app/$libraryId/search/util.tsx @@ -1,26 +1,27 @@ import { CircleDashed, Folder, Icon, Tag } from '@phosphor-icons/react'; import { IconTypes } from '@sd/assets/util'; import clsx from 'clsx'; +import i18n from '~/app/I18n'; import { Icon as SDIcon } from '~/components'; export const filterTypeCondition = { inOrNotIn: { - in: 'is', - notIn: 'is not' + in: i18n.t('is'), + notIn: i18n.t('is_not') }, textMatch: { - contains: 'contains', - startsWith: 'starts with', - endsWith: 'ends with', - equals: 'is' + contains: i18n.t('contains'), + startsWith: i18n.t('starts_with'), + endsWith: i18n.t('ends_with'), + equals: i18n.t('equals') }, optionalRange: { - from: 'from', - to: 'to' + from: i18n.t('from'), + to: i18n.t('to') }, trueOrFalse: { - true: 'is', - false: 'is not' + true: i18n.t('is'), + false: i18n.t('is_not') } } as const; diff --git a/interface/app/$libraryId/settings/client/appearance.tsx b/interface/app/$libraryId/settings/client/appearance.tsx index 1d1ce44f2..4522dd6e6 100644 --- a/interface/app/$libraryId/settings/client/appearance.tsx +++ b/interface/app/$libraryId/settings/client/appearance.tsx @@ -29,7 +29,7 @@ const themes: Theme[] = [ outsideColor: 'bg-[#F0F0F0]', textColor: 'text-black', border: 'border border-[#E6E6E6]', - themeName: 'Light', + themeName: i18n.t('light'), themeValue: 'vanilla' }, { @@ -37,7 +37,7 @@ const themes: Theme[] = [ outsideColor: 'bg-black', textColor: 'text-white', border: 'border border-[#323342]', - themeName: 'Dark', + themeName: i18n.t('dark'), themeValue: 'dark' }, { @@ -45,7 +45,7 @@ const themes: Theme[] = [ outsideColor: '', textColor: 'text-white', border: 'border border-[#323342]', - themeName: 'System', + themeName: i18n.t('system'), themeValue: 'system' } ]; @@ -210,7 +210,11 @@ export const Component = () => {
{/* Date Formatting Settings */} - +