Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add i18n support #6006

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 22 additions & 0 deletions client/app/i18n/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import i18n from "i18next";
import LanguageDetector from "i18next-browser-languagedetector";
import { initReactI18next } from "react-i18next";
import en from "./locales/en.json";
import zh from "./locales/zh.json";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, with this zh I've often seen it split into two. One for "Simplified Chinese", and one for "Traditional Chinese".

Should we have this named as one of them, to leave room for the other one at some future point?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, with this zh I've often seen it split into two. One for "Simplified Chinese", and one for "Traditional Chinese".

Should we have this named as one of them, to leave room for the other one at some future point?

OK. You are right.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One note is that importing languages like this will add all of them into the bundle, which may not be nice 😅.

It's possibly better to do an async import for those cases. https://www.i18next.com/how-to/add-or-load-translations had a bit of content on this.


Separate thing I was thinking about these days: Should we plan ahead how we will do the translations end-to-end, before committing anything? I don't know much about react-i18next, I've used mostly react-intl, the main difference I saw so far is that the later also has a description for each unformatted string. Considering Redash as an open-source / community effort and how LLMs are today, it may be a good thing to plan a flow which includes automatic generation of the translation files on every release. Then would count on the community only for reviewing/fixing eventual mistakes, in this case the description would be useful for providing context and/or fixing the prompt.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One note is that importing languages like this will add all of them into the bundle, which may not be nice 😅.

It's possibly better to do an async import for those cases. https://www.i18next.com/how-to/add-or-load-translations had a bit of content on this.

Separate thing I was thinking about these days: Should we plan ahead how we will do the translations end-to-end, before committing anything? I don't know much about react-i18next, I've used mostly react-intl, the main difference I saw so far is that the later also has a description for each unformatted string. Considering Redash as an open-source / community effort and how LLMs are today, it may be a good thing to plan a flow which includes automatic generation of the translation files on every release. Then would count on the community only for reviewing/fixing eventual mistakes, in this case the description would be useful for providing context and/or fixing the prompt.

Cool. It's a good idea.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One note is that importing languages like this will add all of them into the bundle, which may not be nice 😅.

It's possibly better to do an async import for those cases. https://www.i18next.com/how-to/add-or-load-translations had a bit of content on this.

Separate thing I was thinking about these days: Should we plan ahead how we will do the translations end-to-end, before committing anything? I don't know much about react-i18next, I've used mostly react-intl, the main difference I saw so far is that the later also has a description for each unformatted string. Considering Redash as an open-source / community effort and how LLMs are today, it may be a good thing to plan a flow which includes automatic generation of the translation files on every release. Then would count on the community only for reviewing/fixing eventual mistakes, in this case the description would be useful for providing context and/or fixing the prompt.

I haven't figured out which platform to host these mapping tables for the time being.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a data point - for a project many years ago - we had the project hosted on Launchpad, so it could make use of the translation infrastructure there.

One really cool benefit of that platform at the time was that if anyone, anywhere had translated the same string for any project on Launchpad (with compatible license) then the string was made available as a potential option.

It saved a lot of time for people, and made it easy to get pretty far with new language translations.

That being said, the company behind Launchpad (Canonical) have done some shady stuff in the past so I'm not really a fan of theirs... 😉

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The possible use of LLM's for generating "first attempt" translations sounds like a useful idea too.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The possible use of LLM's for generating "first attempt" translations sounds like a useful idea too.

Cool!


i18n
.use(initReactI18next)
.use(LanguageDetector)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this automatically enables the translation based on the browser? I think it may be better to leave it disabled until we have more strings translated (or under some feature flag / global settings)

.init({
resources: {
en: { translation: en },
zh: { translation: zh },
},
fallbackLng: "zh",
preload: ["en", "zh"],
interpolation: {
escapeValue: false,
},
});

export default i18n;
3 changes: 3 additions & 0 deletions client/app/i18n/locales/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"dashboard": "dashboard"
}
4 changes: 4 additions & 0 deletions client/app/i18n/locales/zh.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"dashboard": "看板",
"Favorite Dashboards": "收藏的看板"
}
1 change: 1 addition & 0 deletions client/app/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import "@/config";

import ApplicationArea from "@/components/ApplicationArea";
import offlineListener from "@/services/offline-listener";
import "./i18n";

