diff --git a/package.json b/package.json
index 90cfb5bb0..262be45b4 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",
@@ -155,6 +156,7 @@
"eslint-plugin-testing-library": "^6.0.2",
"eslint-plugin-vitest": "^0.4.0",
"ethers": "^5.7.2",
+ "glob": "^11.0.0",
"hardhat": "^2.10.2",
"hardhat-dependency-compiler": "^1.1.3",
"hardhat-deploy": "^0.11.12",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 02902bea8..2ecb8c158 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -321,6 +321,9 @@ importers:
ethers:
specifier: ^5.7.2
version: 5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)
+ glob:
+ specifier: ^11.0.0
+ version: 11.0.0
hardhat:
specifier: ^2.10.2
version: 2.22.4(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.33)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)
@@ -462,48 +465,42 @@ importers:
tar-fs:
specifier: ^2.1.1
version: 2.1.1
- viem:
- specifier: ^2.21.37
- version: 2.21.40(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8)
wait-on:
specifier: ^6.0.1
version: 6.0.1
- .yalc/@ensdomains/ensjs:
+ .yalc/@ensdomains/thorin:
dependencies:
- '@adraffy/ens-normalize':
- specifier: 1.10.1
- version: 1.10.1
- '@ensdomains/address-encoder':
- specifier: 1.1.1
- version: 1.1.1
- '@ensdomains/content-hash':
- specifier: 3.1.0-rc.1
- version: 3.1.0-rc.1
- '@ensdomains/dnsprovejs':
- specifier: ^0.5.1
- version: 0.5.1
- abitype:
- specifier: ^1.0.0
- version: 1.0.6(typescript@5.4.5)(zod@3.23.8)
- dns-packet:
- specifier: ^5.3.1
- version: 5.6.1
- graphql:
- specifier: ^16.3.0
- version: 16.8.1
- graphql-request:
- specifier: 6.1.0
- version: 6.1.0(encoding@0.1.13)(graphql@16.8.1)
- pako:
- specifier: ^2.1.0
- version: 2.1.0
+ '@types/jest':
+ specifier: ^29.5.12
+ version: 29.5.12
+ clsx:
+ specifier: ^1.1.1
+ version: 1.2.1
+ focus-visible:
+ specifier: ^5.2.0
+ version: 5.2.1
+ jest-babel:
+ specifier: ^1.0.1
+ version: 1.0.1(babel-core@7.0.0-bridge.0(@babel/core@7.24.6))
+ lodash:
+ specifier: ^4.17.21
+ version: 4.17.21
+ react:
+ specifier: ^18.2.0
+ version: 18.3.1
+ react-dom:
+ specifier: ^18.2.0
+ version: 18.3.1(react@18.3.1)
+ react-transition-state:
+ specifier: ^2.1.1
+ version: 2.2.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ styled-components:
+ specifier: ^5.3.6
+ version: 5.3.11(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)
ts-pattern:
- specifier: ^5.4.0
- version: 5.5.0
- viem:
- specifier: ^2.9.2
- version: 2.21.40(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8)
+ specifier: ^4.3.0
+ version: 4.3.0
packages:
@@ -2103,6 +2100,7 @@ packages:
'@humanwhocodes/config-array@0.11.14':
resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==}
engines: {node: '>=10.10.0'}
+ deprecated: Use @eslint/config-array instead
'@humanwhocodes/module-importer@1.0.1':
resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
@@ -2110,6 +2108,7 @@ packages:
'@humanwhocodes/object-schema@2.0.3':
resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==}
+ deprecated: Use @eslint/object-schema instead
'@ianvs/prettier-plugin-sort-imports@4.2.1':
resolution: {integrity: sha512-NKN1LVFWUDGDGr3vt+6Ey3qPeN/163uR1pOPAlkWpgvAqgxQ6kSdUf1F0it8aHUtKRUzEGcK38Wxd07O61d7+Q==}
@@ -3775,6 +3774,7 @@ packages:
'@walletconnect/sign-client@2.11.1':
resolution: {integrity: sha512-s3oKSx6/F5X2WmkV1jfJImBFACf9Km5HpTb+n5q+mobJVpUQw/clvoVyIrNNppLhm1V1S/ylHXh0qCrDppDpCA==}
+ deprecated: Reliability and performance greatly improved - please see https://github.com/WalletConnect/walletconnect-monorepo/releases
'@walletconnect/time@1.0.2':
resolution: {integrity: sha512-uzdd9woDcJ1AaBZRhqy5rNC9laqWGErfc4dxA9a87mPdKOgWMD85mcFo9dIYIts/Jwocfwn07EC6EzclKubk/g==}
@@ -5564,6 +5564,7 @@ packages:
ethereum-bloom-filters@1.1.0:
resolution: {integrity: sha512-J1gDRkLpuGNvWYzWslBQR9cDV4nd4kfvVTE/Wy4Kkm4yb3EYRSlyi0eB/inTsSTTVyA0+HyzHgbr95Fn/Z1fSw==}
+ deprecated: do not use this package use package versions above as this can miss some topics
ethereum-cryptography@0.1.3:
resolution: {integrity: sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==}
@@ -6000,6 +6001,11 @@ packages:
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
hasBin: true
+ glob@11.0.0:
+ resolution: {integrity: sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==}
+ engines: {node: 20 || >=22}
+ hasBin: true
+
glob@7.1.7:
resolution: {integrity: sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==}
deprecated: Glob versions prior to v9 are no longer supported
@@ -6690,6 +6696,16 @@ packages:
jackspeak@3.4.3:
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
+ jackspeak@4.0.2:
+ resolution: {integrity: sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==}
+ engines: {node: 20 || >=22}
+
+ jest-babel@1.0.1:
+ resolution: {integrity: sha512-hZp74w14Rbv9zqkyrVEUBiGpjE3DvG8qmMDI5qblVhDe5TCjDzJYOHGUcousz+tJrTtHB1im29xS3cZUyhLnAg==}
+ deprecated: jest-babel is outdated and useless now, try to move to babel-jest package
+ peerDependencies:
+ babel-core: '>= 5.5.0 < 6'
+
jest-canvas-mock@2.5.2:
resolution: {integrity: sha512-vgnpPupjOL6+L5oJXzxTxFrlGEIbHdZqFU+LFNdtLxZ3lRDCl17FlTMM7IatoRQkrcyOTMlDinjUguqmQ6bR2A==}
@@ -7041,6 +7057,10 @@ packages:
resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==}
engines: {node: 14 || >=16.14}
+ lru-cache@11.0.2:
+ resolution: {integrity: sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==}
+ engines: {node: 20 || >=22}
+
lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
@@ -7291,6 +7311,10 @@ packages:
minimalistic-crypto-utils@1.0.1:
resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==}
+ minimatch@10.0.1:
+ resolution: {integrity: sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==}
+ engines: {node: 20 || >=22}
+
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
@@ -7934,6 +7958,10 @@ packages:
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
engines: {node: '>=16 || 14 >=14.18'}
+ path-scurry@2.0.0:
+ resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==}
+ engines: {node: 20 || >=22}
+
path-to-regexp@0.1.7:
resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==}
@@ -8389,6 +8417,12 @@ packages:
react: ^18.2.0
react-dom: ^18.2.0
+ react-transition-state@2.2.0:
+ resolution: {integrity: sha512-D3EyLku1Sdxrxq26Fo4Jh0q1BLEFQfDOxKKiSuyqWH84+hM6y0Guc0hcW2IXMXY5l5gQCgkOQ9y90xx6mNoj5w==}
+ peerDependencies:
+ react: ^18.2.0
+ react-dom: ^18.2.0
+
react-universal-interface@0.6.2:
resolution: {integrity: sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw==}
peerDependencies:
@@ -12530,7 +12564,6 @@ snapshots:
'@jest/expect-utils@29.7.0':
dependencies:
jest-get-type: 29.6.3
- optional: true
'@jest/fake-timers@29.7.0':
dependencies:
@@ -14251,7 +14284,6 @@ snapshots:
dependencies:
expect: 29.7.0
pretty-format: 29.7.0
- optional: true
'@types/js-cookie@2.2.7': {}
@@ -15468,6 +15500,17 @@ snapshots:
transitivePeerDependencies:
- '@babel/core'
+ babel-plugin-styled-components@2.1.4(@babel/core@7.24.6)(styled-components@5.3.11(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)):
+ dependencies:
+ '@babel/helper-annotate-as-pure': 7.24.6
+ '@babel/helper-module-imports': 7.24.6
+ '@babel/plugin-syntax-jsx': 7.24.6(@babel/core@7.24.6)
+ lodash: 4.17.21
+ picomatch: 2.3.1
+ styled-components: 5.3.11(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)
+ transitivePeerDependencies:
+ - '@babel/core'
+
babel-plugin-transform-flow-enums@0.0.2(@babel/core@7.24.6):
dependencies:
'@babel/plugin-syntax-flow': 7.24.7(@babel/core@7.24.6)
@@ -16437,8 +16480,7 @@ snapshots:
devtools-protocol@0.0.1286932: {}
- diff-sequences@29.6.3:
- optional: true
+ diff-sequences@29.6.3: {}
diff@4.0.2: {}
@@ -17332,7 +17374,6 @@ snapshots:
jest-matcher-utils: 29.7.0
jest-message-util: 29.7.0
jest-util: 29.7.0
- optional: true
exponential-backoff@3.1.1: {}
@@ -17752,6 +17793,15 @@ snapshots:
package-json-from-dist: 1.0.0
path-scurry: 1.11.1
+ glob@11.0.0:
+ dependencies:
+ foreground-child: 3.3.0
+ jackspeak: 4.0.2
+ minimatch: 10.0.1
+ minipass: 7.1.2
+ package-json-from-dist: 1.0.0
+ path-scurry: 2.0.0
+
glob@7.1.7:
dependencies:
fs.realpath: 1.0.0
@@ -18549,6 +18599,14 @@ snapshots:
optionalDependencies:
'@pkgjs/parseargs': 0.11.0
+ jackspeak@4.0.2:
+ dependencies:
+ '@isaacs/cliui': 8.0.2
+
+ jest-babel@1.0.1(babel-core@7.0.0-bridge.0(@babel/core@7.24.6)):
+ dependencies:
+ babel-core: 7.0.0-bridge.0(@babel/core@7.24.6)
+
jest-canvas-mock@2.5.2:
dependencies:
cssfontparser: 1.2.1
@@ -18560,7 +18618,6 @@ snapshots:
diff-sequences: 29.6.3
jest-get-type: 29.6.3
pretty-format: 29.7.0
- optional: true
jest-environment-node@29.7.0:
dependencies:
@@ -18579,7 +18636,6 @@ snapshots:
jest-diff: 29.7.0
jest-get-type: 29.6.3
pretty-format: 29.7.0
- optional: true
jest-message-util@29.7.0:
dependencies:
@@ -19005,6 +19061,8 @@ snapshots:
lru-cache@10.2.2: {}
+ lru-cache@11.0.2: {}
+
lru-cache@5.1.1:
dependencies:
yallist: 3.1.1
@@ -19364,6 +19422,10 @@ snapshots:
minimalistic-crypto-utils@1.0.1: {}
+ minimatch@10.0.1:
+ dependencies:
+ brace-expansion: 2.0.1
+
minimatch@3.1.2:
dependencies:
brace-expansion: 1.1.11
@@ -20041,6 +20103,11 @@ snapshots:
lru-cache: 10.2.2
minipass: 7.1.2
+ path-scurry@2.0.0:
+ dependencies:
+ lru-cache: 11.0.2
+ minipass: 7.1.2
+
path-to-regexp@0.1.7: {}
path-to-regexp@6.2.2: {}
@@ -20524,6 +20591,11 @@ snapshots:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
+ react-transition-state@2.2.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+
react-universal-interface@0.6.2(react@18.3.1)(tslib@2.6.2):
dependencies:
react: 18.3.1
@@ -21428,6 +21500,24 @@ snapshots:
transitivePeerDependencies:
- '@babel/core'
+ styled-components@5.3.11(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1):
+ dependencies:
+ '@babel/helper-module-imports': 7.24.6
+ '@babel/traverse': 7.24.6(supports-color@5.5.0)
+ '@emotion/is-prop-valid': 1.2.2
+ '@emotion/stylis': 0.8.5
+ '@emotion/unitless': 0.7.5
+ babel-plugin-styled-components: 2.1.4(@babel/core@7.24.6)(styled-components@5.3.11(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1))
+ css-to-react-native: 3.2.0
+ hoist-non-react-statics: 3.3.2
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ react-is: 18.3.1
+ shallowequal: 1.1.0
+ supports-color: 5.5.0
+ transitivePeerDependencies:
+ - '@babel/core'
+
styled-jsx@5.1.1(@babel/core@7.24.6)(react@18.3.1):
dependencies:
client-only: 0.0.1
diff --git a/public/locales/en/address.json b/public/locales/en/address.json
index 485de57c3..71349824b 100644
--- a/public/locales/en/address.json
+++ b/public/locales/en/address.json
@@ -3,15 +3,11 @@
"title": "{{address}} on ENS",
"description": "All names for {{address}} on the Ethereum Name Service"
},
- "addressDetails": "Address Details",
- "nameCount": "{{count}} names",
- "filter": "Filter",
"view": "View",
"noProfile": {
"title": "No primary name set",
"message": "This wallet needs to set a primary name to create a profile"
},
- "noResults": "No names found",
"errors": {
"names": "Could not find address"
}
diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index e2c54a1af..889e7ec41 100644
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -8,7 +8,6 @@
"action": {
"cancel": "Cancel",
"change": "Change",
- "upload": "Upload",
"close": "Close",
"save": "Save",
"edit": "Edit",
@@ -19,23 +18,18 @@
"send": "Send",
"next": "Next",
"add": "Add",
- "set": "Set",
"search": "Search",
"back": "Back",
"continue": "Continue",
"confirm": "Confirm",
"remove": "Remove",
- "sign": "Sign",
- "reset": "Reset",
"restart": "Restart",
- "transfer": "Transfer",
"tryAgain": "Try Again",
"done": "Done",
"burnSelected": "Burn Selected",
"extend": "Extend",
"delete": "Delete",
"sort": "Sort",
- "check": "Check",
"claim": "Claim",
"remindMe": "Remind Me",
"begin": "Begin",
@@ -43,9 +37,7 @@
"skip": "Skip",
"changeNetwork": "Change Network",
"understand": "I understand",
- "feedback": "Feedback",
"enterEmail": "Enter your email",
- "migrate": "Migrate",
"setToSelf": "Set to self",
"editRoles": "Edit roles",
"setReminder": "Set reminder",
@@ -83,7 +75,6 @@
"bounty": "Bug Bounty",
"terms": "Terms of Use",
"privacy": "Privacy Policy",
- "back": "Back",
"oldApp": "Old ENS App",
"language": "Language",
"currency": "Currency",
@@ -95,7 +86,6 @@
"myProfile": "Profile",
"disconnect": "Disconnect",
"connect": "Connect",
- "noPrimaryName": "No Primary Name",
"viewProfile": "View Profile",
"viewDetails": "View Details",
"register": "Register"
@@ -108,13 +98,10 @@
"expiry": "Expiry",
"parent": "Parent",
"noParent": "No parent",
- "registrant": "Registrant",
- "controller": "Controller",
"dnsOwner": "DNS Owner",
"owner": "Owner",
"notOwned": "Not Owned",
"manager": "Manager",
- "all": "All Names",
"copy": "Copy name",
"yourPrimaryName": "Your primary name",
"expiresInYears_one": "Expires in {{count}} year",
@@ -146,12 +133,8 @@
"expiresInHours_other": "Grace period expires in {{count}} hours",
"expiresInHours_zero": "Grace period expires in less than an hour"
},
- "extend": "Extend",
- "send": "Send",
- "transfer": "Transfer",
"sendManager": "Send Manager",
- "sendOwner": "Send Owner",
- "renew": "Renew {{name}}"
+ "sendOwner": "Send Owner"
},
"address": {
"label": "Address",
@@ -167,16 +150,10 @@
"createdAt": "Creation date"
},
"errors": {
- "noResults": "No results found",
"emailRequired": "Email is required",
"emailInvalid": "Invalid email address",
"invalidAddress": "Invalid address",
"addressRequired": "Address required",
- "indexingErrors": {
- "title": "Error syncing data",
- "message": "This data may be out of date. Please use caution.",
- "message_datetime": "This data was last update at {{datetime}} and may be out of date. Please use caution"
- },
"networkError": {
"title": "Error syncing data",
"message": "This data may be out of date. Please use caution.",
@@ -231,20 +208,14 @@
"removeVerificationRecord": "Remove verification record"
},
"info": {
- "sendName": "Set the controller and registrant of the name",
"migrateProfile": "Set existing records on new resolver",
"migrateProfileWithReset": "Set existing records on new resolver",
- "migrateProfileWithSync": "Set existing records on new resolver",
- "migrateProfileWithEthAddress": "Set existing records and eth address on new resolver",
"wrapName": "Wrap the name",
"updateResolver": "Change resolver to",
- "updateProfile": "Update records on existing resolver",
"setPrimaryName": "Set the primary name for your address",
"updateEthAddress": "Update ETH address to this address",
"updateEthAddressOnLatestResolver": "Update ETH address on latest resolver",
"testSendName": "Set the controller and registrant of the name",
- "createSubname": "Create a new subname on the name",
- "burnFuses": "Burn the chosen permissions until name expiry",
"commitName": "Start timer to register name",
"approveNameWrapper": "Approve the NameWrapper to manage your names",
"fuses": {
@@ -344,11 +315,6 @@
"label": "Name search",
"placeholder": "Search for a name",
"emptyText": "Type a name or address to search...",
- "address": "Address",
- "errors": {
- "tooShort": "Name too short",
- "invalid": "Invalid format for name"
- },
"status": {
"registered": "Registered",
"gracePeriod": "Grace Period",
@@ -357,19 +323,11 @@
"notOwned": "Not Owned",
"notImported": "Not Imported",
"short": "Too Short",
- "default": "Invalid",
- "invalid": "Invalid",
- "unsupportedTLD": "Not Supported",
"imported": "Imported",
"owned": "Owned",
"offChain": "View"
}
},
- "selectableInput": {
- "add": "Add",
- "placeholder": "Select an option",
- "empty": "No options found"
- },
"testnetFaucet": {
"explanation": "Each address on {{ testnet }} can claim {{ amount }} {{ ticker }} to test out the ENS manager app, as well as any other testnet usage.",
"note": "It may take a few minutes to show up in your wallet."
@@ -391,10 +349,6 @@
"title": "manager",
"description": "An address or contract that can change the profile, settings, and profile managers."
},
- "profile-editor": {
- "title": "Profile editor",
- "description": "An address that can change the profile."
- },
"eth-record": {
"title": "ETH record",
"description": "The address that this name points to."
diff --git a/public/locales/en/dnssec.json b/public/locales/en/dnssec.json
index 3e8c31367..c9ec5e577 100644
--- a/public/locales/en/dnssec.json
+++ b/public/locales/en/dnssec.json
@@ -10,7 +10,6 @@
},
"status": {
"checking": "Checking...",
- "error": "An error occurred while checking",
"secondsAgo": "Last checked seconds ago",
"aWhileAgo": "Last checked a while ago",
"minutesAgo_one": "Last checked {{count}} minute ago",
diff --git a/public/locales/en/names.json b/public/locales/en/names.json
index 897be727d..dbc1cb190 100644
--- a/public/locales/en/names.json
+++ b/public/locales/en/names.json
@@ -1,11 +1,5 @@
{
"title": "Names",
- "subtitle": {
- "start": "All the names held by",
- "this": "this",
- "your": "your",
- "wallet": "wallet"
- },
"empty": "No names found for this address",
"sortTypes": {
"expiryDate": "Expiry date",
diff --git a/public/locales/en/profile.json b/public/locales/en/profile.json
index 49baa8dfe..b484bbeb6 100644
--- a/public/locales/en/profile.json
+++ b/public/locales/en/profile.json
@@ -4,15 +4,12 @@
"description": "{{name}}'s profile on the Ethereum Name Service"
},
"title": "Profile",
- "yourWallet": "Your wallet",
"yourProfile": "Your profile",
"accounts": "Accounts",
"addresses": "Addresses",
"otherRecords": "Other Records",
"verifications": "Verifications",
- "editProfile": "Edit Profile",
"ownership": "Ownership",
- "viewDetails": "View Details",
"banner": {
"empty": {
"title": "Personalize your profile",
@@ -30,15 +27,11 @@
"actions": {
"setAsPrimaryName": {
"label": "Set as primary name",
- "title": "Set your primary name",
"description": "To set your primary name to this name, the ETH address must match this address."
},
"deleteSubname": {
"label": "Delete subname"
},
- "extend": {
- "label": "Extend name"
- },
"editProfile": {
"label": "Edit profile"
},
@@ -105,9 +98,9 @@
"parent-owner": "The owner of this name's parent ({{parent}}).",
"dns-owner": "The owner of this name, as set by DNS records. The owner cannot change the profile.",
"manager": "The address that can change the profile, settings and profile editors.",
+ "eth-record": "The address that will receive funds sent to this name on mainnet.",
"profile-editor": "An addres that can only change the profile",
"subname-manager": "An address or contract responsible for subname renewals.",
- "eth-record": "The address that will receive funds sent to this name on mainnet.",
"grace-period": "A 90 day grace window after expiration, when the name can still be extended but not re-registered.",
"contract-address": "The address of the contract that enables Wrapped ENS names.",
"namewrapper": "The contract that enables Wrapped ENS names."
@@ -122,12 +115,10 @@
"revokedLabel": "Revoked {{date}}",
"grantedLabel": "Granted {{date}}",
"role": {
- "manager": "manager",
"owner": "owner",
"parent": "parent"
},
"ownership": {
- "title": "Ownership Permissions",
"parentCanControl": {
"label": "This name can be controlled by its parent ({{parent}})",
"list": {
@@ -166,14 +157,9 @@
"ownerCannotChange": {
"label": "The owner of this name cannot change permissions",
"list": {
- "title": "The owner cannot:",
- "item1": "Revoke permissions",
- "item2": "Change or burn custom fuses"
+ "title": "The owner cannot:"
}
},
- "unwrapWarning": {
- "message": "Unwrap permissions must be revoked on the parent ({{parent}}) before giving up parent control.
Go to parent permissions"
- },
"action": {
"giveUpControl": "Give up parent control",
"revokePermission": "Revoke permission"
@@ -183,13 +169,11 @@
"permissions": {
"canExtendExpiry": {
"label": "Can extend expiry",
- "description": "This names permission expiry can be extended by the {{managerOrOwner}}.",
- "description_date": "This names permission expiry ({{date}}) can be extended by the {{managerOrOwner}}."
+ "description": "This names permission expiry can be extended by the {{managerOrOwner}}."
},
"cannotExtendExpiry": {
"label": "Cannot extend expiry",
- "description": "This names permission expiry cannot be extended by the manager.",
- "description_date": "This names permission expiry ({{date}}) cannot be extended by the manager."
+ "description": "This names permission expiry cannot be extended by the manager."
}
},
"manager": "manager",
@@ -199,11 +183,6 @@
}
},
"nameChangePermissions": {
- "title": "Name Change Permissions",
- "subtitle": {
- "parent-can-change": "The parent of this name ({{parent}}) can change settings on this name.",
- "owner-can-change": "The owner of this name can change settings on this name."
- },
"permissions": {
"canUnwrap": {
"label": "Can unwrap this name",
@@ -259,39 +238,22 @@
}
}
},
- "fuses": {
- "name": "Fuses"
- },
"more": {
"name": "More",
"resolver": {
- "label": "Resolver",
- "custom": "Custom",
- "latest": "Latest",
- "outdated": "Outdated",
- "wildcard": "Wildcard",
- "version": "Version",
- "etherscan": "Etherscan"
+ "label": "Resolver"
},
"fuses": {
- "label": "Fuses",
"info": "You must burn 'Can unwrap' in order to burn other fuses",
- "callToAction": "Please wrap your name to unlock this feature",
"burnFormTitle": "Burn Permissions",
"burned": "Burned",
"permissions": {
- "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",
- "PARENT_CANNOT_CONTROL": "Parent can control",
- "CAN_EXTEND_EXPIRY": "Cannot extend expiry",
- "IS_DOT_ETH": "Is not .eth"
+ "CANNOT_CREATE_SUBDOMAIN": "Can create subdomains"
}
},
"ownership": {
@@ -300,7 +262,6 @@
"dnsOwnerWarning": {
"isDnsOwner": "You cannot make changes to this name because you are the DNS Owner, but not the Manager. You can sync the manager to fix this.",
"isManager": "You are the Manager but not DNS Owner of this name. DNS names can be reclaimed by the DNS Owner at any time. You can send this name to the Owner, or update the DNS record to match.",
- "refreshDNS": "Refresh DNS",
"syncManager": "Sync manager"
}
},
@@ -309,7 +270,6 @@
"noToken": "No token",
"hex": "hex",
"decimal": "decimal",
- "wrapper": "wrapper",
"nameWrapper": "Name Wrapper",
"unwrappedText": " Wrapping your name gives it new features and functionality, however some functionality on your name will change. Please make sure you understand these changes before wrapping your name.",
"wrapName": "Wrap Name",
@@ -326,8 +286,6 @@
}
},
"misc": {
- "registered": "Registered",
- "expires": "Expires",
"bankless": {
"title": "Bankless Reminders",
"enterEmail": "Receive Bankless Reminders through Email, PUSH, XMTP, Blockscan Chat, and Mailchain.",
@@ -346,29 +304,14 @@
}
},
"details": {
- "title": "Name Details",
- "notWrapped": "Not wrapped",
"sendName": {
- "title": "Send Name",
- "description": "Sending a name will give the new address control over it",
- "learnMore": "Learn more about name ownership.",
- "makeOwner": "Make owner",
- "makeOwnerDescription": "The owner can send to a new owner and change the manager",
- "makeManager": "Make manager",
- "makeManagerDescription": "The manager can change and set records.",
"inputPlaceholder": "Enter an Ethereum address or ENS name",
"transferSubname": "Transfer Name",
"transferController": "Transfer Controller"
},
"wrap": {
"startTitle": "Upgrade your name",
- "resumeTitle": "Resume your upgrade",
- "completeTitle": "Upgrade complete",
- "description": "Upgrading or \"wrapping\" your name gives it new features.",
- "startLabel": "Upgrade",
- "resumeLabel": "Resume Upgrade",
- "boxTitle": "Unlock new features",
- "boxDescription": "Upgrading your name unlocks the latest functionality of ENS"
+ "description": "Upgrading or \"wrapping\" your name gives it new features."
},
"descriptions": {
"owner": "Owns and controls the name",
@@ -378,7 +321,6 @@
},
"tabs": {
"records": {
- "label": "Records",
"text": "Text",
"contentHash": "Content Hash",
"noContentHash": "No Content Hash",
@@ -388,11 +330,8 @@
"editRecordsDisabled": "Current resolver is not compatible with wrapped names"
},
"subnames": {
- "label": "Subnames",
- "loading": "Loading subnames",
"empty": "No subnames have been added",
"noResults": "No results",
- "noMoreResults": "No more results",
"setProfile": "Set Profile",
"addSubname": {
"title": "Subnames let you create additional names from your existing name.",
@@ -438,10 +377,6 @@
"label": "Content Hash",
"placeholder": "e.g. ipfs://"
},
- "publicKey": {
- "label": "Public Key",
- "placeholder": "e.g. pub_1231231231231"
- },
"abi": {
"label": "ABI",
"placeholder": "Enter an ABI here..."
diff --git a/public/locales/en/register.json b/public/locales/en/register.json
index cb260cb11..14b8b6b3e 100644
--- a/public/locales/en/register.json
+++ b/public/locales/en/register.json
@@ -8,7 +8,6 @@
"transactionFees": "Transaction fees",
"temporaryPremium": "Temporary premium",
"total": "Estimated total",
- "totalPaid": "Total paid",
"expiry": "Name expires"
},
"error": {
@@ -31,11 +30,6 @@
"profile": {
"title": "Create your profile",
"title2": "Edit your profile",
- "default": "Default",
- "advanced": "Advanced",
- "permissions": "Permissions",
- "resolver": "Resolver",
- "visibilityMessage": "Your profile information will be stored on the blockchain. Anything you add will be publicly visible.",
"addProfile": "Add profile fields",
"addMore": "Add more to profile",
"options": {
@@ -171,7 +165,6 @@
"notEnoughEth": "Not enough ETH in wallet",
"creditOrDebit": "Credit or debit card",
"additionalFee": "Additional processing fee",
- "pendingMoonpayTransaction": "Your MoonPay transaction is processing. This may take up to two minutes. You can check your progress from the confirmation email you recieved.",
"failedMoonpayTransaction": "Your MoonPay transaction has failed. Please try again or choose the 'Ethereum' payment method.",
"ethereum": "Ethereum",
"processing": "Processing",
diff --git a/public/locales/en/settings.json b/public/locales/en/settings.json
index 004ef568f..569f4333a 100644
--- a/public/locales/en/settings.json
+++ b/public/locales/en/settings.json
@@ -1,9 +1,6 @@
{
"title": "Settings",
"section": {
- "wallet": {
- "title": "Wallet"
- },
"transaction": {
"title": "Transactions",
"noRecentTransactions": "No recent transactions",
diff --git a/public/locales/en/transactionFlow.json b/public/locales/en/transactionFlow.json
index 6cead2d48..7d05f22c6 100644
--- a/public/locales/en/transactionFlow.json
+++ b/public/locales/en/transactionFlow.json
@@ -32,73 +32,6 @@
"action": "Sign and Upload"
}
}
- },
- "general": {
- "label": "General",
- "name": {
- "label": "Nickname",
- "placeholder": "John Smith"
- },
- "url": {
- "label": "Website",
- "placeholder": "https://ens.domains"
- },
- "location": {
- "label": "Location",
- "placeholder": "Metaverse"
- },
- "description": {
- "label": "Short Bio",
- "placeholder": "I'm a yield farmer from rural Tennessee."
- }
- },
- "accounts": {
- "label": "Accounts",
- "addAccount": "Add account",
- "noOptions": "No account options available",
- "placeholder": {
- "default": "Add username here",
- "com.twitter": "e.g. nicksdjohnson",
- "com.github": "e.g. arachnid",
- "com.discord": "e.g. nickjohnson",
- "org.telegram": "e.g. nicksdjohnson",
- "email": "e.g. hello@example.com"
- }
- },
- "address": {
- "label": "Address",
- "addAddress": "Add address",
- "noOptions": "No address options available",
- "placeholder": {
- "default": "Add address here",
- "eth": "0xD9hbQK...",
- "bnb": "0xD9hbQK...",
- "btc": "3FZbgi29...",
- "ltc": "3FZbgi29...",
- "sol": "D4kA7VzHnmV...",
- "dot": "1D4kA7VxH...",
- "doge": "DFabcd12..."
- }
- },
- "contentHash": {
- "label": "Website",
- "addContentHash": "Add decentralized website",
- "placeholder": {
- "default": "Add url here",
- "ipfs": "ipfs://...",
- "skynet": "sia://...",
- "arweave": "ar://...",
- "swarm": "bzz://...",
- "onion": "onion3://..."
- }
- },
- "other": {
- "label": "Other",
- "addRecord": "Add record",
- "createRecord": "Type a record name...",
- "placeholder": {
- "default": "Add a value here..."
- }
}
},
"warningOverlay": {
@@ -110,8 +43,7 @@
},
"invalidResolver": {
"title": "Unauthorised resolver",
- "subtitle": "You do not have permission to set records for this resolver. You will need to update to the latest resolver to edit your profile.",
- "action": "Update resolver"
+ "subtitle": "You do not have permission to set records for this resolver. You will need to update to the latest resolver to edit your profile."
},
"migrateProfileSelector": {
"title": "Profile",
@@ -134,8 +66,7 @@
},
"noResolver": {
"title": "No resolver set",
- "subtitle": "A resolver needs to be set before editing your profile.",
- "action": "Update"
+ "subtitle": "A resolver needs to be set before editing your profile."
},
"resetProfile": {
"title": "Reset profile",
@@ -172,9 +103,6 @@
"title": "Keep current profile",
"subtitle": "Unselecting this will reset your profile."
}
- },
- "links": {
- "learnMoreResolvers": "Learn more about resolvers"
}
},
"intro": {
@@ -197,13 +125,11 @@
"latestLabel": "Use latest resolver",
"etherscan": "Etherscan",
"latestMessage": "You are on the latest resolver",
- "customLabel": "Custom resolver",
- "customPlaceholder": "Enter custom resolver address"
+ "customLabel": "Custom resolver"
},
"editRoles": {
"views": {
"main": {
- "title": "Edit roles",
"noneSet": "None set"
},
"editRole": {
@@ -227,17 +153,11 @@
},
"invoice": {
"extension": "{{time}} extension",
- "transaction": "Transaction fee",
- "total": "Estimated total"
+ "transaction": "Transaction fee"
},
"bannerMsg": "Extending for multiple years will save money on network costs by avoiding yearly transactions.",
"gasLimitError": "Not enough ETH in wallet"
},
- "transferProfile": {
- "title": "Transfer Profile",
- "message1": "Before upgrading, you can transfer your current profile if you'd like, otherwise it will be reset when you upgrade.",
- "message2": "Would you like to transfer your current profile?"
- },
"revokePermissions": {
"views": {
"revokeWarning": {
@@ -269,14 +189,6 @@
"max": "Date must be less than {{date}}"
}
},
- "revokeAdditional": {
- "title": "Revoke permissions",
- "subtitle": "Would you like to revoke permissions on this name before giving up ownership? This can only be done now.",
- "action": {
- "title": "Revoke additional permissions",
- "description": "Unless you are also the manager, you will not be able to revoke additional permissions after this."
- }
- },
"revokeUnwrap": {
"title": "Revoke permissions",
"subtitle": "You are required to revoke this permission before others can be revoked. This prevents permissions from being removed.",
@@ -286,7 +198,6 @@
},
"revokePermissions": {
"title": "Revoke permissions",
- "unwrapSubtitle": "Select this to enable the others",
"fuses": {
"CAN_EXTEND_EXPIRY": "Grant permission to: Extend expiry.",
"CANNOT_UNWRAP": "Revoke permission to: Unwrap this name",
@@ -421,14 +332,10 @@
"costValue": "{{value}} + fees",
"warning": "Extending this name will not give you ownership of it",
"newExpiry": "New expiry: {{date}}"
- },
- "deleteSubname": {
- "warning": "Hello out there"
}
},
"intro": {
"migrateAndUpdateResolver": {
- "title": "Action Required",
"heading": "The edits you want to save to your profile require that you upgrade your resolver.",
"link": "Learn more about resolver upgrades",
"warning": "Note that if you cancel this at any point your changes will not be saved."
@@ -455,8 +362,5 @@
"title": "Delete Subname",
"description": "Deleting this subname requires multiple transactions"
}
- },
- "errors": {
- "duplicateKey": "{{value}} is a duplicate key"
}
}
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..e8b7fce13
--- /dev/null
+++ b/scripts/scan-locales.mjs
@@ -0,0 +1,77 @@
+import fs from 'fs'
+
+import * as glob from 'glob'
+
+import { BASE_LOCALE, getLocaleData, getLocalePaths } from './locale-utils.mjs'
+
+const whitelist = [
+ /^unit./,
+ /(one|other)$/,
+ /^steps.profile.options.groups/,
+ /^transaction.itemLabel/,
+ /_zero$/,
+ /(transaction.dialog.sent|transaction.dialog.complete|transaction.dialog.failed)/,
+]
+
+const baseLocale = getLocalePaths(BASE_LOCALE)
+
+const { keys: translationKeys, namespaces } = getLocaleData(baseLocale)
+
+const translationKeysWoNs = translationKeys.map((key) =>
+ key.replace(new RegExp(`^(${namespaces.join('|')}).`), ''),
+)
+
+// 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 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
+}
+
+// Find unused translation keys
+function findUnusedKeys() {
+ const usedKeys = searchForTranslationKeysInCode()
+
+ const unusedKeys = []
+
+ for (const key of translationKeysWoNs) {
+ if (!usedKeys.has(key) && !whitelist.some((regex) => regex.test(key))) {
+ unusedKeys.push(key)
+ }
+ }
+
+ unusedKeys.forEach((key) => {
+ console.log(key)
+ })
+
+ if (unusedKeys.length) {
+ console.log(`PROBABLY ${unusedKeys.length} UNSED KEYS:`)
+ } else {
+ console.log('NO UNSED KEYS')
+ }
+}
+
+findUnusedKeys()
diff --git a/src/components/@atoms/ErrorScreen.tsx b/src/components/@atoms/ErrorScreen.tsx
index 6ae6d0fac..e39ef64e6 100644
--- a/src/components/@atoms/ErrorScreen.tsx
+++ b/src/components/@atoms/ErrorScreen.tsx
@@ -1,6 +1,7 @@
import Link from 'next/link'
import { Trans, useTranslation } from 'react-i18next'
import styled, { css } from 'styled-components'
+import { match } from 'ts-pattern'
import { AlertSVG, QuestionCircleSVG, Typography } from '@ensdomains/thorin'
@@ -47,8 +48,20 @@ const LinkWrapper = ({ children }: { children?: React.ReactNode }) => (
type ErrorType = 'not-found' | 'application-error'
+const getErrorTranslationKey = (errorType: ErrorType): { title: string; message: string } =>
+ match(errorType)
+ .with('not-found', () => ({
+ title: 'not-found.title',
+ message: 'not-found.message',
+ }))
+ .with('application-error', () => ({
+ title: 'application-error.title',
+ message: 'application-error.message',
+ }))
+ .otherwise(() => ({ title: '', message: '' }))
+
const ErrorScreen = ({ errorType }: { errorType: ErrorType }) => {
- const { t, ready } = useTranslation('error', { keyPrefix: errorType })
+ const { t, ready } = useTranslation('error')
if (!ready) {
return null
@@ -61,11 +74,12 @@ const ErrorScreen = ({ errorType }: { errorType: ErrorType }) => {
) : (
)}
- {t('title')}
+ {t(getErrorTranslationKey(errorType).title)}
+
,
// eslint-disable-next-line jsx-a11y/control-has-associated-label, jsx-a11y/anchor-has-content
diff --git a/src/components/@molecules/BurnFuses/BurnFusesContent.tsx b/src/components/@molecules/BurnFuses/BurnFusesContent.tsx
index 895934566..6d8217178 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,18 +82,32 @@ 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', { keyPrefix: 'tabs.more.fuses' })
+ const { t } = useTranslation('profile')
+
+ const translationKey = getPermissionTranslationKey(permission)
return (
- {t(`permissions.${permission}`)}
+ {t(translationKey)}
{isBurned && (
- {t('burned')}
+ {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 = ({
@@ -171,12 +186,11 @@ const BurnFusesContent = ({
canUnsetFuse = false,
returnObject,
}: PropsWithReturnArray | PropsWithReturnObject) => {
- const { t } = useTranslation('profile', { keyPrefix: 'tabs.more' })
- const { t: tc } = useTranslation()
+ const { t } = useTranslation('profile')
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 +202,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(`fuses.permissions.${key}`))
+ const permissions = selectedFuses.map((key) => t(getPermissionTranslationKey(key)))
onSubmit(selectedFuses, permissions)
}
@@ -214,12 +228,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')}
>
) : (
@@ -230,10 +244,10 @@ const BurnFusesContent = ({
{Object.entries(_fuseData).map(([key, value]) => (
))}
@@ -241,7 +255,7 @@ const BurnFusesContent = ({
diff --git a/src/components/@molecules/DateSelection/DateSelection.tsx b/src/components/@molecules/DateSelection/DateSelection.tsx
index 3c19320e2..07a2e2ec7 100644
--- a/src/components/@molecules/DateSelection/DateSelection.tsx
+++ b/src/components/@molecules/DateSelection/DateSelection.tsx
@@ -118,7 +118,9 @@ export const DateSelection = ({
data-testid="date-selection"
onClick={() => onChangeDurationType?.(durationType === 'years' ? 'date' : 'years')}
>
- {t(`calendar.pick_by_${durationType === 'date' ? 'years' : 'date'}`, { ns: 'common' })}
+ {t(durationType === '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 141416219..7e9823848 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/ProfileEditor/Avatar/AvatarNFT.tsx b/src/components/@molecules/ProfileEditor/Avatar/AvatarNFT.tsx
index 118034268..0c2d48ee5 100644
--- a/src/components/@molecules/ProfileEditor/Avatar/AvatarNFT.tsx
+++ b/src/components/@molecules/ProfileEditor/Avatar/AvatarNFT.tsx
@@ -437,9 +437,9 @@ export const AvatarNFT = ({
(address !== ethAddress && (
))}
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(
diff --git a/src/components/@molecules/TransactionDialogManager/DisplayItems.tsx b/src/components/@molecules/TransactionDialogManager/DisplayItems.tsx
index a33e334ee..3feb80f01 100644
--- a/src/components/@molecules/TransactionDialogManager/DisplayItems.tsx
+++ b/src/components/@molecules/TransactionDialogManager/DisplayItems.tsx
@@ -299,6 +299,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.test.tsx b/src/components/Notifications.test.tsx
index 8c329f18a..3199f05e3 100644
--- a/src/components/Notifications.test.tsx
+++ b/src/components/Notifications.test.tsx
@@ -65,12 +65,12 @@ describe('Notifications', () => {
const { rerender } = render()
expect(screen.queryByTestId('toast-desktop')).not.toBeInTheDocument()
- cb({ ...mockData[0], status: 'confirmed1' as any })
- cb({ ...mockData[1], status: 'confirmed2' as any })
+ cb({ ...mockData[0], status: 'confirmed' as any })
+ cb({ ...mockData[1], status: 'confirmed' as any })
rerender()
- await waitFor(() => screen.queryByText('transaction.status.confirmed1.notifyTitle'), {
+ await waitFor(() => screen.queryByText('transaction.status.confirmed.notifyTitle'), {
timeout: 500,
}).then((el) => expect(el).toBeInTheDocument())
@@ -78,7 +78,7 @@ describe('Notifications', () => {
vi.advanceTimersByTime(8350)
})
- await waitFor(() => screen.queryByText('transaction.status.confirmed2.notifyTitle'), {
+ await waitFor(() => screen.queryByText('transaction.status.confirmed.notifyTitle'), {
timeout: 500,
}).then((el) => expect(el).toBeInTheDocument())
})
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/ProfileSnippet.tsx b/src/components/ProfileSnippet.tsx
index 148dc7b1c..ea2a9a8e8 100644
--- a/src/components/ProfileSnippet.tsx
+++ b/src/components/ProfileSnippet.tsx
@@ -2,6 +2,7 @@ import { useSearchParams } from 'next/navigation'
import { useEffect, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import styled, { css } from 'styled-components'
+import { match } from 'ts-pattern'
import { useAccount } from 'wagmi'
import { Button, mq, NametagSVG, Tag, Typography } from '@ensdomains/thorin'
@@ -173,6 +174,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,
@@ -184,7 +194,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
@@ -228,6 +238,8 @@ export const ProfileSnippet = ({
}, [isConnected, available, renew, name, canSelfExtend])
const ActionButton = useMemo(() => {
+ const translationKey = getButtonTranslationKey(button)
+
if (button === 'extend')
return (
)
if (button === 'register')
@@ -252,7 +264,7 @@ export const ProfileSnippet = ({
size="small"
colorStyle="accentSecondary"
>
- {t(`wallet.${button}`)}
+ {t(translationKey)}
)
if (button === 'viewProfile')
@@ -262,7 +274,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/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..1560935fe 100644
--- a/src/components/pages/import/[name]/steps/EnableDnssec.tsx
+++ b/src/components/pages/import/[name]/steps/EnableDnssec.tsx
@@ -24,8 +24,7 @@ export const EnableDnssec = ({
dispatch: Dispatch
selected: SelectedItemProperties
}) => {
- const { t } = useTranslation('dnssec', { keyPrefix: 'steps.enableDnssec' })
- const { t: tc } = useTranslation('common')
+ const { t } = useTranslation('dnssec')
const {
data: isDnsSecEnabled,
@@ -40,20 +39,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')}
>
@@ -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 97ddbee25..b876768c4 100644
--- a/src/components/pages/import/[name]/steps/SelectImportType.tsx
+++ b/src/components/pages/import/[name]/steps/SelectImportType.tsx
@@ -153,8 +153,7 @@ export const SelectImportType = ({
item: DnsImportReducerDataItem
selected: SelectedItemProperties
}) => {
- const { t } = useTranslation('dnssec', { keyPrefix: 'steps.selectType' })
- const { t: tc } = useTranslation('common')
+ const { t } = useTranslation('dnssec')
const { address } = useAccount()
const chainId = useChainId()
@@ -195,11 +194,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 +215,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 +244,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"
@@ -257,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 e72dbc4ad..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,
@@ -71,8 +84,7 @@ export const VerifyOffchainOwnership = ({
dispatch: Dispatch
selected: SelectedItemProperties
}) => {
- const { t } = useTranslation('dnssec', { keyPrefix: 'steps.verifyOwnership' })
- const { t: tc } = useTranslation('common')
+ const { t } = useTranslation('dnssec')
const { address, chainId } = selected
const isConnected = !!address
@@ -93,20 +105,21 @@ 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 (
- {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 +136,7 @@ export const VerifyOffchainOwnership = ({
/>
{t('status.mismatching.error.offchain')}
+
+ {t('steps.verifyOwnership.status.mismatching.error.offchain')}
+
)
}
/>
@@ -160,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 d27f5715a..9934b982a 100644
--- a/src/components/pages/import/[name]/steps/onchain/ImportTransaction.tsx
+++ b/src/components/pages/import/[name]/steps/onchain/ImportTransaction.tsx
@@ -102,8 +102,7 @@ export const ImportTransaction = ({
item: DnsImportReducerDataItem
selected: SelectedItemProperties
}) => {
- const { t } = useTranslation('dnssec', { keyPrefix: 'steps.transaction' })
- const { t: tc } = useTranslation('common')
+ const { t } = useTranslation('dnssec')
const { data: gasPrice } = useGasPrice()
const { userConfig, setCurrency } = useUserConfig()
@@ -206,15 +205,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,19 +230,19 @@ export const ImportTransaction = ({
/>
- {t('estimatedNetworkCost')}
+ {t('steps.transaction.estimatedNetworkCost')}
- {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' })}
)}
@@ -247,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 17bfba790..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,
@@ -66,8 +77,7 @@ export const VerifyOnchainOwnership = ({
dispatch: Dispatch
selected: SelectedItemProperties
}) => {
- const { t } = useTranslation('dnssec', { keyPrefix: 'steps.verifyOwnership' })
- const { t: tc } = useTranslation('common')
+ const { t } = useTranslation('dnssec')
const {
data: dnsOwner,
@@ -93,20 +103,23 @@ 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 (
- {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 +132,7 @@ export const VerifyOnchainOwnership = ({
@@ -143,7 +156,9 @@ export const VerifyOnchainOwnership = ({
}
statusHelperElement={
dnsOwnerStatus === 'mismatching' && (
- {t('status.mismatching.error.onchain')}
+
+ {t('steps.verifyOwnership.status.mismatching.error.onchain')}
+
)
}
/>
@@ -155,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('action.importWithoutOwnership')
- : tc('action.next')}
+ ? t('steps.verifyOwnership.action.importWithoutOwnership')
+ : 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 68847c87f..2791c9fae 100644
--- a/src/components/pages/profile/[name]/Profile.tsx
+++ b/src/components/pages/profile/[name]/Profile.tsx
@@ -69,6 +69,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
@@ -258,7 +268,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]/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 2ca36cfad..d83ed93cd 100644
--- a/src/components/pages/profile/[name]/registration/steps/Pricing/Pricing.tsx
+++ b/src/components/pages/profile/[name]/registration/steps/Pricing/Pricing.tsx
@@ -93,7 +93,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..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)}
@@ -263,7 +265,11 @@ const DNSOwnerSection = ({
return (
- {t(`tabs.more.ownership.dnsOwnerWarning.${canSend ? 'isManager' : 'isDnsOwner'}`)}
+ {t(
+ canSend
+ ? `tabs.more.ownership.dnsOwnerWarning.isManager`
+ : `tabs.more.ownership.dnsOwnerWarning.isDnsOwner`,
+ )}