From e7ac590023b8ec6b29b4cb44113d0aacd44843aa Mon Sep 17 00:00:00 2001 From: "Ibrahima G. Coulibaly" Date: Tue, 15 Jul 2025 19:42:34 +0100 Subject: [PATCH] fix: i18n tsc --- .idea/workspace.xml | 124 ++++++++++-------- src/@types/i18n.d.ts | 33 +++++ src/components/Hero.tsx | 26 ++-- src/i18n/index.ts | 7 +- src/pages/tools/number/generic-calc/index.tsx | 7 +- src/tools/defineTool.tsx | 4 +- src/tools/index.ts | 2 +- 7 files changed, 124 insertions(+), 79 deletions(-) create mode 100644 src/@types/i18n.d.ts diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 2f2582e..91c161a 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -5,8 +5,15 @@ + + + + + + + - { - "keyToString": { - "ASKED_ADD_EXTERNAL_FILES": "true", - "ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true", - "Docker.Dockerfile build.executor": "Run", - "Docker.Dockerfile.executor": "Run", - "Node.js.add-i18n-to-meta.js.executor": "Run", - "Node.js.locize-upload.js.executor": "Run", - "Node.js.update-i18n-from-meta.js.executor": "Run", - "Playwright.Create transparent PNG.should make png color transparent.executor": "Run", - "Playwright.JoinText Component.executor": "Run", - "Playwright.JoinText Component.should merge text pieces with specified join character.executor": "Run", - "RunOnceActivity.OpenProjectViewOnStart": "true", - "RunOnceActivity.ShowReadmeOnStart": "true", - "RunOnceActivity.git.unshallow": "true", - "Vitest.compute function (1).executor": "Run", - "Vitest.compute function.executor": "Run", - "Vitest.generatePassword.executor": "Run", - "Vitest.mergeText.executor": "Run", - "Vitest.mergeText.should merge lines and preserve blank lines when deleteBlankLines is false.executor": "Run", - "Vitest.mergeText.should merge lines, preserve blank lines and trailing spaces when both deleteBlankLines and deleteTrailingSpaces are false.executor": "Run", - "Vitest.parsePageRanges.executor": "Run", - "Vitest.removeDuplicateLines function.executor": "Run", - "Vitest.removeDuplicateLines function.newlines option.executor": "Run", - "Vitest.removeDuplicateLines function.newlines option.should filter newlines when newlines is set to filter.executor": "Run", - "Vitest.replaceText function (regexp mode).should return the original text when passed an invalid regexp.executor": "Run", - "Vitest.replaceText function.executor": "Run", - "Vitest.timeBetweenDates.executor": "Run", - "git-widget-placeholder": "main", - "ignore.virus.scanning.warn.message": "true", - "kotlin-language-version-configured": "true", - "last_opened_file_path": "C:/Users/Ibrahima/IdeaProjects/omni-tools", - "node.js.detected.package.eslint": "true", - "node.js.detected.package.tslint": "true", - "node.js.selected.package.eslint": "(autodetect)", - "node.js.selected.package.tslint": "(autodetect)", - "nodejs_package_manager_path": "npm", - "npm.build.executor": "Run", - "npm.dev.executor": "Run", - "npm.i18n:pull.executor": "Run", - "npm.i18n:push.executor": "Run", - "npm.i18n:sync.executor": "Run", - "npm.lint.executor": "Run", - "npm.prebuild.executor": "Run", - "npm.script:create:tool.executor": "Run", - "npm.test.executor": "Run", - "npm.test:e2e.executor": "Run", - "npm.test:e2e:run.executor": "Run", - "prettierjs.PrettierConfiguration.Package": "C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\prettier", - "project.structure.last.edited": "Problems", - "project.structure.proportion": "0.0", - "project.structure.side.proportion": "0.2", - "settings.editor.selected.configurable": "refactai_advanced_settings", - "ts.external.directory.path": "C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\typescript\\lib", - "vue.rearranger.settings.migration": "true" + +}]]> - + @@ -412,8 +420,8 @@ - + diff --git a/src/@types/i18n.d.ts b/src/@types/i18n.d.ts new file mode 100644 index 0000000..a0554fa --- /dev/null +++ b/src/@types/i18n.d.ts @@ -0,0 +1,33 @@ +import 'i18next'; + +import translation from '../../public/locales/en/translation.json'; +import string from '../../public/locales/en/string.json'; +import number from '../../public/locales/en/number.json'; +import video from '../../public/locales/en/video.json'; +import list from '../../public/locales/en/list.json'; +import json from '../../public/locales/en/json.json'; +import time from '../../public/locales/en/time.json'; +import csv from '../../public/locales/en/csv.json'; +import pdf from '../../public/locales/en/pdf.json'; +import audio from '../../public/locales/en/audio.json'; +import xml from '../../public/locales/en/xml.json'; +import image from '../../public/locales/en/image.json'; + +declare module 'i18next' { + interface CustomTypeOptions { + resources: { + translation: typeof translation; + string: typeof string; + number: typeof number; + video: typeof video; + list: typeof list; + json: typeof json; + time: typeof time; + csv: typeof csv; + pdf: typeof pdf; + audio: typeof audio; + xml: typeof xml; + image: typeof image; + }; + } +} diff --git a/src/components/Hero.tsx b/src/components/Hero.tsx index 18c99d5..91143d6 100644 --- a/src/components/Hero.tsx +++ b/src/components/Hero.tsx @@ -18,7 +18,7 @@ import { useNavigate } from 'react-router-dom'; import { Icon } from '@iconify/react'; import { getToolCategoryTitle } from '@utils/string'; import { useTranslation } from 'react-i18next'; -import { validNamespaces } from '../i18n'; +import { FullI18nKey, validNamespaces } from '../i18n'; import { getBookmarkedToolPaths, isBookmarked, @@ -42,7 +42,7 @@ const GroupItems = styled('ul')({ }); type ToolInfo = { - label: string; + label: FullI18nKey; url: string; }; @@ -58,39 +58,39 @@ export default function Hero() { const exampleTools: ToolInfo[] = [ { - label: t('translation:hero.examples.createTransparentImage'), + label: 'translation:hero.examples.createTransparentImage', url: '/image-generic/create-transparent' }, { - label: t('translation:hero.examples.prettifyJson'), + label: 'translation:hero.examples.prettifyJson', url: '/json/prettify' }, { - label: t('translation:hero.examples.changeGifSpeed'), + label: 'translation:hero.examples.changeGifSpeed', url: '/gif/change-speed' }, { - label: t('translation:hero.examples.sortList'), + label: 'translation:hero.examples.sortList', url: '/list/sort' }, { - label: t('translation:hero.examples.compressPng'), + label: 'translation:hero.examples.compressPng', url: '/png/compress-png' }, { - label: t('translation:hero.examples.splitText'), + label: 'translation:hero.examples.splitText', url: '/string/split' }, { - label: t('translation:hero.examples.splitPdf'), + label: 'translation:hero.examples.splitPdf', url: '/pdf/split-pdf' }, { - label: t('translation:hero.examples.trimVideo'), + label: 'translation:hero.examples.trimVideo', url: '/video/trim' }, { - label: t('translation:hero.examples.calculateNumberSum'), + label: 'translation:hero.examples.calculateNumberSum', url: '/number/sum' } ]; @@ -117,7 +117,7 @@ export default function Hero() { if (tool === undefined) { return []; } - return [{ ...tool, label: t(tool.label) }]; + return [tool]; }) : exampleTools; @@ -256,7 +256,7 @@ export default function Hero() { }} > - {tool.label} + {t(tool.label)} {bookmarkedToolPaths.length > 0 && ( { diff --git a/src/i18n/index.ts b/src/i18n/index.ts index d0d392b..95993c1 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -1,8 +1,8 @@ -import i18n, { ParseKeys } from 'i18next'; +import i18n, { Namespace, ParseKeys } from 'i18next'; import { initReactI18next } from 'react-i18next'; import Backend from 'i18next-http-backend'; -export const validNamespaces: (string | 'translation')[] = [ +export const validNamespaces = [ 'string', 'number', 'video', @@ -15,7 +15,8 @@ export const validNamespaces: (string | 'translation')[] = [ 'xml', 'translation', 'image' -]; +] as const satisfies readonly Namespace[]; + export type I18nNamespaces = (typeof validNamespaces)[number]; export type FullI18nKey = { [K in I18nNamespaces]: `${K}:${ParseKeys}`; diff --git a/src/pages/tools/number/generic-calc/index.tsx b/src/pages/tools/number/generic-calc/index.tsx index c311c18..c87e090 100644 --- a/src/pages/tools/number/generic-calc/index.tsx +++ b/src/pages/tools/number/generic-calc/index.tsx @@ -27,6 +27,7 @@ import Typography from '@mui/material/Typography'; import Grid from '@mui/material/Grid'; import useMediaQuery from '@mui/material/useMediaQuery'; import { useTranslation } from 'react-i18next'; +import { validNamespaces } from '../../../../i18n'; function numericSolveEquationFor( equation: string, @@ -62,7 +63,7 @@ export default async function makeTool( return function GenericCalc({ title }: ToolComponentProps) { const { showSnackBar } = useContext(CustomSnackBarContext); - const { t } = useTranslation(); + const { t } = useTranslation(validNamespaces); const theme = useTheme(); const lessThanSmall = useMediaQuery(theme.breakpoints.down('sm')); @@ -239,7 +240,9 @@ export default async function makeTool( initialValues={initialValues} toolInfo={{ title: t(calcData.i18n.name), - description: t(calcData.i18n.longDescription) + description: calcData.i18n.longDescription + ? t(calcData.i18n.longDescription) + : undefined }} verticalGroups // @ts-ignore diff --git a/src/tools/defineTool.tsx b/src/tools/defineTool.tsx index 0ec3113..ed19326 100644 --- a/src/tools/defineTool.tsx +++ b/src/tools/defineTool.tsx @@ -1,7 +1,7 @@ import ToolLayout from '../components/ToolLayout'; import React, { JSXElementConstructor, LazyExoticComponent } from 'react'; import { IconifyIcon } from '@iconify/react'; -import { FullI18nKey } from '../i18n'; +import { FullI18nKey, validNamespaces } from '../i18n'; import { useTranslation } from 'react-i18next'; export interface ToolMeta { @@ -63,7 +63,7 @@ export const defineTool = ( shortDescription: i18n.shortDescription, keywords, component: function ToolComponent() { - const { t } = useTranslation(); + const { t } = useTranslation(validNamespaces); return ( + t: TFunction ): DefinedTool[] => { if (!query) return tools;