From 36087a43d5bef9be1758a3074805aef3b731f214 Mon Sep 17 00:00:00 2001 From: Stanislav Lysak Date: Fri, 23 Aug 2024 13:25:35 +0300 Subject: [PATCH 01/15] fet-1603: Scan unused transaltion keys --- package.json | 1 + scripts/compare-locales.mjs | 94 ++++++--------- scripts/locale-utils.mjs | 69 +++++++++++ scripts/scan-locales.mjs | 108 ++++++++++++++++++ .../transaction/changePermissions.ts | 2 +- 5 files changed, 216 insertions(+), 58 deletions(-) create mode 100644 scripts/locale-utils.mjs create mode 100644 scripts/scan-locales.mjs diff --git a/package.json b/package.json index b2b8dac17..2ecb34065 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "lint:fix": "next lint --fix", "export": "next export", "compare-locales": "node ./scripts/compare-locales.mjs", + "scan-locales": "node ./scripts/scan-locales.mjs", "analyze": "ANALYZE=true pnpm build", "analyse": "pnpm analyze", "test": "vitest run", diff --git a/scripts/compare-locales.mjs b/scripts/compare-locales.mjs index 7d872ad4d..abae99655 100644 --- a/scripts/compare-locales.mjs +++ b/scripts/compare-locales.mjs @@ -1,71 +1,51 @@ import fs from 'fs' -import path from 'path' -const BASE_LOCALE = 'en' -const BASE_DIR = 'public/locales' - -function listLocales(dirPath, arrayOfFiles) { - arrayOfFiles = arrayOfFiles || [] - - const files = fs.readdirSync(dirPath) - - files.forEach(function (file) { - const fullPath = path.join(dirPath, file) - - if (fs.statSync(fullPath).isDirectory()) { - arrayOfFiles.push(fullPath) - listLocales(fullPath, arrayOfFiles) // Recursively list files in subdirectories - } else { - arrayOfFiles.push(fullPath) - } - }) - - return arrayOfFiles -} - -function detectMissingKeys(jsonObject, template) { - let missingKeys = [] - - for (const key in template) { - if (!jsonObject.hasOwnProperty(key)) { - missingKeys.push(key) - } else if (typeof template[key] === 'object' && !Array.isArray(template[key])) { - const nestedMissingKeys = detectMissingKeys(jsonObject[key], template[key]) - if (nestedMissingKeys.length > 0) { - missingKeys = [ - ...missingKeys, - ...nestedMissingKeys.map((nestedKey) => `${key}.${nestedKey}`), - ] +import { BASE_LOCALE, getLocalePaths, LOCALES_DIR } from './locale-utils.mjs' + +;(() => { + function detectMissingKeys(jsonObject, template) { + let missingKeys = [] + + for (const key in template) { + if (!jsonObject.hasOwnProperty(key)) { + missingKeys.push(key) + } else if (typeof template[key] === 'object' && !Array.isArray(template[key])) { + const nestedMissingKeys = detectMissingKeys(jsonObject[key], template[key]) + if (nestedMissingKeys.length > 0) { + missingKeys = [ + ...missingKeys, + ...nestedMissingKeys.map((nestedKey) => `${key}.${nestedKey}`), + ] + } } } + + return missingKeys } - return missingKeys -} + const locales = getLocalePaths() -const locales = fs.readdirSync(BASE_DIR).reduce((result, key) => { - result[key] = listLocales(`${BASE_DIR}/${key}`) - return result -}, {}) + console.log('locales', locales) -const baseLocale = locales[BASE_LOCALE] + const baseLocale = locales[BASE_LOCALE] -for (const key in locales) { - if (key !== BASE_LOCALE) { - for (const filePath of baseLocale) { - const diffPath = filePath.replace(`/${BASE_LOCALE}/`, `/${key}/`) - const source = JSON.parse(fs.readFileSync(filePath, 'utf-8')) - const template = JSON.parse( - fs.existsSync(diffPath) ? fs.readFileSync(diffPath, 'utf-8') : '{}', - ) + for (const key in locales) { + if (key !== BASE_LOCALE) { + for (const filePath of baseLocale) { + const diffPath = filePath.replace(`/${BASE_LOCALE}/`, `/${key}/`) + const source = JSON.parse(fs.readFileSync(filePath, 'utf-8')) + const template = JSON.parse( + fs.existsSync(diffPath) ? fs.readFileSync(diffPath, 'utf-8') : '{}', + ) - const keys = detectMissingKeys(template, source) + const keys = detectMissingKeys(template, source) - if (keys.length) { - console.log('\n') - console.log(key, diffPath.replace(BASE_DIR, '')) - console.log(keys.join('\n')) + if (keys.length) { + console.log('\n') + console.log(key, diffPath.replace(LOCALES_DIR, '')) + console.log(keys.join('\n')) + } } } } -} +})() diff --git a/scripts/locale-utils.mjs b/scripts/locale-utils.mjs new file mode 100644 index 000000000..6be04568c --- /dev/null +++ b/scripts/locale-utils.mjs @@ -0,0 +1,69 @@ +import fs from 'fs' +import path from 'path' + +export const BASE_LOCALE = 'en' +export const LOCALES_DIR = 'public/locales' + +function getDotNotationKeys(obj, parent = '') { + let keys = [] + + for (let key in obj) { + if (obj.hasOwnProperty(key)) { + const fullKey = parent ? `${parent}.${key}` : key + if (typeof obj[key] === 'object' && obj[key] !== null) { + keys = keys.concat(getDotNotationKeys(obj[key], fullKey)) + } else { + keys.push(fullKey) + } + } + } + + return keys +} + +function listLocales(dirPath, arrayOfFiles) { + arrayOfFiles = arrayOfFiles || [] + + const files = fs.readdirSync(dirPath) + + files.forEach(function (file) { + const fullPath = path.join(dirPath, file) + + if (fs.statSync(fullPath).isDirectory()) { + arrayOfFiles.push(fullPath) + listLocales(fullPath, arrayOfFiles) // Recursively list files in subdirectories + } else { + arrayOfFiles.push(fullPath) + } + }) + + return arrayOfFiles +} + +export function getLocalePaths(locale) { + const locales = fs.readdirSync(LOCALES_DIR).reduce((result, key) => { + result[key] = listLocales(`${LOCALES_DIR}/${key}`) + return result + }, {}) + + if (locale) return locales[locale] + + return locales +} + +export function getLocaleData(filePaths) { + let keys = [] + const namespaces = [] + + for (const filePath of filePaths) { + const content = fs.readFileSync(filePath, 'utf-8') + const json = JSON.parse(content) + + const ns = filePath.split('/').at(-1).replace('.json', '') + + namespaces.push(ns) + keys = [...keys, ...getDotNotationKeys({ [ns]: json })] + } + + return { keys, namespaces } +} diff --git a/scripts/scan-locales.mjs b/scripts/scan-locales.mjs new file mode 100644 index 000000000..6c1df9f9d --- /dev/null +++ b/scripts/scan-locales.mjs @@ -0,0 +1,108 @@ +import fs from 'fs' +import path from 'path' + +import { BASE_LOCALE, getLocaleData, getLocalePaths } from './locale-utils.mjs' + +const baseLocale = getLocalePaths(BASE_LOCALE) + +;(() => { + const { keys, namespaces } = getLocaleData(baseLocale) + + function createRegex(text, { caseInsensitive = true } = {}) { + const escapedPattern = text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + const flags = caseInsensitive ? 'gi' : 'g' + return new RegExp(`\\b${escapedPattern}`, flags) + } + + function filterByExt(text, exts = []) { + const regex = new RegExp(`(${exts.join('|')})$`) + return regex.test(text) + } + + const dir = './src' + const search = `t('` + const regex = createRegex(search) + + const results = { + results: [], + files: [], + } + + function extractKey(str) { + const keyRegex = /t\(\s*['"](.+?)['"]\s*,?/ + const keyMatch = str.match(keyRegex) + return keyMatch ? keyMatch[1] : null + } + + function extractMatch(filePath) { + let match = true + const matches = [] + let content = fs.readFileSync(filePath, 'utf-8') + + while ((match = regex.exec(content))) { + // /\b(?:t)\s*\(\s*(['\s\S']*?)\s*\)/g + const line = /\b(?:t)\s*\(['"][^'"]+['"][^)]*\)/g.exec(content)?.at(0) + content = content.replace(match?.[0], '').replace(line, '') + matches.push(extractKey(line)) + } + + return matches + } + + function handleResults(filePath) { + const matches = extractMatch(filePath) + + if (!matches.length) return + + // console.log(`Found ${matches.length} ${search} in ${filePath}:`) + matches.forEach((m) => console.log(m)) + // console.log('\n') + results.results = [...results.results, ...matches] + results.files = [...results.files, filePath] + } + + // Function to recursively scan files in a directory + function scanFiles({ dir, fn, ext = [] }) { + const files = fs.readdirSync(dir) + + files.forEach((file) => { + const filePath = path.join(dir, file) + const stat = fs.statSync(filePath) + + if (stat.isDirectory()) { + scanFiles({ dir: filePath, fn, ext }) // Recursively scan subdirectories + } else if (stat.isFile() && filterByExt(file, ext)) { + fn(filePath) + } + }) + } + + scanFiles({ + dir, + fn: handleResults, + ext: ['.ts', '.tsx'], + }) + + const unusedKeys = [] + const foundKeys = [ + ...new Set( + results.results.map((key) => key.replace(new RegExp(`^(${namespaces.join('|')}).`), '')), + ), + ] + const modifiedKeys = [ + ...new Set(keys.map((key) => key.replace(new RegExp(`^(${namespaces.join('|')}).`), ''))), + ] + + for (const key of modifiedKeys) { + const foundKey = foundKeys.find((k) => key === k) + + if (!foundKey) { + unusedKeys.push(key) + } + } + + console.log('PROBABLY UNSED KEYS\n') + for (const key of unusedKeys) { + console.log(key) + } +})() diff --git a/src/transaction-flow/transaction/changePermissions.ts b/src/transaction-flow/transaction/changePermissions.ts index 7f4f05252..22264fa1f 100644 --- a/src/transaction-flow/transaction/changePermissions.ts +++ b/src/transaction-flow/transaction/changePermissions.ts @@ -73,7 +73,7 @@ const displayItems = ( }, { label: 'action', - value: t('transaction.description.changePermissions') as string, + value: t('transaction.description.changePermissions'), }, { label: 'info', From daebaa5ee98bcea6275783f24e9a12fd27ecb2ec Mon Sep 17 00:00:00 2001 From: Stanislav Lysak Date: Tue, 10 Sep 2024 13:16:54 +0300 Subject: [PATCH 02/15] update scan-locales --- scripts/scan-locales.mjs | 120 +++++++++++---------------------------- 1 file changed, 32 insertions(+), 88 deletions(-) diff --git a/scripts/scan-locales.mjs b/scripts/scan-locales.mjs index 6c1df9f9d..ad05da669 100644 --- a/scripts/scan-locales.mjs +++ b/scripts/scan-locales.mjs @@ -1,108 +1,52 @@ import fs from 'fs' -import path from 'path' + +import glob from 'glob' import { BASE_LOCALE, getLocaleData, getLocalePaths } from './locale-utils.mjs' const baseLocale = getLocalePaths(BASE_LOCALE) -;(() => { - const { keys, namespaces } = getLocaleData(baseLocale) - - function createRegex(text, { caseInsensitive = true } = {}) { - const escapedPattern = text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') - const flags = caseInsensitive ? 'gi' : 'g' - return new RegExp(`\\b${escapedPattern}`, flags) - } - - function filterByExt(text, exts = []) { - const regex = new RegExp(`(${exts.join('|')})$`) - return regex.test(text) - } - - const dir = './src' - const search = `t('` - const regex = createRegex(search) +const { keys: translationKeys, namespaces } = getLocaleData(baseLocale) - const results = { - results: [], - files: [], - } - - function extractKey(str) { - const keyRegex = /t\(\s*['"](.+?)['"]\s*,?/ - const keyMatch = str.match(keyRegex) - return keyMatch ? keyMatch[1] : null - } +// Path to your source code +const sourceCodePath = './src/**/*.{js,jsx,ts,tsx}' // adjust for relevant file types - function extractMatch(filePath) { - let match = true - const matches = [] - let content = fs.readFileSync(filePath, 'utf-8') +// Search for translation keys in the source code +function searchForTranslationKeysInCode(pattern) { + const regex = /t[cs]?\(['"`]([a-zA-Z0-9_.]+)['"`]\s*,?\s*{?/g + // = /t\(['"`]([a-zA-Z0-9_.]+)['"`]\s*,?\s*{?/g + // /t\(['"`]([a-zA-Z0-9_.]+)['"`]\)/g // regex to match t('key') + const files = glob.sync(pattern) + const foundKeys = new Set() - while ((match = regex.exec(content))) { - // /\b(?:t)\s*\(\s*(['\s\S']*?)\s*\)/g - const line = /\b(?:t)\s*\(['"][^'"]+['"][^)]*\)/g.exec(content)?.at(0) - content = content.replace(match?.[0], '').replace(line, '') - matches.push(extractKey(line)) + files.forEach((file) => { + const content = fs.readFileSync(file, 'utf-8') + let match + while ((match = regex.exec(content)) !== null) { + foundKeys.add(match[1]) // Add the matched key } + }) - return matches - } - - function handleResults(filePath) { - const matches = extractMatch(filePath) - - if (!matches.length) return - - // console.log(`Found ${matches.length} ${search} in ${filePath}:`) - matches.forEach((m) => console.log(m)) - // console.log('\n') - results.results = [...results.results, ...matches] - results.files = [...results.files, filePath] - } - - // Function to recursively scan files in a directory - function scanFiles({ dir, fn, ext = [] }) { - const files = fs.readdirSync(dir) - - files.forEach((file) => { - const filePath = path.join(dir, file) - const stat = fs.statSync(filePath) - - if (stat.isDirectory()) { - scanFiles({ dir: filePath, fn, ext }) // Recursively scan subdirectories - } else if (stat.isFile() && filterByExt(file, ext)) { - fn(filePath) - } - }) - } + return foundKeys +} - scanFiles({ - dir, - fn: handleResults, - ext: ['.ts', '.tsx'], - }) +// Find unused translation keys +function findUnusedKeys() { + const usedKeys = searchForTranslationKeysInCode(sourceCodePath) const unusedKeys = [] - const foundKeys = [ - ...new Set( - results.results.map((key) => key.replace(new RegExp(`^(${namespaces.join('|')}).`), '')), - ), - ] - const modifiedKeys = [ - ...new Set(keys.map((key) => key.replace(new RegExp(`^(${namespaces.join('|')}).`), ''))), - ] - for (const key of modifiedKeys) { - const foundKey = foundKeys.find((k) => key === k) + const regex = new RegExp(`^(${namespaces.join('|')}).`) - if (!foundKey) { + for (const key of translationKeys.map((key) => key.replace(regex, ''))) { + if (!usedKeys.has(key)) { unusedKeys.push(key) } } - - console.log('PROBABLY UNSED KEYS\n') - for (const key of unusedKeys) { + console.log(`PROBABLY ${unusedKeys.length} UNSED KEYS:`) + unusedKeys.forEach((key) => { console.log(key) - } -})() + }) +} + +findUnusedKeys() From f6bb039adbaedd782573bdeef835c58532a9d9e9 Mon Sep 17 00:00:00 2001 From: Stanislav Lysak Date: Tue, 10 Sep 2024 15:56:32 +0300 Subject: [PATCH 03/15] fix test --- .../@molecules/ProfileEditor/Avatar/AvatarNFT.test.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/@molecules/ProfileEditor/Avatar/AvatarNFT.test.tsx b/src/components/@molecules/ProfileEditor/Avatar/AvatarNFT.test.tsx index 1c1af4f03..669ee464a 100644 --- a/src/components/@molecules/ProfileEditor/Avatar/AvatarNFT.test.tsx +++ b/src/components/@molecules/ProfileEditor/Avatar/AvatarNFT.test.tsx @@ -6,11 +6,13 @@ import { useAccount, useClient } from 'wagmi' import * as ThorinComponents from '@ensdomains/thorin' -import { AvatarNFT } from './AvatarNFT' import { makeMockIntersectionObserver } from '../../../../../test/mock/makeMockIntersectionObserver' +import { AvatarNFT } from './AvatarNFT' vi.mock('wagmi') - +vi.mock('@app/hooks/chain/useBlockTimestamp', () => ({ + useBlockTimestamp: () => ({ data: new Date().getTime() }), +})) vi.mock('@app/hooks/chain/useChainName', () => ({ useChainName: () => 'mainnet', })) From 2a14c1d3173a5dff8b73d2fed569b85007cc2a56 Mon Sep 17 00:00:00 2001 From: Stanislav Lysak Date: Wed, 11 Sep 2024 11:26:47 +0300 Subject: [PATCH 04/15] script enhancements --- scripts/scan-locales.mjs | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/scripts/scan-locales.mjs b/scripts/scan-locales.mjs index ad05da669..5089c79ee 100644 --- a/scripts/scan-locales.mjs +++ b/scripts/scan-locales.mjs @@ -8,23 +8,35 @@ const baseLocale = getLocalePaths(BASE_LOCALE) const { keys: translationKeys, namespaces } = getLocaleData(baseLocale) -// Path to your source code -const sourceCodePath = './src/**/*.{js,jsx,ts,tsx}' // adjust for relevant file types +const translationKeysWoNs = translationKeys.map((key) => + key.replace(new RegExp(`^(${namespaces.join('|')}).`), ''), +) // Search for translation keys in the source code -function searchForTranslationKeysInCode(pattern) { +function searchForTranslationKeysInCode() { const regex = /t[cs]?\(['"`]([a-zA-Z0-9_.]+)['"`]\s*,?\s*{?/g // = /t\(['"`]([a-zA-Z0-9_.]+)['"`]\s*,?\s*{?/g // /t\(['"`]([a-zA-Z0-9_.]+)['"`]\)/g // regex to match t('key') - const files = glob.sync(pattern) + const files = [ + ...glob.sync(`./src/**/*.{js,jsx,ts,tsx}`), + ...glob.sync(`./src/*.{js,jsx,ts,tsx}`), + ] + const foundKeys = new Set() files.forEach((file) => { const content = fs.readFileSync(file, 'utf-8') let match + while ((match = regex.exec(content)) !== null) { foundKeys.add(match[1]) // Add the matched key } + + const keys = translationKeysWoNs.filter((key) => new RegExp(`['"\`]${key}['"\`]`).test(content)) + + for (const key of keys) { + foundKeys.add(key) + } }) return foundKeys @@ -32,13 +44,11 @@ function searchForTranslationKeysInCode(pattern) { // Find unused translation keys function findUnusedKeys() { - const usedKeys = searchForTranslationKeysInCode(sourceCodePath) + const usedKeys = searchForTranslationKeysInCode() const unusedKeys = [] - const regex = new RegExp(`^(${namespaces.join('|')}).`) - - for (const key of translationKeys.map((key) => key.replace(regex, ''))) { + for (const key of translationKeysWoNs) { if (!usedKeys.has(key)) { unusedKeys.push(key) } From 93c53204975b3d6276538254d9ebb562a0bb8e86 Mon Sep 17 00:00:00 2001 From: Stanislav Lysak Date: Thu, 12 Sep 2024 23:44:35 +0300 Subject: [PATCH 05/15] script enhancements --- scripts/scan-locales.mjs | 124 ++++++++++++--------------------------- 1 file changed, 39 insertions(+), 85 deletions(-) diff --git a/scripts/scan-locales.mjs b/scripts/scan-locales.mjs index 6c1df9f9d..5089c79ee 100644 --- a/scripts/scan-locales.mjs +++ b/scripts/scan-locales.mjs @@ -1,108 +1,62 @@ import fs from 'fs' -import path from 'path' + +import glob from 'glob' import { BASE_LOCALE, getLocaleData, getLocalePaths } from './locale-utils.mjs' const baseLocale = getLocalePaths(BASE_LOCALE) -;(() => { - const { keys, namespaces } = getLocaleData(baseLocale) - - function createRegex(text, { caseInsensitive = true } = {}) { - const escapedPattern = text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') - const flags = caseInsensitive ? 'gi' : 'g' - return new RegExp(`\\b${escapedPattern}`, flags) - } +const { keys: translationKeys, namespaces } = getLocaleData(baseLocale) - function filterByExt(text, exts = []) { - const regex = new RegExp(`(${exts.join('|')})$`) - return regex.test(text) - } +const translationKeysWoNs = translationKeys.map((key) => + key.replace(new RegExp(`^(${namespaces.join('|')}).`), ''), +) - const dir = './src' - const search = `t('` - const regex = createRegex(search) +// Search for translation keys in the source code +function searchForTranslationKeysInCode() { + const regex = /t[cs]?\(['"`]([a-zA-Z0-9_.]+)['"`]\s*,?\s*{?/g + // = /t\(['"`]([a-zA-Z0-9_.]+)['"`]\s*,?\s*{?/g + // /t\(['"`]([a-zA-Z0-9_.]+)['"`]\)/g // regex to match t('key') + const files = [ + ...glob.sync(`./src/**/*.{js,jsx,ts,tsx}`), + ...glob.sync(`./src/*.{js,jsx,ts,tsx}`), + ] - const results = { - results: [], - files: [], - } + const foundKeys = new Set() - function extractKey(str) { - const keyRegex = /t\(\s*['"](.+?)['"]\s*,?/ - const keyMatch = str.match(keyRegex) - return keyMatch ? keyMatch[1] : null - } + files.forEach((file) => { + const content = fs.readFileSync(file, 'utf-8') + let match - function extractMatch(filePath) { - let match = true - const matches = [] - let content = fs.readFileSync(filePath, 'utf-8') - - while ((match = regex.exec(content))) { - // /\b(?:t)\s*\(\s*(['\s\S']*?)\s*\)/g - const line = /\b(?:t)\s*\(['"][^'"]+['"][^)]*\)/g.exec(content)?.at(0) - content = content.replace(match?.[0], '').replace(line, '') - matches.push(extractKey(line)) + while ((match = regex.exec(content)) !== null) { + foundKeys.add(match[1]) // Add the matched key } - return matches - } - - function handleResults(filePath) { - const matches = extractMatch(filePath) - - if (!matches.length) return - - // console.log(`Found ${matches.length} ${search} in ${filePath}:`) - matches.forEach((m) => console.log(m)) - // console.log('\n') - results.results = [...results.results, ...matches] - results.files = [...results.files, filePath] - } + const keys = translationKeysWoNs.filter((key) => new RegExp(`['"\`]${key}['"\`]`).test(content)) - // Function to recursively scan files in a directory - function scanFiles({ dir, fn, ext = [] }) { - const files = fs.readdirSync(dir) - - files.forEach((file) => { - const filePath = path.join(dir, file) - const stat = fs.statSync(filePath) + for (const key of keys) { + foundKeys.add(key) + } + }) - if (stat.isDirectory()) { - scanFiles({ dir: filePath, fn, ext }) // Recursively scan subdirectories - } else if (stat.isFile() && filterByExt(file, ext)) { - fn(filePath) - } - }) - } + return foundKeys +} - scanFiles({ - dir, - fn: handleResults, - ext: ['.ts', '.tsx'], - }) +// Find unused translation keys +function findUnusedKeys() { + const usedKeys = searchForTranslationKeysInCode() const unusedKeys = [] - const foundKeys = [ - ...new Set( - results.results.map((key) => key.replace(new RegExp(`^(${namespaces.join('|')}).`), '')), - ), - ] - const modifiedKeys = [ - ...new Set(keys.map((key) => key.replace(new RegExp(`^(${namespaces.join('|')}).`), ''))), - ] - - for (const key of modifiedKeys) { - const foundKey = foundKeys.find((k) => key === k) - if (!foundKey) { + for (const key of translationKeysWoNs) { + if (!usedKeys.has(key)) { unusedKeys.push(key) } } - - console.log('PROBABLY UNSED KEYS\n') - for (const key of unusedKeys) { + console.log(`PROBABLY ${unusedKeys.length} UNSED KEYS:`) + unusedKeys.forEach((key) => { console.log(key) - } -})() + }) +} + +findUnusedKeys() From d2bf61eb7c0a3dd97a7566b4c91799fb9edc8c9c Mon Sep 17 00:00:00 2001 From: Stanislav Lysak Date: Tue, 24 Sep 2024 12:22:24 +0300 Subject: [PATCH 06/15] remove keyPrefix option --- .../@molecules/BurnFuses/BurnFusesContent.tsx | 16 +++++----- .../import/[name]/steps/CompleteImport.tsx | 17 ++++++----- .../import/[name]/steps/EnableDnssec.tsx | 14 ++++----- .../import/[name]/steps/SelectImportType.tsx | 30 ++++++++++++------- .../[name]/steps/VerifyOffchainOwnership.tsx | 17 ++++++----- .../steps/onchain/ImportTransaction.tsx | 16 ++++++---- .../steps/onchain/VerifyOnchainOwnership.tsx | 23 ++++++++------ .../TransactionSection/TransactionSection.tsx | 2 +- 8 files changed, 79 insertions(+), 56 deletions(-) diff --git a/src/components/@molecules/BurnFuses/BurnFusesContent.tsx b/src/components/@molecules/BurnFuses/BurnFusesContent.tsx index 895934566..3e5ecca78 100644 --- a/src/components/@molecules/BurnFuses/BurnFusesContent.tsx +++ b/src/components/@molecules/BurnFuses/BurnFusesContent.tsx @@ -92,7 +92,7 @@ const BurnButton = ({ handleBurnClick: (permission: ChildFuseReferenceType['Key']) => void isSelected: boolean }) => { - const { t } = useTranslation('profile', { keyPrefix: 'tabs.more.fuses' }) + const { t } = useTranslation('profile') return ( - {t(`permissions.${permission}`)} + {t(`tabs.more.fuses.permissions.${permission}`)} {isBurned && ( - {t('burned')} + {t('tabs.more.fuses.burned')} )} @@ -171,8 +171,8 @@ const BurnFusesContent = ({ canUnsetFuse = false, returnObject, }: PropsWithReturnArray | PropsWithReturnObject) => { - const { t } = useTranslation('profile', { keyPrefix: 'tabs.more' }) - const { t: tc } = useTranslation() + const { t } = useTranslation('profile') + const { t: tc } = useTranslation('common') const [_fuseData, setFuseData] = useState(childFuseObj) const [fuseSelected, setFuseSelected] = useState(childFuseObj) @@ -191,7 +191,7 @@ const BurnFusesContent = ({ (key) => fuseSelected[key as ChildFuseReferenceType['Key']], ) as ChildFuseReferenceType['Key'][] - const permissions = selectedFuses.map((key) => t(`fuses.permissions.${key}`)) + const permissions = selectedFuses.map((key) => t(`tabs.more.fuses.permissions.${key}`)) onSubmit(selectedFuses, permissions) } @@ -214,12 +214,12 @@ const BurnFusesContent = ({ return ( - {t('fuses.burnFormTitle')} + {t('tabs.more.fuses.burnFormTitle')} {!_fuseData.CANNOT_UNWRAP && !fuseSelected.CANNOT_UNWRAP ? ( <> - {t('fuses.info')} + {t('tabs.more.fuses.info')} ) : ( diff --git a/src/components/pages/import/[name]/steps/CompleteImport.tsx b/src/components/pages/import/[name]/steps/CompleteImport.tsx index d0df23e01..89e917271 100644 --- a/src/components/pages/import/[name]/steps/CompleteImport.tsx +++ b/src/components/pages/import/[name]/steps/CompleteImport.tsx @@ -96,7 +96,7 @@ export const CompleteImport = ({ selected: SelectedItemProperties item: DnsImportReducerDataItem }) => { - const { t } = useTranslation('dnssec', { keyPrefix: 'steps.complete' }) + const { t } = useTranslation('dnssec') const router = useRouterWithHistory() const { width, height } = useWindowSize() @@ -107,7 +107,6 @@ export const CompleteImport = ({ }) const isImport = item.type === 'offchain' || addressRecord?.value !== selected.address - const addKeyPrefix = (key: string) => (isImport ? `import.${key}` : `claim.${key}`) const goHome = () => router.push('/') @@ -137,11 +136,11 @@ export const CompleteImport = ({ initialVelocityY={20} /> - {t('title')} + {t('steps.complete.title')} , }} @@ -151,17 +150,19 @@ export const CompleteImport = ({ /> - {t(addKeyPrefix('description'))} - {item.type === 'offchain' && {t('import.warning')}} + + {t(isImport ? 'steps.complete.import.description' : 'steps.complete.claim.description')} + + {item.type === 'offchain' && {t('steps.complete.import.warning')}} diff --git a/src/components/pages/import/[name]/steps/EnableDnssec.tsx b/src/components/pages/import/[name]/steps/EnableDnssec.tsx index cd452c39f..c89da72fe 100644 --- a/src/components/pages/import/[name]/steps/EnableDnssec.tsx +++ b/src/components/pages/import/[name]/steps/EnableDnssec.tsx @@ -24,7 +24,7 @@ export const EnableDnssec = ({ dispatch: Dispatch selected: SelectedItemProperties }) => { - const { t } = useTranslation('dnssec', { keyPrefix: 'steps.enableDnssec' }) + const { t } = useTranslation('dnssec') const { t: tc } = useTranslation('common') const { @@ -40,20 +40,20 @@ export const EnableDnssec = ({ return ( - {t('title')} + {t('steps.enableDnssec.title')} {isDnsSecEnabled ? ( - {t('status.enabled')} + {t('steps.enableDnssec.status.enabled')} ) : ( <> - {t('status.disabled.heading')} + {t('steps.enableDnssec.status.disabled.heading')} diff --git a/src/components/pages/import/[name]/steps/SelectImportType.tsx b/src/components/pages/import/[name]/steps/SelectImportType.tsx index 97ddbee25..208947f22 100644 --- a/src/components/pages/import/[name]/steps/SelectImportType.tsx +++ b/src/components/pages/import/[name]/steps/SelectImportType.tsx @@ -153,7 +153,7 @@ export const SelectImportType = ({ item: DnsImportReducerDataItem selected: SelectedItemProperties }) => { - const { t } = useTranslation('dnssec', { keyPrefix: 'steps.selectType' }) + const { t } = useTranslation('dnssec') const { t: tc } = useTranslation('common') const { address } = useAccount() @@ -195,11 +195,13 @@ export const SelectImportType = ({ return ( - {t('title', { name: selected.name })} - {t('subtitle')} - {t('learnMore')} + {t('steps.selectType.title', { name: selected.name })} + {t('steps.selectType.subtitle')} + + {t('steps.selectType.learnMore')} + - {t('select.heading')} + {t('steps.selectType.select.heading')} { @@ -214,13 +216,17 @@ export const SelectImportType = ({ label={ - {t('select.offchain.name')} - {t('select.offchain.tag')} + + {t('steps.selectType.select.offchain.name')} + + + {t('steps.selectType.select.offchain.tag')} + , b: , @@ -239,9 +245,13 @@ export const SelectImportType = ({ label={ - {t('select.onchain.name')} + + {t('steps.selectType.select.onchain.name')} + - {t('select.onchain.description')} + + {t('steps.selectType.select.onchain.description')} + } data-testid="onchain-radio" diff --git a/src/components/pages/import/[name]/steps/VerifyOffchainOwnership.tsx b/src/components/pages/import/[name]/steps/VerifyOffchainOwnership.tsx index e72dbc4ad..6aaed2e82 100644 --- a/src/components/pages/import/[name]/steps/VerifyOffchainOwnership.tsx +++ b/src/components/pages/import/[name]/steps/VerifyOffchainOwnership.tsx @@ -71,7 +71,7 @@ export const VerifyOffchainOwnership = ({ dispatch: Dispatch selected: SelectedItemProperties }) => { - const { t } = useTranslation('dnssec', { keyPrefix: 'steps.verifyOwnership' }) + const { t } = useTranslation('dnssec') const { t: tc } = useTranslation('common') const { address, chainId } = selected @@ -99,14 +99,15 @@ export const VerifyOffchainOwnership = ({ return ( - {t('title')} + {t('steps.verifyOwnership.title')} {(() => { - if (!isConnected) return {t('status.disconnected')} + if (!isConnected) + return {t('steps.verifyOwnership.status.disconnected')} if (dnsOffchainStatus?.address?.status === 'matching') return ( - {t('status.matching')} + {t('steps.verifyOwnership.status.matching')} ) return ( @@ -123,7 +124,7 @@ export const VerifyOffchainOwnership = ({ /> {t('status.mismatching.error.offchain')} + + {t('steps.verifyOwnership.status.mismatching.error.offchain')} + ) } /> diff --git a/src/components/pages/import/[name]/steps/onchain/ImportTransaction.tsx b/src/components/pages/import/[name]/steps/onchain/ImportTransaction.tsx index d27f5715a..01c0cd2bd 100644 --- a/src/components/pages/import/[name]/steps/onchain/ImportTransaction.tsx +++ b/src/components/pages/import/[name]/steps/onchain/ImportTransaction.tsx @@ -102,7 +102,7 @@ export const ImportTransaction = ({ item: DnsImportReducerDataItem selected: SelectedItemProperties }) => { - const { t } = useTranslation('dnssec', { keyPrefix: 'steps.transaction' }) + const { t } = useTranslation('dnssec') const { t: tc } = useTranslation('common') const { data: gasPrice } = useGasPrice() @@ -206,15 +206,19 @@ export const ImportTransaction = ({ {dnsOwnerStatus === 'mismatching' ? ( <> - {t('mismatching.title')} + {t('steps.transaction.mismatching.title')} - }} /> + }} + /> ) : ( <> - {t('matching.title')} - {t('matching.subtitle')} + {t('steps.transaction.matching.title')} + {t('steps.transaction.matching.subtitle')} )} @@ -227,7 +231,7 @@ export const ImportTransaction = ({ /> - {t('estimatedNetworkCost')} + {t('steps.transaction.estimatedNetworkCost')} diff --git a/src/components/pages/import/[name]/steps/onchain/VerifyOnchainOwnership.tsx b/src/components/pages/import/[name]/steps/onchain/VerifyOnchainOwnership.tsx index 17bfba790..e92d7f837 100644 --- a/src/components/pages/import/[name]/steps/onchain/VerifyOnchainOwnership.tsx +++ b/src/components/pages/import/[name]/steps/onchain/VerifyOnchainOwnership.tsx @@ -66,7 +66,7 @@ export const VerifyOnchainOwnership = ({ dispatch: Dispatch selected: SelectedItemProperties }) => { - const { t } = useTranslation('dnssec', { keyPrefix: 'steps.verifyOwnership' }) + const { t } = useTranslation('dnssec') const { t: tc } = useTranslation('common') const { @@ -98,15 +98,18 @@ export const VerifyOnchainOwnership = ({ return ( - {t('title')} - {dnsOwnerStatus !== 'matching' && {t('status.mismatching.heading')}} + {t('steps.verifyOwnership.title')} + {dnsOwnerStatus !== 'matching' && ( + {t('steps.verifyOwnership.status.mismatching.heading')} + )} {(() => { - if (!isConnected) return {t('status.disconnected')} + if (!isConnected) + return {t('steps.verifyOwnership.status.disconnected')} if (dnsOwnerStatus === 'matching') return ( - {t('status.matching')} + {t('steps.verifyOwnership.status.matching')} ) return ( @@ -119,7 +122,7 @@ export const VerifyOnchainOwnership = ({ @@ -143,7 +146,9 @@ export const VerifyOnchainOwnership = ({ } statusHelperElement={ dnsOwnerStatus === 'mismatching' && ( - {t('status.mismatching.error.onchain')} + + {t('steps.verifyOwnership.status.mismatching.error.onchain')} + ) } /> @@ -170,7 +175,7 @@ export const VerifyOnchainOwnership = ({ : {})} > {dnsOwnerStatus === 'mismatching' - ? t('action.importWithoutOwnership') + ? t('steps.verifyOwnership.action.importWithoutOwnership') : tc('action.next')} ) : ( diff --git a/src/components/pages/profile/settings/TransactionSection/TransactionSection.tsx b/src/components/pages/profile/settings/TransactionSection/TransactionSection.tsx index 013aa29cf..94ec02377 100644 --- a/src/components/pages/profile/settings/TransactionSection/TransactionSection.tsx +++ b/src/components/pages/profile/settings/TransactionSection/TransactionSection.tsx @@ -137,8 +137,8 @@ const getTransactionExtraInfo = (action: string, key?: string) => { } export const TransactionSection = () => { - const { t: tc } = useTranslation() const { t } = useTranslation('settings') + const { t: tc } = useTranslation('common') const chainName = useChainName() const transactions = useRecentTransactions() From 31a2cb417fc317fd4a5d1521b2ec732b351c94c5 Mon Sep 17 00:00:00 2001 From: Leon Talbert Date: Tue, 15 Oct 2024 16:47:14 +0800 Subject: [PATCH 07/15] remove tempalte string example --- .../@molecules/SearchInput/SearchResult.tsx | 67 +++++++++++++------ 1 file changed, 48 insertions(+), 19 deletions(-) diff --git a/src/components/@molecules/SearchInput/SearchResult.tsx b/src/components/@molecules/SearchInput/SearchResult.tsx index f80186183..269e3ba5f 100644 --- a/src/components/@molecules/SearchInput/SearchResult.tsx +++ b/src/components/@molecules/SearchInput/SearchResult.tsx @@ -191,27 +191,56 @@ const PremiumTag = styled(StyledTag)( `, ) +// const StatusTag = ({ status }: { status: RegistrationStatus }) => { +// const { t } = useTranslation('common') +// switch (status) { +// case 'owned': +// case 'imported': +// case 'registered': +// return {t(`search.status.${status}`)} +// case 'gracePeriod': +// return {t(`search.status.${status}`)} +// case 'premium': +// return {t(`search.status.${status}`)} +// case 'available': +// return {t(`search.status.${status}`)} +// case 'notOwned': +// case 'offChain': +// case 'notImported': +// return {t(`search.status.${status}`)} +// case 'short': +// default: +// return {t(`search.status.${status}`)} +// } +// } + +const getStatusTranslationKey = (status: RegistrationStatus): string => + match(status) + .with('owned', () => 'search.status.owned') + .with('imported', () => 'search.status.imported') + .with('registered', () => 'search.status.registered') + .with('gracePeriod', () => 'search.status.gracePeriod') + .with('premium', () => 'search.status.premium') + .with('available', () => 'search.status.available') + .with('notOwned', () => 'search.status.notOwned') + .with('offChain', () => 'search.status.offChain') + .with('notImported', () => 'search.status.notImported') + .with('short', () => 'search.status.short') + .otherwise(() => 'search.status.short') + const StatusTag = ({ status }: { status: RegistrationStatus }) => { const { t } = useTranslation('common') - switch (status) { - case 'owned': - case 'imported': - case 'registered': - return {t(`search.status.${status}`)} - case 'gracePeriod': - return {t(`search.status.${status}`)} - case 'premium': - return {t(`search.status.${status}`)} - case 'available': - return {t(`search.status.${status}`)} - case 'notOwned': - case 'offChain': - case 'notImported': - return {t(`search.status.${status}`)} - case 'short': - default: - return {t(`search.status.${status}`)} - } + const translationKey = getStatusTranslationKey(status) + + return match(status) + .with('owned', 'imported', 'registered', () => {t(translationKey)}) + .with('gracePeriod', () => {t(translationKey)}) + .with('premium', () => {t(translationKey)}) + .with('available', () => {t(translationKey)}) + .with('notOwned', 'offChain', 'notImported', () => ( + {t(translationKey)} + )) + .otherwise(() => {t(translationKey)}) } const TextWrapper = styled.div( From 4cb8c94e7938a6c30a128fa3172eb85b8029a751 Mon Sep 17 00:00:00 2001 From: Stanislav Lysak Date: Tue, 15 Oct 2024 13:23:24 +0300 Subject: [PATCH 08/15] updated keys --- public/locales/en/profile.json | 6 +- .../@molecules/BurnFuses/BurnFusesContent.tsx | 39 +++++++--- src/components/ProfileSnippet.tsx | 20 ++++- .../[name]/registration/steps/Info.tsx | 2 +- .../registration/steps/Pricing/Pricing.tsx | 2 +- .../profile/[name]/tabs/MoreTab/Ownership.tsx | 6 +- .../ExpirySection/components/ExpiryPanel.tsx | 24 +++++- .../PermissionsTab/NameChangePermissions.tsx | 78 ++++++++++++++++--- .../transaction/changePermissions.ts | 14 +++- .../transaction/transferName.ts | 9 ++- 10 files changed, 165 insertions(+), 35 deletions(-) diff --git a/public/locales/en/profile.json b/public/locales/en/profile.json index 7e8a11f42..a258979bf 100644 --- a/public/locales/en/profile.json +++ b/public/locales/en/profile.json @@ -278,12 +278,12 @@ "label": "Permissions", "warning": "Fuses can be changed by the parent", "CAN_DO_EVERYTHING": "Can do everything", + "CANNOT_UNWRAP": "Can unwrap", "CANNOT_BURN_FUSES": "Can burn fuses", - "CANNOT_CREATE_SUBDOMAIN": "Can create subdomains", + "CANNOT_TRANSFER": "Can transfer", "CANNOT_SET_RESOLVER": "Can set resolver", "CANNOT_SET_TTL": "Can set TTL", - "CANNOT_TRANSFER": "Can transfer", - "CANNOT_UNWRAP": "Can unwrap", + "CANNOT_CREATE_SUBDOMAIN": "Can create subdomains", "PARENT_CANNOT_CONTROL": "Parent can control", "CAN_EXTEND_EXPIRY": "Cannot extend expiry", "IS_DOT_ETH": "Is not .eth" diff --git a/src/components/@molecules/BurnFuses/BurnFusesContent.tsx b/src/components/@molecules/BurnFuses/BurnFusesContent.tsx index 3e5ecca78..347fefa30 100644 --- a/src/components/@molecules/BurnFuses/BurnFusesContent.tsx +++ b/src/components/@molecules/BurnFuses/BurnFusesContent.tsx @@ -2,6 +2,7 @@ import isEqual from 'lodash/isEqual' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import styled, { css } from 'styled-components' +import { match } from 'ts-pattern' import { ChildFuseKeys, ChildFuseReferenceType } from '@ensdomains/ensjs/utils' import { Button, FlameSVG, Helper, mq, Typography } from '@ensdomains/thorin' @@ -81,19 +82,33 @@ const StyledButton = styled(Button)( `, ) +type Permission = ChildFuseReferenceType['Key'] + +const getPermissionTranslationKey = (permission: Permission): string => + match(permission) + .with('CANNOT_UNWRAP', () => `tabs.more.fuses.permissions.CANNOT_UNWRAP`) + .with('CANNOT_BURN_FUSES', () => `tabs.more.fuses.permissions.CANNOT_BURN_FUSES`) + .with('CANNOT_TRANSFER', () => `tabs.more.fuses.permissions.CANNOT_TRANSFER`) + .with('CANNOT_SET_RESOLVER', () => `tabs.more.fuses.permissions.CANNOT_SET_RESOLVER`) + .with('CANNOT_SET_TTL', () => `tabs.more.fuses.permissions.CANNOT_SET_TTL`) + .with('CANNOT_CREATE_SUBDOMAIN', () => `tabs.more.fuses.permissions.CANNOT_CREATE_SUBDOMAIN`) + .otherwise(() => '') + const BurnButton = ({ permission, isBurned, handleBurnClick, isSelected, }: { - permission: ChildFuseReferenceType['Key'] + permission: Permission isBurned: boolean - handleBurnClick: (permission: ChildFuseReferenceType['Key']) => void + handleBurnClick: (permission: Permission) => void isSelected: boolean }) => { const { t } = useTranslation('profile') + const translationKey = getPermissionTranslationKey(permission) + return ( handleBurnClick(permission)} @@ -109,7 +124,7 @@ const BurnButton = ({ } > - {t(`tabs.more.fuses.permissions.${permission}`)} + {t(translationKey)} {isBurned && ( {t('tabs.more.fuses.burned')} @@ -135,8 +150,8 @@ const canContinue = ( ) => { const filteredInitialFuseData: CurrentChildFuses = { ...fuseData } Object.keys(filteredInitialFuseData).forEach((key: string) => { - if (filteredInitialFuseData[key as ChildFuseReferenceType['Key']]) { - delete filteredInitialFuseData[key as ChildFuseReferenceType['Key']] + if (filteredInitialFuseData[key as Permission]) { + delete filteredInitialFuseData[key as Permission] } }) const cannotUnwrap = !fuseData.CANNOT_UNWRAP && !fuseSelected.CANNOT_UNWRAP @@ -161,7 +176,7 @@ type PropsWithReturnObject = BaseProps & { type PropsWithReturnArray = BaseProps & { returnObject?: never - onSubmit: (fuses: ChildFuseReferenceType['Key'][], fuseNames: string[]) => void + onSubmit: (fuses: Permission[], fuseNames: string[]) => void } const BurnFusesContent = ({ @@ -176,7 +191,7 @@ const BurnFusesContent = ({ const [_fuseData, setFuseData] = useState(childFuseObj) const [fuseSelected, setFuseSelected] = useState(childFuseObj) - const handleBurnClick = (permission: ChildFuseReferenceType['Key']) => { + const handleBurnClick = (permission: Permission) => { const nextFuseSelected = { ...fuseSelected } as CurrentChildFuses nextFuseSelected[permission] = !nextFuseSelected[permission] setFuseSelected(nextFuseSelected) @@ -188,10 +203,10 @@ const BurnFusesContent = ({ } const selectedFuses = Object.keys(fuseSelected).filter( - (key) => fuseSelected[key as ChildFuseReferenceType['Key']], - ) as ChildFuseReferenceType['Key'][] + (key) => fuseSelected[key as Permission], + ) as Permission[] - const permissions = selectedFuses.map((key) => t(`tabs.more.fuses.permissions.${key}`)) + const permissions = selectedFuses.map((key) => t(getPermissionTranslationKey(key))) onSubmit(selectedFuses, permissions) } @@ -230,10 +245,10 @@ const BurnFusesContent = ({ {Object.entries(_fuseData).map(([key, value]) => ( ))} diff --git a/src/components/ProfileSnippet.tsx b/src/components/ProfileSnippet.tsx index a478fea35..5e1feafdc 100644 --- a/src/components/ProfileSnippet.tsx +++ b/src/components/ProfileSnippet.tsx @@ -1,6 +1,7 @@ import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import styled, { css } from 'styled-components' +import { match } from 'ts-pattern' import { Button, mq, NametagSVG, Tag, Typography } from '@ensdomains/thorin' @@ -170,6 +171,15 @@ export const getUserDefinedUrl = (url?: string) => { return `` } +type ProfileButton = 'viewProfile' | 'extend' | 'register' + +const getButtonTranslationKey = (button?: ProfileButton): string => + match(button) + .with('viewProfile', () => 'wallet.viewProfile') + .with('extend', () => 'action.extend') + .with('register', () => 'wallet.register') + .otherwise(() => '') + export const ProfileSnippet = ({ name, getTextRecord, @@ -181,7 +191,7 @@ export const ProfileSnippet = ({ }: { name: string getTextRecord?: (key: string) => { value: string } | undefined - button?: 'viewProfile' | 'extend' | 'register' + button?: ProfileButton isPrimary?: boolean isVerified?: boolean children?: React.ReactNode @@ -202,6 +212,8 @@ export const ProfileSnippet = ({ const recordName = getTextRecord?.('name')?.value const ActionButton = useMemo(() => { + const translationKey = getButtonTranslationKey(button) + if (button === 'extend') return ( ) if (button === 'register') @@ -226,7 +238,7 @@ export const ProfileSnippet = ({ size="small" colorStyle="accentSecondary" > - {t(`wallet.${button}`)} + {t(translationKey)} ) if (button === 'viewProfile') @@ -236,7 +248,7 @@ export const ProfileSnippet = ({ size="small" colorStyle="accentSecondary" > - {t(`wallet.${button}`)} + {t(translationKey)} ) // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/src/components/pages/profile/[name]/registration/steps/Info.tsx b/src/components/pages/profile/[name]/registration/steps/Info.tsx index b6dc35a0d..af45e67b0 100644 --- a/src/components/pages/profile/[name]/registration/steps/Info.tsx +++ b/src/components/pages/profile/[name]/registration/steps/Info.tsx @@ -94,7 +94,7 @@ const ProfileButton = styled.button( `, ) -const infoItemArr = Array.from({ length: 3 }, (_, i) => `steps.info.ethItems.${i}`) +const infoItemArr = ['steps.info.ethItems.0', 'steps.info.ethItems.1', 'steps.info.ethItems.2'] type Props = { registrationData: RegistrationReducerDataItem diff --git a/src/components/pages/profile/[name]/registration/steps/Pricing/Pricing.tsx b/src/components/pages/profile/[name]/registration/steps/Pricing/Pricing.tsx index 873ecb57e..efb626f4c 100644 --- a/src/components/pages/profile/[name]/registration/steps/Pricing/Pricing.tsx +++ b/src/components/pages/profile/[name]/registration/steps/Pricing/Pricing.tsx @@ -92,7 +92,7 @@ const gridAreaStyle = ({ $name }: { $name: string }) => css` grid-area: ${$name}; ` -const moonpayInfoItems = Array.from({ length: 2 }, (_, i) => `steps.info.moonpayItems.${i}`) +const moonpayInfoItems = ['steps.info.moonpayItems.0', 'steps.info.moonpayItems.1'] const PaymentChoiceContainer = styled.div` width: 100%; diff --git a/src/components/pages/profile/[name]/tabs/MoreTab/Ownership.tsx b/src/components/pages/profile/[name]/tabs/MoreTab/Ownership.tsx index ff1f8c2c0..ce1f0f3e1 100644 --- a/src/components/pages/profile/[name]/tabs/MoreTab/Ownership.tsx +++ b/src/components/pages/profile/[name]/tabs/MoreTab/Ownership.tsx @@ -263,7 +263,11 @@ const DNSOwnerSection = ({ return ( - {t(`tabs.more.ownership.dnsOwnerWarning.${canSend ? 'isManager' : 'isDnsOwner'}`)} + {t( + canSend + ? `tabs.more.ownership.dnsOwnerWarning.isManager` + : `tabs.more.ownership.dnsOwnerWarning.isDnsOwner`, + )} diff --git a/src/components/@molecules/DateSelection/DateSelection.tsx b/src/components/@molecules/DateSelection/DateSelection.tsx index ceb5ee73a..c8219c6bf 100644 --- a/src/components/@molecules/DateSelection/DateSelection.tsx +++ b/src/components/@molecules/DateSelection/DateSelection.tsx @@ -104,7 +104,9 @@ export const DateSelection = ({ data-testid="date-selection" onClick={() => toggleYearPickView()} > - {t(`calendar.pick_by_${yearPickView === 'date' ? 'years' : 'date'}`, { ns: 'common' })} + {t(yearPickView === 'date' ? 'calendar.pick_by_years' : 'calendar.pick_by_date', { + ns: 'common', + })} diff --git a/src/components/@molecules/NameTableHeader/NameTableHeader.tsx b/src/components/@molecules/NameTableHeader/NameTableHeader.tsx index 1c4479a40..2549973f1 100644 --- a/src/components/@molecules/NameTableHeader/NameTableHeader.tsx +++ b/src/components/@molecules/NameTableHeader/NameTableHeader.tsx @@ -1,6 +1,7 @@ import { PropsWithChildren } from 'react' import { useTranslation } from 'react-i18next' import styled, { css } from 'styled-components' +import { match } from 'ts-pattern' import { GetNamesForAddressParameters } from '@ensdomains/ensjs/subgraph' import { Input, MagnifyingGlassSimpleSVG, mq, Select } from '@ensdomains/thorin' @@ -138,6 +139,14 @@ type Props = { onSortDirectionChange?: (direction: SortDirection) => void } +const getSortTypeTranslationKey = (sort: SortType): string => + match(sort) + .with('name', () => 'sortTypes.name') + .with('createdAt', () => 'sortTypes.createdAt') + .with('expiryDate', () => 'sortTypes.expiryDate') + .with('labelName', () => 'sortTypes.labelName') + .otherwise(() => '') + export const NameTableHeader = ({ sortType, sortTypeOptionValues, @@ -157,7 +166,7 @@ export const NameTableHeader = ({ const inSelectMode = selectable && mode === 'select' const sortTypeOptions = sortTypeOptionValues.map((value) => ({ - label: t(`sortTypes.${value}`), + label: t(getSortTypeTranslationKey(value)), value, })) diff --git a/src/components/@molecules/TransactionDialogManager/DisplayItems.tsx b/src/components/@molecules/TransactionDialogManager/DisplayItems.tsx index 20d967577..30dfe7697 100644 --- a/src/components/@molecules/TransactionDialogManager/DisplayItems.tsx +++ b/src/components/@molecules/TransactionDialogManager/DisplayItems.tsx @@ -259,6 +259,7 @@ export const DisplayItem = ({ key={`${label}-${value}`} > + {/* TODO ? */} {useRawLabel ? label : t(`transaction.itemLabel.${label}`)} diff --git a/src/components/@molecules/TransactionDialogManager/stage/Intro.tsx b/src/components/@molecules/TransactionDialogManager/stage/Intro.tsx index 363ad7996..0e1a41f6f 100644 --- a/src/components/@molecules/TransactionDialogManager/stage/Intro.tsx +++ b/src/components/@molecules/TransactionDialogManager/stage/Intro.tsx @@ -2,6 +2,7 @@ import { useTranslation } from 'react-i18next' import { Button, Dialog } from '@ensdomains/thorin' +import { getTransactionActionTranslationKeys, TransactionAction } from '@app/intl/translationKeys' import { intros } from '@app/transaction-flow/intro' import { TransactionIntro } from '@app/transaction-flow/types' import { TransactionDisplayItemSingle } from '@app/types' @@ -47,6 +48,7 @@ export const IntroStageModal = ({ const TrailingButton = ( ) @@ -57,6 +59,7 @@ export const IntroStageModal = ({ return ( <> + {/* TODO ? */} @@ -69,7 +72,7 @@ export const IntroStageModal = ({ fade: currentStep > index, shrink: true, label: t('transaction.dialog.intro.step', { step: index + 1 }), - value: t(`transaction.description.${name}`), + value: t(getTransactionActionTranslationKeys(name as TransactionAction)), useRawLabel: true, }) as TransactionDisplayItemSingle, ) || [] diff --git a/src/components/Notifications.tsx b/src/components/Notifications.tsx index 811050a68..4e7e063f7 100644 --- a/src/components/Notifications.tsx +++ b/src/components/Notifications.tsx @@ -5,6 +5,11 @@ import styled, { css } from 'styled-components' import { Button, Toast } from '@ensdomains/thorin' import { useChainName } from '@app/hooks/chain/useChainName' +import { + getStatusTranslationKeys, + getTransactionActionTranslationKeys, + TransactionAction, +} from '@app/intl/translationKeys' import { useTransactionFlow } from '@app/transaction-flow/TransactionFlowProvider' import { useBreakpoint } from '@app/utils/BreakpointProvider' import { UpdateCallback, useCallbackOnTransaction } from '@app/utils/SyncProvider/SyncProvider' @@ -28,6 +33,18 @@ const ButtonContainer = styled.div( `, ) +// const getStatusTranslationKeys = (status?: 'confirmed' | 'failed' | 'pending'): string => +// match(status) +// .with('confirmed', () => ({ +// title: `transaction.status.confirmed.notifyTitle`, +// description: t(`transaction.status.confirmed.notifyMessage`, { +// action: t(`transaction.description.${action}`), +// }), +// })) +// .with('failed', () => 'action.extend') +// .with('register', () => 'wallet.register') +// .otherwise(() => '') + export const Notifications = () => { const { t } = useTranslation() const breakpoints = useBreakpoint() @@ -61,9 +78,9 @@ export const Notifications = () => { } const resumable = key && getResumable(key) const item = { - title: t(`transaction.status.${status}.notifyTitle`), - description: t(`transaction.status.${status}.notifyMessage`, { - action: t(`transaction.description.${action}`), + title: t(getStatusTranslationKeys(status).notifyTitle), + description: t(getStatusTranslationKeys(status).notifyMessage, { + action: t(getTransactionActionTranslationKeys(action as TransactionAction)), }), children: resumable ? ( diff --git a/src/components/pages/import/[name]/steps/EnableDnssec.tsx b/src/components/pages/import/[name]/steps/EnableDnssec.tsx index c89da72fe..1560935fe 100644 --- a/src/components/pages/import/[name]/steps/EnableDnssec.tsx +++ b/src/components/pages/import/[name]/steps/EnableDnssec.tsx @@ -25,7 +25,6 @@ export const EnableDnssec = ({ selected: SelectedItemProperties }) => { const { t } = useTranslation('dnssec') - const { t: tc } = useTranslation('common') const { data: isDnsSecEnabled, @@ -74,14 +73,14 @@ export const EnableDnssec = ({ colorStyle="accentSecondary" onClick={() => dispatch({ name: 'decreaseStep', selected })} > - {tc('action.back')} + {t('action.back', { ns: 'common' })} dispatch({ name: 'increaseStep', selected })} data-testid="import-next-button" > - {tc('action.next')} + {t('action.next', { ns: 'common' })} diff --git a/src/components/pages/import/[name]/steps/SelectImportType.tsx b/src/components/pages/import/[name]/steps/SelectImportType.tsx index 208947f22..b876768c4 100644 --- a/src/components/pages/import/[name]/steps/SelectImportType.tsx +++ b/src/components/pages/import/[name]/steps/SelectImportType.tsx @@ -154,7 +154,6 @@ export const SelectImportType = ({ selected: SelectedItemProperties }) => { const { t } = useTranslation('dnssec') - const { t: tc } = useTranslation('common') const { address } = useAccount() const chainId = useChainId() @@ -267,7 +266,7 @@ export const SelectImportType = ({ onClick={() => setStepsAndNavigate()} data-testid="import-next-button" > - {tc('action.next')} + {t('action.next', { ns: 'common' })} ) diff --git a/src/components/pages/import/[name]/steps/VerifyOffchainOwnership.tsx b/src/components/pages/import/[name]/steps/VerifyOffchainOwnership.tsx index 6aaed2e82..c5479b545 100644 --- a/src/components/pages/import/[name]/steps/VerifyOffchainOwnership.tsx +++ b/src/components/pages/import/[name]/steps/VerifyOffchainOwnership.tsx @@ -2,6 +2,7 @@ import { useConnectModal } from '@rainbow-me/rainbowkit' import { Dispatch, useMemo } from 'react' import { useTranslation } from 'react-i18next' import styled, { css } from 'styled-components' +import { match } from 'ts-pattern' import { CheckCircleSVG, Helper } from '@ensdomains/thorin' @@ -22,6 +23,7 @@ import { import { StatusChecker } from '../StatusChecker' import { SupportLinkList } from '../SupportLinkList' import { DnsImportReducerAction, SelectedItemProperties } from '../useDnsImportReducer' +import { checkDnsError } from '../utils' const ValueButtonsContainer = styled.div( ({ theme }) => css` @@ -63,6 +65,17 @@ const getDnsResolverValue = (chainId: number) => { if (chainId === 1) return 'dnsname.ens.eth' return EXTENDED_DNS_RESOLVER_MAP[String(chainId)] } +type ErrorKey = ReturnType + +const getErrorTranslationKey = (error: ErrorKey): string => + match(error) + .with('unknown', () => 'error.unknown') + .with('noTxtRecord', () => 'error.noTxtRecord') + .with('dnssecFailure', () => 'error.dnssecFailure') + .with('invalidTxtRecord', () => 'error.invalidTxtRecord') + .with('invalidAddressChecksum', () => 'error.invalidAddressChecksum') + .with('resolutionFailure', () => 'error.resolutionFailure') + .otherwise(() => '') export const VerifyOffchainOwnership = ({ dispatch, @@ -72,7 +85,6 @@ export const VerifyOffchainOwnership = ({ selected: SelectedItemProperties }) => { const { t } = useTranslation('dnssec') - const { t: tc } = useTranslation('common') const { address, chainId } = selected const isConnected = !!address @@ -93,9 +105,9 @@ export const VerifyOffchainOwnership = ({ const { openConnectModal } = useConnectModal() const errorMessage = useMemo(() => { - if (error) return tc(`error.${error}`, { ns: 'dnssec' }) + if (error) return t(getErrorTranslationKey(error as ErrorKey)) return null - }, [tc, error]) + }, [t, error]) return ( @@ -163,7 +175,7 @@ export const VerifyOffchainOwnership = ({ colorStyle="accentSecondary" onClick={() => dispatch({ name: 'decreaseStep', selected })} > - {tc('action.back')} + {t('action.back', { ns: 'common' })} {isConnected ? ( {dnsOffchainStatus?.address?.status === 'mismatching' - ? tc('action.finish') - : tc('action.claim')} + ? t('action.finish', { ns: 'common' }) + : t('action.claim', { ns: 'common' })} ) : ( openConnectModal?.()}> - {tc('action.connect')} + {t('action.connect', { ns: 'common' })} )} diff --git a/src/components/pages/import/[name]/steps/onchain/ImportTransaction.tsx b/src/components/pages/import/[name]/steps/onchain/ImportTransaction.tsx index 01c0cd2bd..9934b982a 100644 --- a/src/components/pages/import/[name]/steps/onchain/ImportTransaction.tsx +++ b/src/components/pages/import/[name]/steps/onchain/ImportTransaction.tsx @@ -103,7 +103,6 @@ export const ImportTransaction = ({ selected: SelectedItemProperties }) => { const { t } = useTranslation('dnssec') - const { t: tc } = useTranslation('common') const { data: gasPrice } = useGasPrice() const { userConfig, setCurrency } = useUserConfig() @@ -237,13 +236,13 @@ export const ImportTransaction = ({ - {tc('name.owner')} + {t('name.owner', { ns: 'common' })} {dnsOwner && } {dnsOwnerStatus === 'mismatching' && ( - {tc('steps.verifyOwnership.status.mismatching.error.onchain', { ns: 'dnssec' })} + {t('steps.verifyOwnership.status.mismatching.error.onchain', { ns: 'dnssec' })} )} @@ -251,7 +250,7 @@ export const ImportTransaction = ({ colorStyle="accentSecondary" onClick={() => dispatch({ name: 'decreaseStep', selected })} > - {tc('action.back')} + {t('action.back', { ns: 'common' })} startOrResumeFlow()} data-testid="import-next-button" > - {dnsOwnerStatus === 'mismatching' ? tc('action.import') : tc('action.claim')} + {dnsOwnerStatus === 'mismatching' + ? t('action.import', { ns: 'common' }) + : t('action.claim', { ns: 'common' })} diff --git a/src/components/pages/import/[name]/steps/onchain/VerifyOnchainOwnership.tsx b/src/components/pages/import/[name]/steps/onchain/VerifyOnchainOwnership.tsx index e92d7f837..5db7df6a4 100644 --- a/src/components/pages/import/[name]/steps/onchain/VerifyOnchainOwnership.tsx +++ b/src/components/pages/import/[name]/steps/onchain/VerifyOnchainOwnership.tsx @@ -2,6 +2,7 @@ import { useConnectModal } from '@rainbow-me/rainbowkit' import { Dispatch, useMemo } from 'react' import { useTranslation } from 'react-i18next' import styled, { css } from 'styled-components' +import { match } from 'ts-pattern' import { CheckCircleSVG, Helper, Typography } from '@ensdomains/thorin' @@ -59,6 +60,16 @@ const RecordItemWrapper = styled.div( `, ) +const getErrorTranslationKey = (error: ReturnType): string => + match(error) + .with('unknown', () => 'error.unknown') + .with('noTxtRecord', () => 'error.noTxtRecord') + .with('dnssecFailure', () => 'error.dnssecFailure') + .with('invalidTxtRecord', () => 'error.invalidTxtRecord') + .with('invalidAddressChecksum', () => 'error.invalidAddressChecksum') + .with('resolutionFailure', () => 'error.resolutionFailure') + .otherwise(() => '') + export const VerifyOnchainOwnership = ({ dispatch, selected, @@ -67,7 +78,6 @@ export const VerifyOnchainOwnership = ({ selected: SelectedItemProperties }) => { const { t } = useTranslation('dnssec') - const { t: tc } = useTranslation('common') const { data: dnsOwner, @@ -93,8 +103,8 @@ export const VerifyOnchainOwnership = ({ const errorMessage = useMemo(() => { const errorKey = checkDnsError({ error, isLoading }) if (!errorKey) return null - return tc(`error.${errorKey}`, { ns: 'dnssec' }) - }, [tc, error, isLoading]) + return t(getErrorTranslationKey(errorKey), { ns: 'dnssec' }) + }, [t, error, isLoading]) return ( @@ -160,7 +170,7 @@ export const VerifyOnchainOwnership = ({ colorStyle="accentSecondary" onClick={() => dispatch({ name: 'decreaseStep', selected })} > - {tc('action.back')} + {t('action.back', { ns: 'common' })} {isConnected ? ( {dnsOwnerStatus === 'mismatching' ? t('steps.verifyOwnership.action.importWithoutOwnership') - : tc('action.next')} + : t('action.next', { ns: 'common' })} ) : ( openConnectModal?.()}> - {tc('action.connect')} + {t('action.connect', { ns: 'common' })} )} diff --git a/src/components/pages/profile/[name]/Profile.tsx b/src/components/pages/profile/[name]/Profile.tsx index adf9c2a9e..7f3998cfc 100644 --- a/src/components/pages/profile/[name]/Profile.tsx +++ b/src/components/pages/profile/[name]/Profile.tsx @@ -68,6 +68,16 @@ const TabButton = styled.button<{ $selected: boolean }>( const tabs = ['profile', 'records', 'ownership', 'subnames', 'permissions', 'more'] as const type Tab = (typeof tabs)[number] +const getTabTranslationKey = (tab: Tab): string => + match(tab) + .with('profile', () => `tabs.profile.name`) + .with('records', () => 'tabs.records.name') + .with('ownership', () => 'tabs.ownership.name') + .with('subnames', () => 'tabs.subnames.name') + .with('permissions', () => 'tabs.permissions.name') + .with('more', () => 'tabs.more.name') + .otherwise(() => '') + type Props = { isSelf: boolean isLoading: boolean @@ -247,7 +257,7 @@ const ProfileContent = ({ isSelf, isLoading: parentIsLoading, name }: Props) => onClick={() => setTab(tabItem)} > - {t(`tabs.${tabItem}.name`)} + {t(getTabTranslationKey(tabItem))} ))} diff --git a/src/components/pages/profile/[name]/tabs/MoreTab/Ownership.tsx b/src/components/pages/profile/[name]/tabs/MoreTab/Ownership.tsx index ce1f0f3e1..cd8abb01b 100644 --- a/src/components/pages/profile/[name]/tabs/MoreTab/Ownership.tsx +++ b/src/components/pages/profile/[name]/tabs/MoreTab/Ownership.tsx @@ -16,6 +16,7 @@ import { useDnsImportData } from '@app/hooks/ensjs/dns/useDnsImportData' import { GetDnsOwnerQueryKey, UseDnsOwnerError } from '@app/hooks/ensjs/dns/useDnsOwner' import { usePrimaryName } from '@app/hooks/ensjs/public/usePrimaryName' import { useOwners } from '@app/hooks/useOwners' +import { getProfileErrorTranslationKey, ProfileError } from '@app/intl/translationKeys' import { makeIntroItem } from '@app/transaction-flow/intro' import { createTransactionItem } from '@app/transaction-flow/transaction' import { useTransactionFlow } from '@app/transaction-flow/TransactionFlowProvider' @@ -151,6 +152,7 @@ const Owner = ({ address, label }: OwnerItem) => { )} + {/* TODO ? */} {t(label)} @@ -299,7 +301,7 @@ const Ownership = ({ name: string owners: ReturnType canSend: boolean - canSendError?: string + canSendError?: ProfileError isCachedData: boolean isWrapped: boolean }) => { @@ -331,7 +333,7 @@ const Ownership = ({ {!canSend && canSendError && ( 'emancipated' as const) .otherwise(() => 'wrapped') +const getTokenStatusTranslationKey = (status: NameWrapperState): string => + match(status) + .with('unwrapped', () => 'tabs.more.token.status.unwrapped') + .with('wrapped', () => 'tabs.more.token.status.wrapped') + .with('emancipated', () => 'tabs.more.token.status.emancipated') + .with('locked', () => 'tabs.more.token.status.locked') + .otherwise(() => '') + const Token = ({ name, isWrapped, canBeWrapped, ownerData, wrapperData, profile }: Props) => { const { t } = useTranslation('profile') @@ -165,7 +173,7 @@ const Token = ({ name, isWrapped, canBeWrapped, ownerData, wrapperData, profile {isWrapped ? ( diff --git a/src/components/pages/profile/[name]/tabs/OwnershipTab/sections/RolesSection/RolesSection.tsx b/src/components/pages/profile/[name]/tabs/OwnershipTab/sections/RolesSection/RolesSection.tsx index 5f0d587bc..c1242eec6 100644 --- a/src/components/pages/profile/[name]/tabs/OwnershipTab/sections/RolesSection/RolesSection.tsx +++ b/src/components/pages/profile/[name]/tabs/OwnershipTab/sections/RolesSection/RolesSection.tsx @@ -8,6 +8,7 @@ import { PseudoActionButton } from '@app/components/@atoms/PseudoActionButton/Ps import { DisabledButtonWithTooltip } from '@app/components/@molecules/DisabledButtonWithTooltip' import type { GroupedRoleRecord } from '@app/hooks/ownership/useRoles/useRoles' import type { useNameDetails } from '@app/hooks/useNameDetails' +import { getProfileErrorTranslationKey, ProfileError } from '@app/intl/translationKeys' import { Header } from './components/Header' import { RoleRow } from './components/RoleRow' @@ -70,7 +71,7 @@ export const RolesSection = ({ name, roles, details }: Props) => { return (
window.open(makeEtherscanLink(address!, networkName, 'address'), '_blank'), icon: , } diff --git a/src/components/pages/profile/[name]/tabs/OwnershipTab/sections/RolesSection/components/RoleTag.tsx b/src/components/pages/profile/[name]/tabs/OwnershipTab/sections/RolesSection/components/RoleTag.tsx index e95b41bb1..215d2d8e5 100644 --- a/src/components/pages/profile/[name]/tabs/OwnershipTab/sections/RolesSection/components/RoleTag.tsx +++ b/src/components/pages/profile/[name]/tabs/OwnershipTab/sections/RolesSection/components/RoleTag.tsx @@ -1,6 +1,7 @@ import { TOptions } from 'i18next' import { useTranslation } from 'react-i18next' import styled, { css } from 'styled-components' +import { match } from 'ts-pattern' import { OutlinkSVG, QuestionCircleSVG, Tooltip, Typography } from '@ensdomains/thorin' @@ -49,6 +50,30 @@ const Container = styled.button( `, ) +const getRoleTranslationKey = ( + role: + | Role + | 'owner-emancipated' + | 'profile-editor' + | 'subname-manager' + | 'grace-period' + | 'contract-address' + | 'namewrapper', +): string => + match(role) + .with('owner', () => 'tabs.ownership.tooltips.owner') + .with('owner-emancipated', () => 'tabs.ownership.tooltips.owner-emancipated') + .with('parent-owner', () => 'tabs.ownership.tooltips.parent-owner') + .with('dns-owner', () => 'tabs.ownership.tooltips.dns-owner') + .with('manager', () => 'tabs.ownership.tooltips.manager') + .with('eth-record', () => 'tabs.ownership.tooltips.eth-record') + .with('profile-editor', () => 'tabs.ownership.tooltips.profile-editor') + .with('subname-manager', () => 'tabs.ownership.tooltips.subname-manager') + .with('grace-period', () => 'tabs.ownership.tooltips.grace-period') + .with('contract-address', () => 'tabs.ownership.tooltips.contract-address') + .with('namewrapper', () => 'tabs.ownership.tooltips.namewrapper') + .otherwise(() => '') + export const RoleTag = ({ name, role, @@ -68,7 +93,7 @@ export const RoleTag = ({ - {t(`tabs.ownership.tooltips.${_role}`, tOptions)} + {t(getRoleTranslationKey(role), tOptions)} {link && ( diff --git a/src/components/pages/profile/[name]/tabs/PermissionsTab/NameChangePermissions.tsx b/src/components/pages/profile/[name]/tabs/PermissionsTab/NameChangePermissions.tsx index e1e8e6dd6..ebb72b9a6 100644 --- a/src/components/pages/profile/[name]/tabs/PermissionsTab/NameChangePermissions.tsx +++ b/src/components/pages/profile/[name]/tabs/PermissionsTab/NameChangePermissions.tsx @@ -230,16 +230,14 @@ export const NameChangePermissions = ({ ))} {permissions.burned.map(({ translationKey, fuse }) => ( - - {t(`tabs.permissions.nameChangePermissions.permissions.${translationKey}.label`)} - + {t(translationKey.label)} {fusesSetDates?.[fuse] && ( {t('tabs.permissions.revokedLabel', { date: fusesSetDates[fuse] })} )} - {t(`tabs.permissions.nameChangePermissions.permissions.${translationKey}.description`, { + {t(translationKey.description, { owner: isParentLocked ? t('tabs.permissions.role.owner') : t('tabs.permissions.role.parent'), diff --git a/src/components/pages/profile/[name]/tabs/SubnamesTab.tsx b/src/components/pages/profile/[name]/tabs/SubnamesTab.tsx index 4fd3f918d..2dabacca3 100644 --- a/src/components/pages/profile/[name]/tabs/SubnamesTab.tsx +++ b/src/components/pages/profile/[name]/tabs/SubnamesTab.tsx @@ -16,6 +16,7 @@ import { Card } from '@app/components/Card' import { Outlink } from '@app/components/Outlink' import { TabWrapper } from '@app/components/pages/profile/TabWrapper' import { useSubnames } from '@app/hooks/ensjs/subgraph/useSubnames' +import { getProfileErrorTranslationKey, ProfileError } from '@app/intl/translationKeys' import { useTransactionFlow } from '@app/transaction-flow/TransactionFlowProvider' import { emptyAddress } from '@app/utils/constants' import { getSupportLink } from '@app/utils/supportLinks' @@ -226,7 +227,11 @@ export const SubnamesTab = ({ {...{ size: 'medium', buttonId: 'add-subname-disabled-button', - content: t(`errors.${canCreateSubdomainsError || 'default'}`), + content: t( + getProfileErrorTranslationKey( + (canCreateSubdomainsError || 'default') as ProfileError, + ), + ), buttonText: t('details.tabs.subnames.addSubname.action'), mobileWidth: 200, mobilePlacement: 'top', diff --git a/src/components/pages/profile/settings/TransactionSection/TransactionSection.tsx b/src/components/pages/profile/settings/TransactionSection/TransactionSection.tsx index 94ec02377..c1c9b3e29 100644 --- a/src/components/pages/profile/settings/TransactionSection/TransactionSection.tsx +++ b/src/components/pages/profile/settings/TransactionSection/TransactionSection.tsx @@ -10,6 +10,7 @@ import { useChainName } from '@app/hooks/chain/useChainName' import { useClearRecentTransactions } from '@app/hooks/transactions/useClearRecentTransactions' import { useRecentTransactions } from '@app/hooks/transactions/useRecentTransactions' import useThrottledCallback from '@app/hooks/useThrottledCallback' +import { getStatusTranslationKeys } from '@app/intl/translationKeys' import { useTransactionFlow } from '@app/transaction-flow/TransactionFlowProvider' import { makeEtherscanLink } from '@app/utils/utils' @@ -138,7 +139,6 @@ const getTransactionExtraInfo = (action: string, key?: string) => { export const TransactionSection = () => { const { t } = useTranslation('settings') - const { t: tc } = useTranslation('common') const chainName = useChainName() const transactions = useRecentTransactions() @@ -194,7 +194,7 @@ export const TransactionSection = () => { disabled={!canClear} data-testid="transaction-clear-button" > - {tc('action.clear')} + {t('action.clear', { ns: 'common' })} } fill @@ -216,15 +216,15 @@ export const TransactionSection = () => { )} - {`${tc( - `transaction.description.${action}`, - )}${getTransactionExtraInfo(action, key)}`} + {`${ + (t(`transaction.description.${action}`), { ns: 'common' }) + }${getTransactionExtraInfo(action, key)}`} - {tc(`transaction.status.${status}.regular`)} + {t(getStatusTranslationKeys(status).regular, { ns: 'common' })} @@ -243,7 +243,9 @@ export const TransactionSection = () => { onClick={() => setViewAmt((curr) => curr + 5)} data-testid="transaction-view-more-button" > - {tc('transaction.viewMore')} + + {t('transaction.viewMore', { ns: 'common' })} + )} diff --git a/src/components/pages/profile/settings/WalletSection.tsx b/src/components/pages/profile/settings/WalletSection.tsx index 1b90278ba..c387d7f51 100644 --- a/src/components/pages/profile/settings/WalletSection.tsx +++ b/src/components/pages/profile/settings/WalletSection.tsx @@ -6,7 +6,6 @@ import { Button } from '@ensdomains/thorin' import { SectionContainer } from './Section' export const WalletSection = () => { - const { t: tc } = useTranslation() const { t } = useTranslation('settings') const { disconnect } = useDisconnect() @@ -21,7 +20,7 @@ export const WalletSection = () => { colorStyle="redSecondary" onClick={() => disconnect()} > - {tc('wallet.disconnect')} + {t('wallet.disconnect', { ns: 'common' })} } fill diff --git a/src/hooks/primary/useGetPrimaryNameTransactionFlowItem/utils/index.ts b/src/hooks/primary/useGetPrimaryNameTransactionFlowItem/utils/index.ts index 234fc83f3..d99a0910c 100644 --- a/src/hooks/primary/useGetPrimaryNameTransactionFlowItem/utils/index.ts +++ b/src/hooks/primary/useGetPrimaryNameTransactionFlowItem/utils/index.ts @@ -1,7 +1,22 @@ +import { match } from 'ts-pattern' + export type IntroType = 'noResolver' | 'invalidResolver' | 'updateEthAddress' -export const getIntroTranslation = (type: IntroType, part: 'title' | 'description') => - `intro.selectPrimaryName.${type}.${part}` +export const getIntroTranslation = (type: IntroType, part: 'title' | 'description'): string => + match([type, part]) + .with(['noResolver', 'title'], () => `intro.selectPrimaryName.noResolver.title`) + .with(['noResolver', 'description'], () => `intro.selectPrimaryName.noResolver.description`) + .with(['invalidResolver', 'title'], () => `intro.selectPrimaryName.invalidResolver.title`) + .with( + ['invalidResolver', 'description'], + () => `intro.selectPrimaryName.invalidResolver.description`, + ) + .with(['updateEthAddress', 'title'], () => `intro.selectPrimaryName.updateEthAddress.title`) + .with( + ['updateEthAddress', 'description'], + () => `intro.selectPrimaryName.updateEthAddress.description`, + ) + .otherwise(() => '') /** * Conditions: diff --git a/src/intl/translationKeys.ts b/src/intl/translationKeys.ts new file mode 100644 index 000000000..aecfe2206 --- /dev/null +++ b/src/intl/translationKeys.ts @@ -0,0 +1,200 @@ +import { match } from 'ts-pattern' + +import type { Role } from '@app/hooks/ownership/useRoles/useRoles' + +type GetTranslationKeys = (...args: T[]) => RT + +export type TransactionAction = + | 'setName' + | 'setRecords' + | 'test' + | 'addSuccess' + | 'sendName' + | 'migrateProfile' + | 'migrateProfileWithReset' + | 'migrateProfileWithSync' + | 'migrateProfileWithEthAddress' + | 'wrapName' + | 'updateResolver' + | 'updateProfile' + | 'setPrimaryName' + | 'resetPrimaryName' + | 'updateEthAddress' + | 'testSendName' + | 'burnFuses' + | 'createSubname' + | 'deleteSubname' + | 'extendNames' + | 'approveDnsRegistrar' + | 'claimDnsName' + | 'importDnsName' + | 'commitName' + | 'registerName' + | 'approveNameWrapper' + | 'clearRecords' + | 'updateRecords' + | 'updateRecord' + | 'removeRecord' + | 'resetProfileWithRecords' + | 'transferName' + | 'transferSubname' + | 'changePermissions' + | 'syncManager' + | 'updateProfileRecords' + | 'resetProfile' + | 'unwrapName' + | 'updateVerificationRecord' + | 'removeVerificationRecord' + +export type ProfileError = + | 'unknown' + | 'invalidName' + | 'invalidAddress' + | 'expiringSoon' + | 'hasExpired' + | 'ownerManagerChoice' + | 'notMigrated' + | 'featureNotAvailable' + | 'featureNotAvailableLink' + | 'migrationNotAvailable' + | 'migrationNotAvailableLink' + | 'addressLength' + | 'unsupportedTLD' + | 'keyInUse' + | 'hasSubnames' + | 'permissionRevoked' + | 'gracePeriod' + | 'default' + | 'invalidJSON' + | 'isOwnerCannotEdit' + | 'cannotEdit' + | 'isOwnerCannotVerify' + | 'cannotVerify' + +export const getProfileErrorTranslationKey: GetTranslationKeys = (error) => + match(error) + .with('invalidName', () => 'errors.invalidName') + .with('invalidAddress', () => 'errors.invalidAddress') + .with('expiringSoon', () => 'errors.expiringSoon') + .with('hasExpired', () => 'errors.hasExpired') + .with('ownerManagerChoice', () => 'errors.ownerManagerChoice') + .with('notMigrated', () => 'errors.notMigrated') + .with('featureNotAvailable', () => 'errors.featureNotAvailable') + .with('featureNotAvailableLink', () => 'errors.featureNotAvailableLink') + .with('migrationNotAvailable', () => 'errors.migrationNotAvailable') + .with('migrationNotAvailableLink', () => 'errors.migrationNotAvailableLink') + .with('addressLength', () => 'errors.addressLength') + .with('unsupportedTLD', () => 'errors.unsupportedTLD') + .with('keyInUse', () => 'errors.keyInUse') + .with('hasSubnames', () => 'errors.hasSubnames') + .with('permissionRevoked', () => 'errors.permissionRevoked') + .with('gracePeriod', () => 'errors.gracePeriod') + .with('default', () => 'errors.default') + .with('invalidJSON', () => 'errors.invalidJSON') + .with('isOwnerCannotEdit', () => 'errors.isOwnerCannotEdit') + .with('cannotEdit', () => 'errors.cannotEdit') + .with('isOwnerCannotVerify', () => 'errors.isOwnerCannotVerify') + .with('cannotVerify', () => 'errors.cannotVerify') + .otherwise(() => 'errors.unknown') + +export const getRoleTranslationKeys: GetTranslationKeys< + Role, + { title: string; description: string } +> = (role) => + match(role) + .with('manager', () => ({ + title: 'roles.manager.title', + description: 'roles.manager.description', + })) + .with('owner', () => ({ + title: 'roles.owner.title', + description: 'roles.owner.description', + })) + .with('eth-record', () => ({ + title: 'roles.eth-record.title', + description: 'roles.eth-record.description', + })) + .with('dns-owner', () => ({ + title: 'roles.dns-owner.title', + description: 'roles.dns-owner.description', + })) + .with('parent-owner', () => ({ + title: 'roles.parent-owner.title', + description: 'roles.parent-owner.description', + })) + .otherwise(() => ({ title: '', description: '' })) + +export const getStatusTranslationKeys: GetTranslationKeys< + 'pending' | 'confirmed' | 'failed' | 'searching', + { + regular: string + notifyTitle: string + notifyMessage: string + } +> = (status) => + match(status) + .with('pending', () => ({ + regular: 'transaction.status.pending.regular', + notifyTitle: 'transaction.status.pending..title', + notifyMessage: 'transaction.status.pending..description', + })) + .with('confirmed', () => ({ + regular: 'transaction.status.confirmed.regular', + notifyTitle: 'transaction.status.confirmed.notifyTitle', + notifyMessage: 'transaction.status.confirmed.notifyMessage', + })) + .with('failed', () => ({ + regular: 'transaction.status.failed.regular', + notifyTitle: 'transaction.status.failed.notifyTitle', + notifyMessage: 'transaction.status.failed.notifyMessage', + })) + .otherwise(() => ({ regular: '', notifyTitle: '', notifyMessage: '' })) + +export const getTransactionActionTranslationKeys: GetTranslationKeys = ( + status, +) => + match(status) + .with('setName', () => 'transaction.description.setName') + .with('setRecords', () => 'transaction.description.setRecords') + .with('test', () => 'transaction.description.test') + .with('addSuccess', () => 'transaction.description.addSuccess') + .with('sendName', () => 'transaction.description.sendName') + .with('migrateProfile', () => 'transaction.description.migrateProfile') + .with('migrateProfileWithReset', () => 'transaction.description.migrateProfileWithReset') + .with('migrateProfileWithSync', () => 'transaction.description.migrateProfileWithSync') + .with( + 'migrateProfileWithEthAddress', + () => 'transaction.description.migrateProfileWithEthAddress', + ) + .with('wrapName', () => 'transaction.description.wrapName') + .with('updateResolver', () => 'transaction.description.updateResolver') + .with('updateProfile', () => 'transaction.description.updateProfile') + .with('setPrimaryName', () => 'transaction.description.setPrimaryName') + .with('resetPrimaryName', () => 'transaction.description.resetPrimaryName') + .with('updateEthAddress', () => 'transaction.description.updateEthAddress') + .with('testSendName', () => 'transaction.description.testSendName') + .with('burnFuses', () => 'transaction.description.burnFuses') + .with('createSubname', () => 'transaction.description.createSubname') + .with('deleteSubname', () => 'transaction.description.deleteSubname') + .with('extendNames', () => 'transaction.description.extendNames') + .with('approveDnsRegistrar', () => 'transaction.description.approveDnsRegistrar') + .with('claimDnsName', () => 'transaction.description.claimDnsName') + .with('importDnsName', () => 'transaction.description.importDnsName') + .with('commitName', () => 'transaction.description.commitName') + .with('registerName', () => 'transaction.description.registerName') + .with('approveNameWrapper', () => 'transaction.description.approveNameWrapper') + .with('clearRecords', () => 'transaction.description.clearRecords') + .with('updateRecords', () => 'transaction.description.updateRecords') + .with('updateRecord', () => 'transaction.description.updateRecord') + .with('removeRecord', () => 'transaction.description.removeRecord') + .with('resetProfileWithRecords', () => 'transaction.description.resetProfileWithRecords') + .with('transferName', () => 'transaction.description.transferName') + .with('transferSubname', () => 'transaction.description.transferSubname') + .with('changePermissions', () => 'transaction.description.changePermissions') + .with('syncManager', () => 'transaction.description.syncManager') + .with('updateProfileRecords', () => 'transaction.description.updateProfileRecords') + .with('resetProfile', () => 'transaction.description.resetProfile') + .with('unwrapName', () => 'transaction.description.unwrapName') + .with('updateVerificationRecord', () => 'transaction.description.updateVerificationRecord') + .with('removeVerificationRecord', () => 'transaction.description.removeVerificationRecord') + .otherwise(() => '') diff --git a/src/transaction-flow/input/CreateSubname-flow.tsx b/src/transaction-flow/input/CreateSubname-flow.tsx index a025c8aed..9f9f82a16 100644 --- a/src/transaction-flow/input/CreateSubname-flow.tsx +++ b/src/transaction-flow/input/CreateSubname-flow.tsx @@ -1,6 +1,7 @@ import { useState } from 'react' import { useTranslation } from 'react-i18next' import styled, { css } from 'styled-components' +import { match } from 'ts-pattern' import { validateName } from '@ensdomains/ensjs/utils' import { Button, Dialog, Input } from '@ensdomains/thorin' @@ -11,6 +12,13 @@ import { useValidateSubnameLabel } from '../../hooks/useValidateSubnameLabel' import { createTransactionItem } from '../transaction' import { TransactionDialogPassthrough } from '../types' +type AddSubnameError = + | 'invalidCharacters' + | 'mustUseLowercase' + | 'alreadyExists' + | 'nameTooLong' + | 'pccBurned' + type Data = { parent: string isWrapped: boolean @@ -29,6 +37,21 @@ const ParentLabel = styled.div( `, ) +const getErrorTranslationKey = (error: AddSubnameError): string => + match(error) + .with( + 'invalidCharacters', + () => 'details.tabs.subnames.addSubname.dialog.error.invalidCharacters', + ) + .with( + 'mustUseLowercase', + () => 'details.tabs.subnames.addSubname.dialog.error.mustUseLowercase', + ) + .with('alreadyExists', () => 'details.tabs.subnames.addSubname.dialog.error.alreadyExists') + .with('nameTooLong', () => 'details.tabs.subnames.addSubname.dialog.error.nameTooLong') + .with('pccBurned', () => 'details.tabs.subnames.addSubname.dialog.error.pccBurned') + .otherwise(() => '') + const CreateSubname = ({ data: { parent, isWrapped }, dispatch, onDismiss }: Props) => { const { t } = useTranslation('profile') @@ -85,7 +108,7 @@ const CreateSubname = ({ data: { parent, isWrapped }, dispatch, onDismiss }: Pro }} error={ error - ? t(`details.tabs.subnames.addSubname.dialog.error.${error}`, { date: expiryLabel }) + ? t(getErrorTranslationKey(error as AddSubnameError), { date: expiryLabel }) : undefined } /> diff --git a/src/transaction-flow/input/EditRoles/views/EditRoleView/EditRoleView.tsx b/src/transaction-flow/input/EditRoles/views/EditRoleView/EditRoleView.tsx index a021149f6..ae61a6a87 100644 --- a/src/transaction-flow/input/EditRoles/views/EditRoleView/EditRoleView.tsx +++ b/src/transaction-flow/input/EditRoles/views/EditRoleView/EditRoleView.tsx @@ -7,6 +7,7 @@ import { match, P } from 'ts-pattern' import { Button, Dialog, Input, MagnifyingGlassSimpleSVG, mq } from '@ensdomains/thorin' import { DialogFooterWithBorder } from '@app/components/@molecules/DialogComponentVariants/DialogFooterWithBorder' +import { getRoleTranslationKeys } from '@app/intl/translationKeys' import { SearchViewErrorView } from '@app/transaction-flow/input/SendName/views/SearchView/views/SearchViewErrorView' import { SearchViewLoadingView } from '@app/transaction-flow/input/SendName/views/SearchView/views/SearchViewLoadingView' import { SearchViewNoResultsView } from '@app/transaction-flow/input/SendName/views/SearchView/views/SearchViewNoResultsView' @@ -48,7 +49,7 @@ export const EditRoleView = ({ index, onBack }: Props) => { <> diff --git a/src/transaction-flow/input/EditRoles/views/EditRoleView/views/EditRoleIntroView.tsx b/src/transaction-flow/input/EditRoles/views/EditRoleView/views/EditRoleIntroView.tsx index 546b64a01..d7670316e 100644 --- a/src/transaction-flow/input/EditRoles/views/EditRoleView/views/EditRoleIntroView.tsx +++ b/src/transaction-flow/input/EditRoles/views/EditRoleView/views/EditRoleIntroView.tsx @@ -7,6 +7,7 @@ import { Button, mq } from '@ensdomains/thorin' import { AvatarWithIdentifier } from '@app/components/@molecules/AvatarWithIdentifier/AvatarWithIdentifier' import { useAccountSafely } from '@app/hooks/account/useAccountSafely' import type { Role } from '@app/hooks/ownership/useRoles/useRoles' +import { getRoleTranslationKeys } from '@app/intl/translationKeys' import { SearchViewIntroView } from '@app/transaction-flow/input/SendName/views/SearchView/views/SearchViewIntroView' import { emptyAddress } from '@app/utils/constants' @@ -68,7 +69,7 @@ export const EditRoleIntroView = ({ role, address, onSelect }: Props) => {