From b6f5707b9e6c4d5dca61c90afc17d3427e12a709 Mon Sep 17 00:00:00 2001 From: VampireChicken12 Date: Thu, 30 Nov 2023 20:52:36 -0500 Subject: [PATCH 1/4] build: extract things to files --- src/i18n/index.ts | 64 +++++----- src/utils/checkLocalesForMissingKeys.ts | 27 ++++ src/utils/plugins/utils.ts | 64 +++++++++- src/utils/updateAvailableLocales.ts | 25 ++++ src/utils/updateLocalePercentages.ts | 46 +++++++ vite.config.ts | 158 ++---------------------- 6 files changed, 199 insertions(+), 185 deletions(-) create mode 100644 src/utils/checkLocalesForMissingKeys.ts create mode 100644 src/utils/updateAvailableLocales.ts create mode 100644 src/utils/updateLocalePercentages.ts diff --git a/src/i18n/index.ts b/src/i18n/index.ts index fbf3a3d6..75469bea 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -2,40 +2,40 @@ import { type Resource, createInstance } from "i18next"; import { waitForSpecificMessage } from "../utils/utilities"; export const availableLocales = [ - "ca-ES", - "cs-CZ", - "de-DE", - "en-US", - "es-ES", - "fa-IR", - "fr-FR", - "he-IL", - "hi-IN", - "it-IT", - "ja-JP", - "pl-PL", - "pt-BR", - "ru-RU", - "tr-TR", - "zh-CN" + "ca-ES", + "cs-CZ", + "de-DE", + "en-US", + "es-ES", + "fa-IR", + "fr-FR", + "he-IL", + "hi-IN", + "it-IT", + "ja-JP", + "pl-PL", + "pt-BR", + "ru-RU", + "tr-TR", + "zh-CN" ] as const; export const translationPercentages: Record = { - "ca-ES": 0, - "cs-CZ": 0, - "de-DE": 4, - "es-ES": 19, - "fa-IR": 0, - "fr-FR": 0, - "he-IL": 0, - "hi-IN": 0, - "it-IT": 0, - "ja-JP": 100, - "pl-PL": 3, - "pt-BR": 0, - "ru-RU": 100, - "tr-TR": 66, - "zh-CN": 75, - "en-US": 100 + "ca-ES": 0, + "cs-CZ": 0, + "de-DE": 4, + "en-US": 100, + "es-ES": 19, + "fa-IR": 0, + "fr-FR": 0, + "he-IL": 0, + "hi-IN": 0, + "it-IT": 0, + "ja-JP": 100, + "pl-PL": 3, + "pt-BR": 0, + "ru-RU": 100, + "tr-TR": 66, + "zh-CN": 75 }; export const localeDirection: Record = { "ca-ES": "ltr", diff --git a/src/utils/checkLocalesForMissingKeys.ts b/src/utils/checkLocalesForMissingKeys.ts new file mode 100644 index 00000000..97663c3a --- /dev/null +++ b/src/utils/checkLocalesForMissingKeys.ts @@ -0,0 +1,27 @@ +import type EnUS from "public/locales/en-US.json"; + +import { availableLocales } from "../i18n"; +import { type LocaleFile, flattenLocaleValues, getLocaleFile } from "./plugins/utils"; +function checkForMissingKeys(englishFile: LocaleFile, localeFile: LocaleFile) { + const { keys: englishKeys } = flattenLocaleValues(englishFile); + const { keys: localeKeys } = flattenLocaleValues(localeFile); + if (englishKeys.length !== localeKeys.length) { + const missingKeys = englishKeys.filter((key) => !localeKeys.includes(key)); + const message = `${(localeFile as unknown as EnUS)["langCode"]} is missing ${missingKeys.length} keys\nMissing keys:\n${missingKeys.join(", ")}`; + return message; + } + return false; +} +export default function checkLocalesForMissingKeys() { + const englishFile = getLocaleFile("en-US"); + const missingKeys = availableLocales + .filter((availableLocales) => availableLocales !== "en-US") + .map((locale) => { + const localeFile = getLocaleFile(locale); + return checkForMissingKeys(englishFile, localeFile); + }) + .filter(Boolean); + if (missingKeys.length) { + throw new Error(missingKeys.join("\n\n")); + } +} diff --git a/src/utils/plugins/utils.ts b/src/utils/plugins/utils.ts index a69667ae..a924925b 100644 --- a/src/utils/plugins/utils.ts +++ b/src/utils/plugins/utils.ts @@ -1,7 +1,26 @@ -import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs"; +import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync } from "fs"; import { GetInstalledBrowsers } from "get-installed-browsers"; -import { join } from "path"; +import { join, resolve } from "path"; +import type { AvailableLocales } from "../../../src/i18n"; + +import { outputFolderName } from "../../../src/utils/constants"; +export type LocaleValue = { [key: string]: LocaleValue } | string; +export type LocaleFile = { + [key: string]: LocaleValue; +}; +export const rootDir = resolve(__dirname, "../../../"); +export const srcDir = resolve(rootDir, "src"); +export const outDir = resolve(rootDir, outputFolderName); +export const publicDir = resolve(rootDir, "public"); +export const pagesDir = resolve(srcDir, "pages"); +export const assetsDir = resolve(srcDir, "assets"); +export const componentsDir = resolve(srcDir, "components"); +export const utilsDir = resolve(srcDir, "utils"); +export const hooksDir = resolve(srcDir, "hooks"); +export const i18nDir = resolve(srcDir, "i18n"); + +export const browsers = GetInstalledBrowsers(); export function copyDirectorySync(sourceDir: string, targetDir: string) { // Create the target directory if it doesn't exist if (!existsSync(targetDir)) { @@ -25,4 +44,43 @@ export function copyDirectorySync(sourceDir: string, targetDir: string) { } } } -export const browsers = GetInstalledBrowsers(); + +export const emptyOutputFolder = () => { + if (!existsSync(outDir)) return; + const files = readdirSync(outDir); + for (const file of files) { + if (file.endsWith(".zip")) continue; + const filePath = resolve(outDir, file); + const fileStat = statSync(filePath); + if (fileStat.isDirectory()) { + rmSync(filePath, { force: true, recursive: true }); + } else { + rmSync(filePath, { force: true }); + } + } +}; +export function flattenLocaleValues(localeFile: LocaleFile, parentKey = ""): { keys: string[]; values: string[] } { + let values: string[] = []; + let keys: string[] = []; + for (const key in localeFile) { + if (["langCode", "langName"].includes(key)) continue; + const { [key]: value } = localeFile; + + const currentKey = parentKey ? `${parentKey}.${key}` : key; + + if (typeof value === "object") { + const { keys: nestedKeys, values: nestedValues } = flattenLocaleValues(value, currentKey); + values = values.concat(nestedValues); + keys = keys.concat(nestedKeys); + } else { + values.push(value); + keys.push(currentKey); + } + } + + return { keys, values }; +} +export function getLocaleFile(locale: AvailableLocales): LocaleFile { + const localeFile = readFileSync(`${publicDir}/locales/${locale}.json`, "utf-8"); + return JSON.parse(localeFile) as LocaleFile; +} diff --git a/src/utils/updateAvailableLocales.ts b/src/utils/updateAvailableLocales.ts new file mode 100644 index 00000000..24e70309 --- /dev/null +++ b/src/utils/updateAvailableLocales.ts @@ -0,0 +1,25 @@ +import { readFileSync, readdirSync, writeFileSync } from "fs"; + +import { i18nDir, publicDir } from "./plugins/utils"; +function updateAvailableLocalesArray(code: string, updatedArray: string[]) { + const match = code.match(/export\s+const\s+availableLocales\s*=\s*\[([^\]]*)\]\s*as\s*const\s*;/); + + if (match) { + const [, oldArrayPart] = match; + const newArrayPart = JSON.stringify(updatedArray, null, 2).replace(/^\[|\]$/g, ""); + return code.replace(oldArrayPart, newArrayPart); + } else { + return null; + } +} + +export default function updateAvailableLocales() { + const availableLocales = readdirSync(`${publicDir}/locales`) + .filter((locale) => locale.endsWith(".json")) + .map((locale) => locale.replace(".json", "")); + const availableLocalesFile = readFileSync(`${i18nDir}/index.ts`, "utf-8"); + const updatedAvailableLocalesFile = updateAvailableLocalesArray(availableLocalesFile, availableLocales); + if (updatedAvailableLocalesFile && updatedAvailableLocalesFile !== availableLocalesFile) { + writeFileSync(`${i18nDir}/index.ts`, updatedAvailableLocalesFile); + } +} diff --git a/src/utils/updateLocalePercentages.ts b/src/utils/updateLocalePercentages.ts new file mode 100644 index 00000000..0fca0668 --- /dev/null +++ b/src/utils/updateLocalePercentages.ts @@ -0,0 +1,46 @@ +import { readFileSync, writeFileSync } from "fs"; + +import { type AvailableLocales, availableLocales } from "../i18n"; +import { type LocaleFile, flattenLocaleValues, getLocaleFile, i18nDir } from "./plugins/utils"; +function calculateLocalePercentage(englishFile: LocaleFile, localeFile: LocaleFile): number { + const { values: englishValues } = flattenLocaleValues(englishFile); + const { values: localeValues } = flattenLocaleValues(localeFile); + const differingValues = englishValues.filter((value, index) => value !== localeValues[index]); + + const localePercentage = (differingValues.length / englishValues.length) * 100; + + return Math.floor(localePercentage); +} +function calculateLocalePercentages() { + const englishFile = getLocaleFile("en-US"); + const localePercentages = new Map([["en-US", 100]]); + + availableLocales + .filter((availableLocales) => availableLocales !== "en-US") + .forEach((locale) => { + const localeFile = getLocaleFile(locale); + const localePercentage = calculateLocalePercentage(englishFile, localeFile); + localePercentages.set(locale, localePercentage); + }); + localePercentages.set("en-US", 100); + return localePercentages; +} +function updateLocalePercentageObject(code: string, updatedObject: Record) { + const match = code.match(/export\s+const\s+localePercentages\s*:\s*Record\s*=\s*({[^}]+});/); + + if (match) { + const [, oldObjectPart] = match; + const newObjectPart = JSON.stringify(updatedObject, null, 2); + return code.replace(oldObjectPart, newObjectPart); + } else { + return null; + } +} +export default function updateLocalePercentages() { + const localePercentages = calculateLocalePercentages(); + const localePercentagesFile = readFileSync(`${i18nDir}/index.ts`, "utf-8"); + const updatedLocalePercentagesFile = updateLocalePercentageObject(localePercentagesFile, Object.fromEntries(localePercentages)); + if (updatedLocalePercentagesFile && updatedLocalePercentagesFile !== localePercentagesFile) { + writeFileSync(`${i18nDir}/index.ts`, updatedLocalePercentagesFile); + } +} diff --git a/vite.config.ts b/vite.config.ts index c98d6efc..d7e81bfa 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,164 +1,22 @@ -import type EnUS from "public/locales/en-US.json"; - import react from "@vitejs/plugin-react-swc"; -import { existsSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "fs"; import { resolve } from "path"; import { defineConfig } from "vite"; -import { type AvailableLocales, availableLocales } from "./src/i18n"; -import { outputFolderName } from "./src/utils/constants"; +import checkLocalesForMissingKeys from "./src/utils/checkLocalesForMissingKeys"; import buildContentScript from "./src/utils/plugins/build-content-script"; import copyBuild from "./src/utils/plugins/copy-build"; import copyPublic from "./src/utils/plugins/copy-public"; import makeManifest from "./src/utils/plugins/make-manifest"; import makeReleaseZips from "./src/utils/plugins/make-release-zips"; +import { assetsDir, componentsDir, emptyOutputFolder, hooksDir, outDir, pagesDir, srcDir, utilsDir } from "./src/utils/plugins/utils"; +import updateAvailableLocales from "./src/utils/updateAvailableLocales"; +import updateLocalePercentages from "./src/utils/updateLocalePercentages"; -const root = resolve(__dirname, "src"); -const pagesDir = resolve(root, "pages"); -const assetsDir = resolve(root, "assets"); -const componentsDir = resolve(root, "components"); -const utilsDir = resolve(root, "utils"); -const hooksDir = resolve(root, "hooks"); -const outDir = resolve(__dirname, outputFolderName); -const publicDir = resolve(__dirname, "public"); -const i18nDir = resolve(__dirname, "src", "i18n"); -type TranslationValue = { [key: string]: TranslationValue } | string; - -type TranslationFile = { - [key: string]: TranslationValue; -}; -const emptyOutputFolder = () => { - if (!existsSync(outDir)) return; - const files = readdirSync(outDir); - for (const file of files) { - if (file.endsWith(".zip")) continue; - const filePath = resolve(outDir, file); - const fileStat = statSync(filePath); - if (fileStat.isDirectory()) { - rmSync(filePath, { force: true, recursive: true }); - } else { - rmSync(filePath, { force: true }); - } - } -}; -function flattenTranslationValues(translationFile: TranslationFile, parentKey = ""): { keys: string[]; values: string[] } { - let values: string[] = []; - let keys: string[] = []; - for (const key in translationFile) { - if (["langCode", "langName"].includes(key)) continue; - const { [key]: value } = translationFile; - - const currentKey = parentKey ? `${parentKey}.${key}` : key; - - if (typeof value === "object") { - const { keys: nestedKeys, values: nestedValues } = flattenTranslationValues(value, currentKey); - values = values.concat(nestedValues); - keys = keys.concat(nestedKeys); - } else { - values.push(value); - keys.push(currentKey); - } - } - - return { keys, values }; -} -function getTranslationFile(translation: AvailableLocales): TranslationFile { - const translationFile = readFileSync(`${publicDir}/locales/${translation}.json`, "utf-8"); - return JSON.parse(translationFile) as TranslationFile; -} -function calculateTranslationPercentages() { - const englishFile = getTranslationFile("en-US"); - const translationPercentages = new Map(); - - availableLocales - .filter((availableLocales) => availableLocales !== "en-US") - .forEach((translation) => { - const translationFile = getTranslationFile(translation); - const translationPercentage = calculateTranslationPercentage(englishFile, translationFile); - translationPercentages.set(translation, translationPercentage); - }); - translationPercentages.set("en-US", 100); - return translationPercentages; -} -function calculateTranslationPercentage(englishFile: TranslationFile, translationFile: TranslationFile): number { - const { values: englishValues } = flattenTranslationValues(englishFile); - const { values: translationValues } = flattenTranslationValues(translationFile); - const differingValues = englishValues.filter((value, index) => value !== translationValues[index]); - - const translationPercentage = (differingValues.length / englishValues.length) * 100; - - return Math.floor(translationPercentage); -} -function checkForMissingKeys(englishFile: TranslationFile, translationFile: TranslationFile) { - const { keys: englishKeys } = flattenTranslationValues(englishFile); - const { keys: translationKeys } = flattenTranslationValues(translationFile); - if (englishKeys.length !== translationKeys.length) { - const missingKeys = englishKeys.filter((key) => !translationKeys.includes(key)); - const message = `${(translationFile as unknown as EnUS)["langCode"]} is missing ${missingKeys.length} keys\nMissing keys:\n${missingKeys.join( - ", " - )}`; - return message; - } - return false; -} -function checkLocalesForMissingKeys() { - const englishFile = getTranslationFile("en-US"); - const missingKeys = availableLocales - .filter((availableLocales) => availableLocales !== "en-US") - .map((locale) => { - const translationFile = getTranslationFile(locale); - return checkForMissingKeys(englishFile, translationFile); - }) - .filter(Boolean); - if (missingKeys.length) { - throw new Error(missingKeys.join("\n\n")); - } -} -function updateTranslationPercentageObject(code: string, updatedObject: Record) { - const match = code.match(/export\s+const\s+translationPercentages\s*:\s*Record\s*=\s*({[^}]+});/); - - if (match) { - const [, oldObjectPart] = match; - const newObjectPart = JSON.stringify(updatedObject, null, 2); - return code.replace(oldObjectPart, newObjectPart); - } else { - return null; - } -} -function updateTranslationPercentages() { - const translationPercentages = calculateTranslationPercentages(); - const translationPercentagesFile = readFileSync(`${i18nDir}/index.ts`, "utf-8"); - const updatedTranslationPercentagesFile = updateTranslationPercentageObject(translationPercentagesFile, Object.fromEntries(translationPercentages)); - if (updatedTranslationPercentagesFile && updatedTranslationPercentagesFile !== translationPercentagesFile) { - writeFileSync(`${i18nDir}/index.ts`, updatedTranslationPercentagesFile); - } -} -function updateAvailableLocalesArray(code: string, updatedArray: string[]) { - const match = code.match(/export\s+const\s+availableLocales\s*=\s*\[([^\]]*)\]\s*as\s*const\s*;/); - - if (match) { - const [, oldArrayPart] = match; - const newArrayPart = JSON.stringify(updatedArray, null, 2).replace(/^\[|\]$/g, ""); - return code.replace(oldArrayPart, newArrayPart); - } else { - return null; - } -} -function updateAvailableLocales() { - const availableLocales = readdirSync(`${publicDir}/locales`) - .filter((locale) => locale.endsWith(".json")) - .map((locale) => locale.replace(".json", "")); - const availableLocalesFile = readFileSync(`${i18nDir}/index.ts`, "utf-8"); - const updatedAvailableLocalesFile = updateAvailableLocalesArray(availableLocalesFile, availableLocales); - if (updatedAvailableLocalesFile && updatedAvailableLocalesFile !== availableLocalesFile) { - writeFileSync(`${i18nDir}/index.ts`, updatedAvailableLocalesFile); - } -} export default function build() { emptyOutputFolder(); - updateAvailableLocales(); - checkLocalesForMissingKeys(); - updateTranslationPercentages(); + void updateAvailableLocales(); + void checkLocalesForMissingKeys(); + void updateLocalePercentages(); return defineConfig({ build: { emptyOutDir: false, @@ -184,7 +42,7 @@ export default function build() { "@/components": componentsDir, "@/hooks": hooksDir, "@/pages": pagesDir, - "@/src": root, + "@/src": srcDir, "@/utils": utilsDir } } From d04c58bd8b28e7be60fde10a46d7dea4822daf7a Mon Sep 17 00:00:00 2001 From: VampireChicken12 Date: Fri, 1 Dec 2023 13:14:07 -0500 Subject: [PATCH 2/4] refactor: rename variable --- src/utils/updateLocalePercentages.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/updateLocalePercentages.ts b/src/utils/updateLocalePercentages.ts index 0fca0668..1df32f3a 100644 --- a/src/utils/updateLocalePercentages.ts +++ b/src/utils/updateLocalePercentages.ts @@ -5,9 +5,9 @@ import { type LocaleFile, flattenLocaleValues, getLocaleFile, i18nDir } from "./ function calculateLocalePercentage(englishFile: LocaleFile, localeFile: LocaleFile): number { const { values: englishValues } = flattenLocaleValues(englishFile); const { values: localeValues } = flattenLocaleValues(localeFile); - const differingValues = englishValues.filter((value, index) => value !== localeValues[index]); + const translatedValues = englishValues.filter((value, index) => value !== localeValues[index]); - const localePercentage = (differingValues.length / englishValues.length) * 100; + const localePercentage = (translatedValues.length / englishValues.length) * 100; return Math.floor(localePercentage); } From 2a883524f485e7a788317d45e12dbf19b87569c7 Mon Sep 17 00:00:00 2001 From: VampireChicken12 Date: Fri, 1 Dec 2023 14:45:26 -0500 Subject: [PATCH 3/4] fix(popup): width was made too wide by long text --- src/pages/popup/index.css | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/popup/index.css b/src/pages/popup/index.css index f4db8295..3ad7d57b 100644 --- a/src/pages/popup/index.css +++ b/src/pages/popup/index.css @@ -1,11 +1,12 @@ body { - width: max-content; + width: fit-content; margin: 12px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - + min-width: 400px; + max-width: 640px; position: relative; overflow-y: scroll; } From 49542238798b53127649731f882e1df9cd05c2d5 Mon Sep 17 00:00:00 2001 From: VampireChicken12 Date: Fri, 1 Dec 2023 15:47:35 -0500 Subject: [PATCH 4/4] fix(notifications): used old language when switching language --- src/components/Settings/Settings.tsx | 15 ++-- .../components/SettingNotifications.tsx | 72 ++++++++++--------- src/hooks/useNotifications/context.ts | 8 ++- src/hooks/useNotifications/provider.tsx | 14 ++-- src/types/index.ts | 3 +- 5 files changed, 63 insertions(+), 49 deletions(-) diff --git a/src/components/Settings/Settings.tsx b/src/components/Settings/Settings.tsx index 3f4a2d6f..ab87d77b 100644 --- a/src/components/Settings/Settings.tsx +++ b/src/components/Settings/Settings.tsx @@ -165,11 +165,11 @@ export default function Settings() { } } - addNotification("success", t("pages.options.notifications.success.saved")); + addNotification("success", "pages.options.notifications.success.saved"); } } function resetOptions() { - addNotification("info", t("pages.options.notifications.info.reset"), "reset_settings"); + addNotification("info", "pages.options.notifications.info.reset", "reset_settings"); } function clearData() { const userHasConfirmed = window.confirm(t("settings.clearData.confirmAlert")); @@ -183,7 +183,7 @@ export default function Settings() { void chrome.storage.local.set({ [key]: defaultSettings[key] as string }); } } - addNotification("success", t("settings.clearData.allDataDeleted")); + addNotification("success", "settings.clearData.allDataDeleted"); } } const { @@ -359,7 +359,7 @@ export default function Settings() { } } // Show a success notification. - addNotification("success", t("settings.sections.importExportSettings.importButton.success")); + addNotification("success", "settings.sections.importExportSettings.importButton.success"); } } catch (error) { // Handle any import errors. @@ -406,12 +406,12 @@ export default function Settings() { a.click(); // Show a success notification. - addNotification("success", t("settings.sections.importExportSettings.exportButton.success")); + addNotification("success", "settings.sections.importExportSettings.exportButton.success"); } }; // TODO: add "default player mode" setting (theater, fullscreen, etc.) feature return ( - +