ReactDOM.render(<ApplicationArea />, document.getElementById("application-root"), () => {
offlineListener.init();
Expand Down
5 changes: 4 additions & 1 deletion client/app/pages/home/components/FavoritesList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import LoadingOutlinedIcon from "@ant-design/icons/LoadingOutlined";

import { Dashboard } from "@/services/dashboard";
import { Query } from "@/services/query";
import { useTranslation } from "react-i18next";

export function FavoriteList({ title, resource, itemUrl, emptyState }) {
const [items, setItems] = useState([]);
Expand Down Expand Up @@ -53,13 +54,15 @@ FavoriteList.propTypes = {
FavoriteList.defaultProps = { emptyState: null };

export function DashboardAndQueryFavoritesList() {
const { t } = useTranslation();

return (
<div className="tile">
<div className="t-body tb-padding">
<div className="row home-favorites-list">
<div className="col-sm-6 m-t-20">
<FavoriteList
title="Favorite Dashboards"
title={t("Favorite Dashboards")}
resource={Dashboard}
itemUrl={dashboard => dashboard.url}
emptyState={
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@
"font-awesome": "^4.7.0",
"history": "^4.10.1",
"hoist-non-react-statics": "^3.3.0",
"i18next": "^22.4.15",
"i18next-browser-languagedetector": "^7.0.1",
Comment on lines +61 to +62
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From CI failure it seems it's missing the yarn.lock changes, so you may need to re-run yarn and push it :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From CI failure it seems it's missing the yarn.lock changes, so you may need to re-run yarn and push it :)

I pushed yarn.lock file.

"markdown": "0.5.0",
"material-design-iconic-font": "^2.2.0",
"moment": "^2.19.3",
Expand All @@ -71,6 +73,7 @@
"react-ace": "^9.1.1",
"react-dom": "^16.14.0",
"react-grid-layout": "^0.18.2",
"react-i18next": "^12.2.2",
"react-resizable": "^1.10.1",
"react-virtualized": "^9.21.2",
"sql-formatter": "git+https://github.com/getredash/sql-formatter.git",
Expand Down
22 changes: 22 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8129,6 +8129,20 @@ hyperlinker@^1.0.0:
resolved "https://registry.yarnpkg.com/hyperlinker/-/hyperlinker-1.0.0.tgz#23dc9e38a206b208ee49bc2d6c8ef47027df0c0e"
integrity sha512-Ty8UblRWFEcfSuIaajM34LdPXIhbs1ajEX/BBPv24J+enSVaEVY63xQ6lTO9VRYS5LAoghIG0IDJ+p+IPzKUQQ==

i18next-browser-languagedetector@^7.0.1:
version "7.0.2"
resolved "https://registry.npmmirror.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.0.2.tgz#22d8ed48411750c1a1828ac2031c816a5108e55f"
integrity sha512-5ViaK+gikxfqZ9M3jJ7gJkUzzu/p3HwiqfLoL1bdiL7CUb0IylcTyVLdPaTU3pH5VFWFCiGFuJDg3VkLUikWgg==
dependencies:
"@babel/runtime" "^7.19.4"

i18next@^22.4.15:
version "22.5.1"
resolved "https://registry.npmmirror.com/i18next/-/i18next-22.5.1.tgz#99df0b318741a506000c243429a7352e5f44d424"
integrity sha512-8TGPgM3pAD+VRsMtUMNknRz3kzqwp/gPALrWMsDnmC1mKqJwpWyooQRLMcbTwq8z8YwSmuj+ZYvc+xCuEpkssA==
dependencies:
"@babel/runtime" "^7.20.6"

iconv-lite@0.4.23:
version "0.4.23"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
Expand Down Expand Up @@ -13060,6 +13074,14 @@ react-grid-layout@^0.18.2:
react-draggable "^4.0.0"
react-resizable "^1.9.0"

react-i18next@^12.2.2:
version "12.3.1"
resolved "https://registry.npmmirror.com/react-i18next/-/react-i18next-12.3.1.tgz#30134a41a2a71c61dc69c6383504929aed1c99e7"
integrity sha512-5v8E2XjZDFzK7K87eSwC7AJcAkcLt5xYZ4+yTPDAW1i7C93oOY1dnr4BaQM7un4Hm+GmghuiPvevWwlca5PwDA==
dependencies:
"@babel/runtime" "^7.20.6"
html-parse-stringify "^3.0.1"

react-is@^16.12.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
Expand Down