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