From cf02ea2572b19a1e5b7f784866e13888d1aa7a87 Mon Sep 17 00:00:00 2001 From: Timon <39559178+Its-treason@users.noreply.github.com> Date: Mon, 26 Aug 2024 07:39:50 +0200 Subject: [PATCH] feat: Replace dictonary i18n with react-i18next (#2915) --- contributing.md | 1 + package-lock.json | 116 +++++++++++++++--- packages/bruno-app/package.json | 4 +- .../components/Preferences/Support/index.js | 14 +-- .../bruno-app/src/components/Welcome/index.js | 28 ++--- packages/bruno-app/src/dictionaries/en.js | 16 --- packages/bruno-app/src/dictionaries/index.js | 5 - packages/bruno-app/src/i18n/index.js | 24 ++++ .../bruno-app/src/i18n/translation/en.json | 20 +++ packages/bruno-app/src/pages/_app.js | 17 ++- packages/bruno-app/src/pages/index.js | 1 + .../src/providers/Dictionary/index.js | 28 ----- 12 files changed, 173 insertions(+), 101 deletions(-) delete mode 100644 packages/bruno-app/src/dictionaries/en.js delete mode 100644 packages/bruno-app/src/dictionaries/index.js create mode 100644 packages/bruno-app/src/i18n/index.js create mode 100644 packages/bruno-app/src/i18n/translation/en.json delete mode 100644 packages/bruno-app/src/providers/Dictionary/index.js diff --git a/contributing.md b/contributing.md index 2bc02f3ea..3835c2cb6 100644 --- a/contributing.md +++ b/contributing.md @@ -34,6 +34,7 @@ Libraries we use - Schema Validation - Yup - Request Client - axios - Filesystem Watcher - chokidar +- i18n - i18next ### Dependencies diff --git a/package-lock.json b/package-lock.json index 7e3f5fd04..01f9bf787 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50,7 +50,6 @@ }, "node_modules/@ampproject/remapping": { "version": "2.3.0", - "dev": true, "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -671,7 +670,6 @@ }, "node_modules/@babel/compat-data": { "version": "7.25.2", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -679,7 +677,6 @@ }, "node_modules/@babel/core": { "version": "7.25.2", - "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", @@ -743,7 +740,6 @@ }, "node_modules/@babel/helper-compilation-targets": { "version": "7.25.2", - "dev": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.25.2", @@ -758,7 +754,6 @@ }, "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { "version": "5.1.1", - "dev": true, "license": "ISC", "dependencies": { "yallist": "^3.0.2" @@ -766,7 +761,6 @@ }, "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { "version": "3.1.1", - "dev": true, "license": "ISC" }, "node_modules/@babel/helper-create-class-features-plugin": { @@ -845,7 +839,6 @@ }, "node_modules/@babel/helper-module-transforms": { "version": "7.25.2", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.24.7", @@ -912,7 +905,6 @@ }, "node_modules/@babel/helper-simple-access": { "version": "7.24.7", - "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.24.7", @@ -950,7 +942,6 @@ }, "node_modules/@babel/helper-validator-option": { "version": "7.24.8", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -971,7 +962,6 @@ }, "node_modules/@babel/helpers": { "version": "7.25.0", - "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.25.0", @@ -3654,6 +3644,37 @@ "node": ">=12" } }, + "node_modules/@n8n/vm2": { + "version": "3.9.25", + "resolved": "https://registry.npmjs.org/@n8n/vm2/-/vm2-3.9.25.tgz", + "integrity": "sha512-qoGLFzyHBW7HKpwXkl05QKsIh3GkDw6lOiTOWYlUDnOIQ1b7EgM+O5EMjrMGy7r+kz52+Q7o6GLxBIcxVI8rEg==", + "license": "MIT", + "peer": true, + "dependencies": { + "acorn": "^8.7.0", + "acorn-walk": "^8.2.0" + }, + "bin": { + "vm2": "bin/vm2" + }, + "engines": { + "node": ">=18.10", + "pnpm": ">=9.6" + } + }, + "node_modules/@n8n/vm2/node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/@next/env": { "version": "12.3.3", "license": "MIT" @@ -4730,7 +4751,6 @@ }, "node_modules/@types/linkify-it": { "version": "5.0.0", - "dev": true, "license": "MIT" }, "node_modules/@types/lodash": { @@ -4739,7 +4759,6 @@ }, "node_modules/@types/markdown-it": { "version": "12.2.3", - "dev": true, "license": "MIT", "dependencies": { "@types/linkify-it": "*", @@ -4748,7 +4767,6 @@ }, "node_modules/@types/mdurl": { "version": "2.0.0", - "dev": true, "license": "MIT" }, "node_modules/@types/minimatch": { @@ -6222,7 +6240,6 @@ }, "node_modules/browserslist": { "version": "4.23.3", - "dev": true, "funding": [ { "type": "opencollective", @@ -7221,7 +7238,6 @@ }, "node_modules/convert-source-map": { "version": "2.0.0", - "dev": true, "license": "MIT" }, "node_modules/cookie": { @@ -8461,7 +8477,6 @@ }, "node_modules/electron-to-chromium": { "version": "1.5.11", - "dev": true, "license": "ISC" }, "node_modules/electron-util": { @@ -9423,7 +9438,6 @@ }, "node_modules/gensync": { "version": "1.0.0-beta.2", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -10109,6 +10123,15 @@ "node": ">= 12" } }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, "node_modules/html-webpack-plugin": { "version": "5.6.0", "dev": true, @@ -10254,6 +10277,29 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/i18next": { + "version": "23.14.0", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.14.0.tgz", + "integrity": "sha512-Y5GL4OdA8IU2geRrt2+Uc1iIhsjICdHZzT9tNwQ3TVqdNzgxHToGCKf/TPRP80vTCAP6svg2WbbJL+Gx5MFQVA==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, "node_modules/iconv-corefoundation": { "version": "1.1.7", "license": "MIT", @@ -13140,7 +13186,6 @@ }, "node_modules/node-releases": { "version": "2.0.18", - "dev": true, "license": "MIT" }, "node_modules/node-vault": { @@ -15225,6 +15270,28 @@ "react-dom": ">=16" } }, + "node_modules/react-i18next": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.0.1.tgz", + "integrity": "sha512-NwxLqNM6CLbeGA9xPsjits0EnXdKgCRSS6cgkgOdNcPXqL+1fYNl8fBg1wmnnHvFy812Bt4IWTPE9zjoPmFj3w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.8", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 23.2.3", + "react": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/react-inspector": { "version": "6.0.2", "license": "MIT", @@ -16134,7 +16201,6 @@ }, "node_modules/semver": { "version": "6.3.1", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -17597,7 +17663,6 @@ }, "node_modules/update-browserslist-db": { "version": "1.1.0", - "dev": true, "funding": [ { "type": "opencollective", @@ -17866,6 +17931,15 @@ "node": ">=0.4.0" } }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/vscode-languageserver-types": { "version": "3.17.5", "license": "MIT" @@ -18376,6 +18450,7 @@ "graphql": "^16.6.0", "graphql-request": "^3.7.0", "httpsnippet": "^3.0.6", + "i18next": "^23.14.0", "idb": "^7.0.0", "immer": "^9.0.15", "jsesc": "^3.0.2", @@ -18405,6 +18480,7 @@ "react-dom": "18.2.0", "react-github-btn": "^1.4.0", "react-hot-toast": "^2.4.0", + "react-i18next": "^15.0.1", "react-inspector": "^6.0.2", "react-pdf": "^7.5.1", "react-redux": "^7.2.6", diff --git a/packages/bruno-app/package.json b/packages/bruno-app/package.json index d94b75212..7f1dfe7c5 100644 --- a/packages/bruno-app/package.json +++ b/packages/bruno-app/package.json @@ -37,14 +37,15 @@ "graphql": "^16.6.0", "graphql-request": "^3.7.0", "httpsnippet": "^3.0.6", + "i18next": "^23.14.0", "idb": "^7.0.0", "immer": "^9.0.15", "jsesc": "^3.0.2", "jshint": "^2.13.6", "json5": "^2.2.3", "jsonc-parser": "^3.2.1", - "jsonpath-plus": "^7.2.0", "jsonlint": "^1.6.3", + "jsonpath-plus": "^7.2.0", "know-your-http-well": "^0.5.0", "lodash": "^4.17.21", "markdown-it": "^13.0.2", @@ -66,6 +67,7 @@ "react-dom": "18.2.0", "react-github-btn": "^1.4.0", "react-hot-toast": "^2.4.0", + "react-i18next": "^15.0.1", "react-inspector": "^6.0.2", "react-pdf": "^7.5.1", "react-redux": "^7.2.6", diff --git a/packages/bruno-app/src/components/Preferences/Support/index.js b/packages/bruno-app/src/components/Preferences/Support/index.js index 5e1b0dacc..01aab9e21 100644 --- a/packages/bruno-app/src/components/Preferences/Support/index.js +++ b/packages/bruno-app/src/components/Preferences/Support/index.js @@ -1,10 +1,10 @@ import React from 'react'; import { IconSpeakerphone, IconBrandTwitter, IconBrandGithub, IconBrandDiscord, IconBook } from '@tabler/icons'; import StyledWrapper from './StyledWrapper'; -import { useDictionary } from 'providers/Dictionary/index'; +import { useTranslation } from 'react-i18next'; const Support = () => { - const { dictionary } = useDictionary(); + const { t } = useTranslation(); return ( @@ -12,31 +12,31 @@ const Support = () => {
- {dictionary.documentation} + {t('COMMON.DOCUMENTATION')}
- {dictionary.reportIssues} + {t('COMMON.REPORT_ISSUES')}
- {dictionary.discord} + {t('COMMON.DISCORD')}
- {dictionary.gitHub} + {t('COMMON.GITHUB')}
- {dictionary.twitter} + {t('COMMON.TWITTER')}
diff --git a/packages/bruno-app/src/components/Welcome/index.js b/packages/bruno-app/src/components/Welcome/index.js index 54f7b5378..0ec4c1245 100644 --- a/packages/bruno-app/src/components/Welcome/index.js +++ b/packages/bruno-app/src/components/Welcome/index.js @@ -1,6 +1,7 @@ import { useState } from 'react'; import toast from 'react-hot-toast'; import { useDispatch } from 'react-redux'; +import { useTranslation } from 'react-i18next'; import { openCollection, importCollection } from 'providers/ReduxStore/slices/collections/actions'; import { IconBrandGithub, IconPlus, IconDownload, IconFolders, IconSpeakerphone, IconBook } from '@tabler/icons'; @@ -9,11 +10,10 @@ import CreateCollection from 'components/Sidebar/CreateCollection'; import ImportCollection from 'components/Sidebar/ImportCollection'; import ImportCollectionLocation from 'components/Sidebar/ImportCollectionLocation'; import StyledWrapper from './StyledWrapper'; -import { useDictionary } from 'providers/Dictionary/index'; const Welcome = () => { const dispatch = useDispatch(); - const { dictionary } = useDictionary(); + const { t } = useTranslation(); const [importedCollection, setImportedCollection] = useState(null); const [importedTranslationLog, setImportedTranslationLog] = useState({}); const [createCollectionModalOpen, setCreateCollectionModalOpen] = useState(false); @@ -22,7 +22,7 @@ const Welcome = () => { const handleOpenCollection = () => { dispatch(openCollection()).catch( - (err) => console.log(err) && toast.error(dictionary.errorWhileOpeningCollection) + (err) => console.log(err) && toast.error(t('WELCOME.COLLECTION_OPEN_ERROR')) ); }; @@ -40,12 +40,12 @@ const Welcome = () => { .then(() => { setImportCollectionLocationModalOpen(false); setImportedCollection(null); - toast.success(dictionary.collectionImportedSuccessfully); + toast.success(t('WELCOME.COLLECTION_IMPORT_SUCCESS')); }) .catch((err) => { setImportCollectionLocationModalOpen(false); console.error(err); - toast.error(dictionary.errorWhileImportingCollection); + toast.error(t('WELCOME.COLLECTION_IMPORT_ERROR')); }); }; @@ -68,45 +68,45 @@ const Welcome = () => {
bruno
-
{dictionary.aboutBruno}
+
{t('WELCOME.ABOUT_BRUNO')}
-
{dictionary.collections}
+
{t('COMMON.COLLECTIONS')}
setCreateCollectionModalOpen(true)}> - {dictionary.createCollection} + {t('WELCOME.CREATE_COLLECTION')}
- {dictionary.openCollection} + {t('WELCOME.OPEN_COLLECTION')}
setImportCollectionModalOpen(true)}> - {dictionary.importCollection} + {t('WELCOME.IMPORT_COLLECTION')}
-
Links
+
{t('WELCOME.LINKS')}
- {dictionary.documentation} + {t('COMMON.DOCUMENTATION')}
- {dictionary.reportIssues} + {t('COMMON.REPORT_ISSUES')}
- {dictionary.gitHub} + {t('COMMON.GITHUB')}
diff --git a/packages/bruno-app/src/dictionaries/en.js b/packages/bruno-app/src/dictionaries/en.js deleted file mode 100644 index a9ff316cd..000000000 --- a/packages/bruno-app/src/dictionaries/en.js +++ /dev/null @@ -1,16 +0,0 @@ -export default { - aboutBruno: 'Opensource IDE for exploring and testing APIs', - collections: 'Collections', - createCollection: 'Create Collection', - openCollection: 'Open Collection', - importCollection: 'Import Collection', - documentation: 'Documentation', - reportIssues: 'Report Issues', - gitHub: 'GitHub', - collectionImportedSuccessfully: 'Collection imported successfully', - errorWhileOpeningCollection: 'An error occurred while opening the collection', - errorWhileImportingCollection: - 'An error occurred while importing the collection. Check the logs for more information.', - discord: 'Discord', - twitter: 'Twitter' -}; diff --git a/packages/bruno-app/src/dictionaries/index.js b/packages/bruno-app/src/dictionaries/index.js deleted file mode 100644 index fb5f797dc..000000000 --- a/packages/bruno-app/src/dictionaries/index.js +++ /dev/null @@ -1,5 +0,0 @@ -import en from './en.js'; - -export const dictionaries = { - en -}; diff --git a/packages/bruno-app/src/i18n/index.js b/packages/bruno-app/src/i18n/index.js new file mode 100644 index 000000000..26e89695f --- /dev/null +++ b/packages/bruno-app/src/i18n/index.js @@ -0,0 +1,24 @@ +import i18n from 'i18next'; +import { initReactI18next } from 'react-i18next'; +import translationEn from './translation/en.json'; + +const resources = { + en: { + translation: translationEn, + }, +}; + +i18n + .use(initReactI18next) // passes i18n down to react-i18next + .init({ + resources, + lng: 'en', // Use "en" as the default language. "cimode" can be used to debug / show translation placeholder + + ns: 'translation', // Use translation as the default Namespace that will be loaded by default + + interpolation: { + escapeValue: false // react already safes from xss + } + }); + +export default i18n; diff --git a/packages/bruno-app/src/i18n/translation/en.json b/packages/bruno-app/src/i18n/translation/en.json new file mode 100644 index 000000000..7dda41e42 --- /dev/null +++ b/packages/bruno-app/src/i18n/translation/en.json @@ -0,0 +1,20 @@ +{ + "COMMON": { + "COLLECTIONS": "Collections", + "DOCUMENTATION": "Documentation", + "REPORT_ISSUES": "Report Issues", + "GITHUB": "GitHub", + "DISCORD": "Discord", + "TWITTER": "Twitter" + }, + "WELCOME": { + "ABOUT_BRUNO": "Opensource IDE for exploring and testing APIs", + "LINKS": "Links", + "CREATE_COLLECTION": "Create Collection", + "OPEN_COLLECTION": "Open Collection", + "IMPORT_COLLECTION": "Import Collection", + "COLLECTION_IMPORT_SUCCESS": "Collection imported successfully", + "COLLECTION_IMPORT_ERROR": "An error occurred while importing the collection. Check the logs for more information.", + "COLLECTION_OPEN_ERROR": "An error occurred while opening the collection" + } +} diff --git a/packages/bruno-app/src/pages/_app.js b/packages/bruno-app/src/pages/_app.js index 20ff052d7..0f3f553d6 100644 --- a/packages/bruno-app/src/pages/_app.js +++ b/packages/bruno-app/src/pages/_app.js @@ -23,7 +23,6 @@ import '@fontsource/inter/600.css'; import '@fontsource/inter/700.css'; import '@fontsource/inter/800.css'; import '@fontsource/inter/900.css'; -import { DictionaryProvider } from 'providers/Dictionary/index'; function SafeHydrate({ children }) { return
{typeof window === 'undefined' ? null : children}
; @@ -69,15 +68,13 @@ function MyApp({ Component, pageProps }) { - - - - - - - - - + + + + + + + diff --git a/packages/bruno-app/src/pages/index.js b/packages/bruno-app/src/pages/index.js index de969b5a5..08a43dbd4 100644 --- a/packages/bruno-app/src/pages/index.js +++ b/packages/bruno-app/src/pages/index.js @@ -1,6 +1,7 @@ import Head from 'next/head'; import Bruno from './Bruno'; import GlobalStyle from '../globalStyles'; +import '../i18n'; export default function Home() { return ( diff --git a/packages/bruno-app/src/providers/Dictionary/index.js b/packages/bruno-app/src/providers/Dictionary/index.js deleted file mode 100644 index 75a399f27..000000000 --- a/packages/bruno-app/src/providers/Dictionary/index.js +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; -import { useState, useContext } from 'react'; -import { dictionaries } from 'src/dictionaries/index'; - -export const DictionaryContext = React.createContext(); - -const DictionaryProvider = (props) => { - const [language, setLanguage] = useState('en'); - const dictionary = dictionaries[language] ?? dictionaries.en; - - return ( - - <>{props.children} - - ); -}; - -const useDictionary = () => { - const context = useContext(DictionaryContext); - - if (context === undefined) { - throw new Error(`useDictionary must be used within a DictionaryProvider`); - } - - return context; -}; - -export { useDictionary, DictionaryProvider };