From 861c84693298ee8acf6c9a249cf54ac7b06cf1ad Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Fri, 7 Oct 2022 17:15:47 +0530 Subject: [PATCH] feat(ui): initial support for localisation (#8015) update AppBar to use localisation changes --- .../src/main/resources/ui/package.json | 3 ++ .../ui/src/components/app-bar/Appbar.tsx | 12 +++-- .../ui/src/components/nav-bar/NavBar.tsx | 12 +++-- .../src/main/resources/ui/src/index.tsx | 4 ++ .../ui/src/locale/languages/en-us.json | 17 +++++++ .../ui/src/utils/i18next/LocalUtil.ts | 25 ++++++++++ .../ui/src/utils/i18next/i18nextUtil.ts | 33 +++++++++++++ .../src/main/resources/ui/yarn.lock | 48 ++++++++++++++++--- 8 files changed, 137 insertions(+), 17 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json create mode 100644 openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtil.ts create mode 100644 openmetadata-ui/src/main/resources/ui/src/utils/i18next/i18nextUtil.ts diff --git a/openmetadata-ui/src/main/resources/ui/package.json b/openmetadata-ui/src/main/resources/ui/package.json index 4bfa0a24df2..253e76ad495 100644 --- a/openmetadata-ui/src/main/resources/ui/package.json +++ b/openmetadata-ui/src/main/resources/ui/package.json @@ -44,6 +44,8 @@ "fs-extra": "^10.1.0", "html-react-parser": "^1.2.6", "https-browserify": "^1.0.0", + "i18next": "^21.10.0", + "i18next-browser-languagedetector": "^6.1.6", "ieee754": "^1.2.1", "immutable": "^4.0.0-rc.14", "jwt-decode": "^3.1.2", @@ -71,6 +73,7 @@ "react-error-boundary": "^3.1.4", "react-flow-renderer": "^10.3.17", "react-google-login": "^5.2.2", + "react-i18next": "^11.18.6", "react-oidc": "^1.0.3", "react-quill": "^1.3.5", "react-router-dom": "^5.2.0", diff --git a/openmetadata-ui/src/main/resources/ui/src/components/app-bar/Appbar.tsx b/openmetadata-ui/src/main/resources/ui/src/components/app-bar/Appbar.tsx index a2948780d9b..861c7dfc8c4 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/app-bar/Appbar.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/app-bar/Appbar.tsx @@ -18,6 +18,7 @@ import { isEmpty } from 'lodash'; import { observer } from 'mobx-react'; import { Match } from 'Models'; import React, { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { Link, useHistory, useLocation, useRouteMatch } from 'react-router-dom'; import appState from '../../AppState'; import { useAuthContext } from '../../authentication/auth-provider/AuthProvider'; @@ -52,6 +53,7 @@ const cookieStorage = new CookieStorage(); const Appbar: React.FC = (): JSX.Element => { const location = useLocation(); const history = useHistory(); + const { t } = useTranslation(); const { isFirstTimeUser } = useAuth(location.pathname); const { isAuthDisabled, @@ -82,7 +84,7 @@ const Appbar: React.FC = (): JSX.Element => { { name: ( - {`Version ${ + {`${t('label.version')} ${ (version ? version : '?').split('-')[0] }`} @@ -100,7 +102,7 @@ const Appbar: React.FC = (): JSX.Element => { ), }, { - name: `Docs`, + name: t('label.docs'), to: urlGitbookDocs, isOpenNewTab: true, disabled: false, @@ -114,7 +116,7 @@ const Appbar: React.FC = (): JSX.Element => { ), }, { - name: `API`, + name: t('label.api-uppercase'), to: ROUTES.SWAGGER, disabled: false, icon: ( @@ -127,7 +129,7 @@ const Appbar: React.FC = (): JSX.Element => { ), }, { - name: `Slack`, + name: t('label.slack'), to: urlJoinSlack, disabled: false, isOpenNewTab: true, @@ -228,7 +230,7 @@ const Appbar: React.FC = (): JSX.Element => { isText: true, }, { - name: 'Logout', + name: t('label.logout'), to: '', disabled: false, method: onLogoutHandler, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/nav-bar/NavBar.tsx b/openmetadata-ui/src/main/resources/ui/src/components/nav-bar/NavBar.tsx index 75b71e58aa4..b4202457caa 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/nav-bar/NavBar.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/nav-bar/NavBar.tsx @@ -14,6 +14,7 @@ import { Badge, Dropdown, Image, Input, Space } from 'antd'; import { debounce, toString } from 'lodash'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { Link, NavLink, useHistory } from 'react-router-dom'; import AppState from '../../AppState'; import Logo from '../../assets/svg/logo-monogram.svg'; @@ -70,6 +71,7 @@ const NavBar = ({ [AppState.userDetails, AppState.nonSecureUserDetails] ); const history = useHistory(); + const { t } = useTranslation(); const [searchIcon, setSearchIcon] = useState('icon-searchv1'); const [suggestionSearch, setSuggestionSearch] = useState(''); const [hasTaskNotification, setHasTaskNotification] = @@ -239,7 +241,7 @@ const NavBar = ({ to={{ pathname: '/explore/tables', }}> - Explore + {t('label.explore')} - Glossary + {t('label.glossary')} - Tags + {t('label.tags')} - Settings + {t('label.settings')} @@ -281,7 +283,7 @@ const NavBar = ({ className="tw-relative search-grey hover:tw-outline-none focus:tw-outline-none tw-pl-2 tw-pt-2 tw-pb-1.5 tw-ml-4 tw-z-41" data-testid="searchBox" id="searchBox" - placeholder="Search for Tables, Topics, Dashboards, Pipelines and ML Models" + placeholder={t('label.search-global')} style={{ borderRadius: '0.24rem', boxShadow: 'none', diff --git a/openmetadata-ui/src/main/resources/ui/src/index.tsx b/openmetadata-ui/src/main/resources/ui/src/index.tsx index 180dc33c82a..ab6f09d09b8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/index.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/index.tsx @@ -15,6 +15,10 @@ import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; import './styles/index.js'; +import { initLocale } from './utils/i18next/LocalUtil'; + +// Initialize locale +initLocale(); ReactDOM.render( diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json new file mode 100644 index 00000000000..fac484b6597 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json @@ -0,0 +1,17 @@ +{ + "label": { + "docs": "Docs", + "api-uppercase": "API", + "slack": "Slack", + "logout": "Logout", + "version": "Version", + "explore": "Explore", + "glossary": "Glossary", + "tags": "Tags", + "settings": "Settings", + "search-global": "Search for Tables, Topics, Dashboards, Pipelines and ML Models" + }, + "message": {}, + "server": {}, + "url": {} +} diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtil.ts b/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtil.ts new file mode 100644 index 00000000000..5001d956d58 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/utils/i18next/LocalUtil.ts @@ -0,0 +1,25 @@ +/* + * Copyright 2021 Collate + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import i18n from 'i18next'; +import LanguageDetector from 'i18next-browser-languagedetector'; +import { initReactI18next } from 'react-i18next'; +import { getInitOptions } from './i18nextUtil'; + +export const initLocale = (): void => { + // Initialize i18next (language) + i18n + .use(LanguageDetector) // Detects system language + .use(initReactI18next) + .init(getInitOptions()); +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/i18next/i18nextUtil.ts b/openmetadata-ui/src/main/resources/ui/src/utils/i18next/i18nextUtil.ts new file mode 100644 index 00000000000..49f8ba3b7c5 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/utils/i18next/i18nextUtil.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2021 Collate + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { InitOptions } from 'i18next'; +import enUS from '../../locale/languages/en-us.json'; + +// Returns i18next options +export const getInitOptions = (): InitOptions => { + return { + supportedLngs: ['en-US'], + resources: { + 'en-US': { translation: enUS }, + }, + fallbackLng: ['en-US'], + interpolation: { + escapeValue: false, // XSS safety provided by React + }, + missingKeyHandler: (_lngs, _ns, key) => + // eslint-disable-next-line no-console + console.error(`i18next: key not found "${key}"`), + saveMissing: true, // Required for missing key handler + }; +}; diff --git a/openmetadata-ui/src/main/resources/ui/yarn.lock b/openmetadata-ui/src/main/resources/ui/yarn.lock index 45eac75620e..39ea83d34f5 100644 --- a/openmetadata-ui/src/main/resources/ui/yarn.lock +++ b/openmetadata-ui/src/main/resources/ui/yarn.lock @@ -1871,6 +1871,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.14.5", "@babel/runtime@^7.17.2", "@babel/runtime@^7.18.9", "@babel/runtime@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.0.tgz#22b11c037b094d27a8a2504ea4dcff00f50e2259" + integrity sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/runtime@^7.16.3": version "7.16.5" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.5.tgz#7f3e34bf8bdbbadf03fbb7b1ea0d929569c9487a" @@ -1878,13 +1885,6 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.18.9": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.0.tgz#22b11c037b094d27a8a2504ea4dcff00f50e2259" - integrity sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA== - dependencies: - regenerator-runtime "^0.13.4" - "@babel/template@^7.14.5", "@babel/template@^7.3.3", "@babel/template@^7.4.0": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.14.5.tgz#a9bc9d8b33354ff6e55a9c60d1109200a68974f4" @@ -7968,6 +7968,13 @@ html-minifier-terser@^5.0.1: relateurl "^0.2.7" terser "^4.6.3" +html-parse-stringify@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2" + integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg== + dependencies: + void-elements "3.1.0" + html-react-parser@^1.2.6: version "1.2.8" resolved "https://registry.yarnpkg.com/html-react-parser/-/html-react-parser-1.2.8.tgz#6806984a5056417de38fc4b52145bd56b1b32d73" @@ -8124,6 +8131,20 @@ husky@^8.0.1: resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.1.tgz#511cb3e57de3e3190514ae49ed50f6bc3f50b3e9" integrity sha512-xs7/chUH/CKdOCs7Zy0Aev9e/dKOMZf3K1Az1nar3tzlv0jfqnYtu235bstsWTmXOR0EfINrPa97yy4Lz6RiKw== +i18next-browser-languagedetector@^6.1.6: + version "6.1.6" + resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-6.1.6.tgz#c8a13185bc0d445bd9afcc8be5ab8d343bb259b1" + integrity sha512-5ZErazMh/8ZF5bNmJ9OCvSTzMXzefK9vJMtXekXUkwZ6ZX3XUSagWseQgtbWjo8iiZyCP2lxveKwIBikfVRGDg== + dependencies: + "@babel/runtime" "^7.19.0" + +i18next@^21.10.0: + version "21.10.0" + resolved "https://registry.yarnpkg.com/i18next/-/i18next-21.10.0.tgz#85429af55fdca4858345d0e16b584ec29520197d" + integrity sha512-YeuIBmFsGjUfO3qBmMOc0rQaun4mIpGKET5WDwvu8lU7gvwpcariZLNtL0Fzj+zazcHUrlXHiptcFhBMFaxzfg== + dependencies: + "@babel/runtime" "^7.17.2" + iconv-lite@0.4.24, iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -12459,6 +12480,14 @@ react-google-login@^5.2.2: "@types/react" "*" prop-types "^15.6.0" +react-i18next@^11.18.6: + version "11.18.6" + resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.18.6.tgz#e159c2960c718c1314f1e8fcaa282d1c8b167887" + integrity sha512-yHb2F9BiT0lqoQDt8loZ5gWP331GwctHz9tYQ8A2EIEUu+CcEdjBLQWli1USG3RdWQt3W+jqQLg/d4rrQR96LA== + dependencies: + "@babel/runtime" "^7.14.5" + html-parse-stringify "^3.0.1" + react-is@^16.12.0, react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6, react-is@^16.9.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" @@ -15012,6 +15041,11 @@ vfile@^5.0.0: unist-util-stringify-position "^3.0.0" vfile-message "^3.0.0" +void-elements@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" + integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w== + w3c-hr-time@^1.0.1, w3c-hr-time@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"