@@ -726,7 +726,7 @@ export default function Settings() { } } - addNotification("success", t("pages.options.notifications.success.saved")); + addNotification("success", "pages.options.notifications.success.saved"); }} title={t("settings.sections.bottomButtons.confirm.title")} type="button" @@ -751,6 +751,7 @@ export default function Settings() { } type SettingsContextProps = { direction: "ltr" | "rtl"; + i18nInstance: i18nInstanceType; settings: configuration; }; export const SettingsContext = createContext(undefined); diff --git a/src/components/Settings/components/SettingNotifications.tsx b/src/components/Settings/components/SettingNotifications.tsx index 77630413..064224b5 100644 --- a/src/components/Settings/components/SettingNotifications.tsx +++ b/src/components/Settings/components/SettingNotifications.tsx @@ -2,48 +2,56 @@ import { useNotifications } from "@/src/hooks"; import { cn } from "@/src/utils/utilities"; import { useAutoAnimate } from "@formkit/auto-animate/react"; +import { useSettings } from "../Settings"; + export default function SettingsNotifications() { const { notifications, removeNotification } = useNotifications(); const [parentRef] = useAutoAnimate({ duration: 300 }); + const { + i18nInstance: { t } + } = useSettings(); return (
- {notifications.map((notification, index) => ( -
- {notification.action ? ( - notification.action === "reset_settings" ? ( + {notifications.map((notification, index) => { + const message: string = t(notification.message); + return ( +
+ {notification.action ? ( + notification.action === "reset_settings" ? ( + <> + {message.split("\n").map((line, index) => ( +

{line}

+ ))} + + + ) : null + ) : ( <> - {notification.message.split("\n").map((line, index) => ( -

{line}

- ))} + {message} - ) : null - ) : ( - <> - {notification.message} - - - )} -
-
- ))} + )} +
+
+ ); + })}
); } diff --git a/src/hooks/useNotifications/context.ts b/src/hooks/useNotifications/context.ts index 5661122a..40d9c19b 100644 --- a/src/hooks/useNotifications/context.ts +++ b/src/hooks/useNotifications/context.ts @@ -2,9 +2,13 @@ import type { Notification, NotificationAction, NotificationType } from "@/src/t import { createContext } from "react"; +export type AddNotification = (type: NotificationType, message: Notification["message"], action?: NotificationAction) => void; + +export type RemoveNotification = (notification: Notification) => void; + export type NotificationsContextProps = { - addNotification: (type: NotificationType, message: string, action?: NotificationAction) => void; + addNotification: AddNotification; notifications: Notification[]; - removeNotification: (notification: Notification) => void; + removeNotification: RemoveNotification; }; export const NotificationsContext = createContext(undefined); diff --git a/src/hooks/useNotifications/provider.tsx b/src/hooks/useNotifications/provider.tsx index fa60ee95..8292a39b 100644 --- a/src/hooks/useNotifications/provider.tsx +++ b/src/hooks/useNotifications/provider.tsx @@ -1,14 +1,14 @@ -import type { Notification, NotificationAction, NotificationType } from "@/src/types"; +import type { Notification } from "@/src/types"; import { isNotStrictEqual } from "@/src/utils/utilities"; -import React, { type ReactElement, useEffect, useState } from "react"; +import { type ReactElement, useEffect, useState } from "react"; -import { NotificationsContext, type NotificationsContextProps } from "./context"; +import { type AddNotification, NotificationsContext, type NotificationsContextProps, type RemoveNotification } from "./context"; type NotificationProviderProps = { children: ReactElement | ReactElement[] }; export const NotificationsProvider = ({ children }: NotificationProviderProps) => { const [notifications, setNotifications] = useState([]); - function addNotification(type: NotificationType, message: string, action?: NotificationAction) { + const addNotification: AddNotification = (type, message, action) => { const existingNotification = notifications.find((n) => n.message === message && n.type === type); if (existingNotification) { return; @@ -24,10 +24,10 @@ export const NotificationsProvider = ({ children }: NotificationProviderProps) = removeNotification(notification); }, removeNotificationAfterMs); } - } - function removeNotification(notification: Notification) { + }; + const removeNotification: RemoveNotification = (notification) => { setNotifications((notifications) => notifications.filter(isNotStrictEqual(notification))); - } + }; useEffect(() => { const interval = setInterval(() => { setNotifications((notifications) => { diff --git a/src/types/index.ts b/src/types/index.ts index f5157609..eaae0c2b 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,3 +1,4 @@ +import type { ParseKeys, TOptions } from "i18next"; import type { YouTubePlayer } from "youtube-player/dist/types"; import z from "zod"; @@ -181,7 +182,7 @@ export type NotificationAction = "reset_settings" | undefined; export type Notification = { action: NotificationAction; - message: string; + message: ParseKeys<"en-US", TOptions, undefined>; progress?: number; removeAfterMs?: number; timestamp?: number;