From 985957aa31f5939d049da0ce29c317ba3d12a14d Mon Sep 17 00:00:00 2001 From: Sid Vishnoi <8426945+sidvishnoi@users.noreply.github.com> Date: Fri, 5 Jul 2024 17:02:37 +0530 Subject: [PATCH] refactor: add `t`, `useTranslation` helpers to use translations --- .eslintrc.json | 7 ++++++ src/background/services/tabEvents.ts | 8 ++++--- src/popup/Popup.tsx | 11 +++++---- src/popup/components/SiteNotMonetized.tsx | 7 +++--- src/popup/lib/context.tsx | 27 +++++++++++++++++++++-- src/popup/pages/MissingHostPermission.tsx | 4 +++- src/shared/helpers.ts | 15 +++++++++++++ src/shared/types.ts | 3 +++ 8 files changed, 68 insertions(+), 14 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index bc38f51d..4ded5dda 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -59,6 +59,13 @@ "react-hooks/exhaustive-deps": "error", "react/react-in-jsx-scope": "off", "react/jsx-uses-react": "off", + "@typescript-eslint/no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_" + } + ], "@typescript-eslint/no-explicit-any": "off" }, "overrides": [ diff --git a/src/background/services/tabEvents.ts b/src/background/services/tabEvents.ts index ddd4bb24..8da21bcd 100644 --- a/src/background/services/tabEvents.ts +++ b/src/background/services/tabEvents.ts @@ -4,6 +4,7 @@ import { MonetizationService } from './monetization' import { StorageService } from './storage' import { IsTabMonetizedPayload } from '@/shared/messages' import { getTabId } from '../utils' +import { tFactory } from '@/shared/helpers' const runtime = browser.runtime const ICONS = { @@ -62,15 +63,16 @@ export class TabEvents { sender?: Runtime.MessageSender ) => { const { enabled } = await this.storage.get(['enabled']) + const t = tFactory(this.browser) - let title = this.browser.i18n.getMessage('appName') + let title = t('appName') let iconData = enabled ? ICONS.default : ICONS.warning if (enabled && payload) { const { value: isTabMonetized } = payload iconData = isTabMonetized ? ICONS.active : ICONS.inactive const tabStateText = isTabMonetized - ? this.browser.i18n.getMessage('monetizationActiveShort') - : this.browser.i18n.getMessage('monetizationInactiveShort') + ? t('monetizationActiveShort') + : t('monetizationInactiveShort') title = `${title} - ${tabStateText}` } const tabId = sender && getTabId(sender) diff --git a/src/popup/Popup.tsx b/src/popup/Popup.tsx index ab7ca67c..6fd2b49f 100644 --- a/src/popup/Popup.tsx +++ b/src/popup/Popup.tsx @@ -1,7 +1,8 @@ import { MainLayout } from '@/popup/components/layout/MainLayout' -import { PopupContextProvider } from './lib/context' +import { PopupContextProvider, TranslationContextProvider } from './lib/context' import { LazyMotion, domAnimation } from 'framer-motion' import React from 'react' +import browser from 'webextension-polyfill' import { ProtectedRoute } from '@/popup/components/ProtectedRoute' import { RouteObject, @@ -49,9 +50,11 @@ const router = createMemoryRouter(routes) export const Popup = () => { return ( - - - + + + + + ) } diff --git a/src/popup/components/SiteNotMonetized.tsx b/src/popup/components/SiteNotMonetized.tsx index 813a3575..b5c7ba57 100644 --- a/src/popup/components/SiteNotMonetized.tsx +++ b/src/popup/components/SiteNotMonetized.tsx @@ -1,16 +1,15 @@ import React from 'react' -import browser from 'webextension-polyfill' import { WarningSign } from '@/popup/components/Icons' +import { useTranslation } from '@/popup/lib/context' export const SiteNotMonetized = () => { + const t = useTranslation() return (
-

- {browser.i18n.getMessage('siteNotMonetized')} -

+

{t('siteNotMonetized')}

) } diff --git a/src/popup/lib/context.tsx b/src/popup/lib/context.tsx index e656cad4..3bab2aae 100644 --- a/src/popup/lib/context.tsx +++ b/src/popup/lib/context.tsx @@ -1,7 +1,8 @@ import React from 'react' -import browser from 'webextension-polyfill' +import browser, { type Browser } from 'webextension-polyfill' import { getContextData } from '@/popup/lib/messages' -import { DeepNonNullable, PopupStore } from '@/shared/types' +import { tFactory } from '@/shared/helpers' +import type { DeepNonNullable, PopupStore } from '@/shared/types' import { ContentToBackgroundAction, type ContentToBackgroundMessage @@ -161,3 +162,25 @@ export function PopupContextProvider({ children }: PopupContextProviderProps) { ) } + +const TranslationContext = React.createContext>( + (v: string) => v +) + +export const TranslationContextProvider = ({ + browser, + children +}: { + browser: Browser + children: React.ReactNode +}) => { + const t = tFactory(browser) + + return ( + + {children} + + ) +} + +export const useTranslation = () => React.useContext(TranslationContext) diff --git a/src/popup/pages/MissingHostPermission.tsx b/src/popup/pages/MissingHostPermission.tsx index 954c37f7..c4a015eb 100644 --- a/src/popup/pages/MissingHostPermission.tsx +++ b/src/popup/pages/MissingHostPermission.tsx @@ -2,8 +2,10 @@ import React from 'react' import browser from 'webextension-polyfill' import { PERMISSION_HOSTS } from '@/shared/defines' import { WarningSign } from '@/popup/components/Icons' +import { useTranslation } from '@/popup/lib/context' export const Component = () => { + const t = useTranslation() return (
@@ -13,7 +15,7 @@ export const Component = () => {

Permission needed

-

{browser.i18n.getMessage('hostsPermissionsNeeded')}

+

{t('hostsPermissionsNeeded')}

diff --git a/src/shared/helpers.ts b/src/shared/helpers.ts index e3e27a72..79f36688 100644 --- a/src/shared/helpers.ts +++ b/src/shared/helpers.ts @@ -2,6 +2,8 @@ import { SuccessResponse } from '@/shared/messages' import { WalletAddress } from '@interledger/open-payments/dist/types' import { cx, CxOptions } from 'class-variance-authority' import { twMerge } from 'tailwind-merge' +import type { Browser } from 'webextension-polyfill' +import type { ExcludeFirst } from './types' export const cn = (...inputs: CxOptions) => { return twMerge(cx(inputs)) @@ -192,3 +194,16 @@ export function convert(value: bigint, source: number, target: number) { export function bigIntMax(a: string, b: string) { return BigInt(a) > BigInt(b) ? a : b } + +/** + * Helper over calling cumbersome `this.browser.i18n.getMessage(key)` with added + * benefit that it type-checks if key exists in message.json + */ +export function t< + T extends keyof typeof import('../_locales/en/messages.json') +>(browser: Pick, key: T, substitutions?: string | string[]) { + return browser.i18n.getMessage(key, substitutions) +} +export function tFactory(browser: Pick) { + return (...args: ExcludeFirst>) => t(browser, ...args) +} diff --git a/src/shared/types.ts b/src/shared/types.ts index 713c8046..c9c42fab 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -95,3 +95,6 @@ export type PopupStore = Omit< export type DeepNonNullable = { [P in keyof T]?: NonNullable } + +export type ExcludeFirst = + T extends [infer _F, ...infer Rest] ? Rest : never