Skip to content

Commit f0900b1

Browse files
authored
Merge pull request #94 from alan412/consolidate_ui_strings
Prepare for consolidating UI strings
2 parents 189543b + a865f63 commit f0900b1

File tree

7 files changed

+225
-32
lines changed

7 files changed

+225
-32
lines changed

docs/design.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,6 @@
3333
## Components
3434
* Every actuator is a component
3535
* Every sensor is a component
36+
37+
# Localization
38+
* This page describes easy ways to use the i18n support that I added: https://phrase.com/blog/posts/localizing-react-apps-with-i18next/

package-lock.json

Lines changed: 139 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@
1010
"@types/react-dom": "^19.0.2",
1111
"antd": "^5.22.1",
1212
"blockly": "^11.1.1",
13+
"i18next": "^24.2.2",
14+
"i18next-http-backend": "^3.0.2",
1315
"jszip": "^3.10.1",
1416
"lucide-react": "^0.460.0",
1517
"re-resizable": "^6.10.1",
1618
"react": "^18.3.1",
1719
"react-dom": "^18.3.1",
20+
"react-i18next": "^15.4.1",
1821
"react-syntax-highlighter": "^15.6.1",
1922
"web-vitals": "^2.1.4"
2023
},

public/locales/en/translation.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"mechanism_delete": "Delete Project",
3+
"mechanism_rename": "Rename Project",
4+
"mechanism_copy": "Copy Project",
5+
"opmode_delete": "Delete Project",
6+
"opmode_rename": "Rename Project",
7+
"opmode_copy": "Copy Project",
8+
"project_delete": "Delete Project",
9+
"project_rename": "Rename Project",
10+
"project_copy": "Copy Project",
11+
"fail_list_modules": "Failed to load the list of modules."
12+
}

src/App.tsx

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ import {
3535
UploadOutlined,
3636
} from '@ant-design/icons';
3737

38+
import { useTranslation } from "react-i18next";
39+
3840
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
3941
import { dracula } from 'react-syntax-highlighter/dist/esm/styles/prism';
4042

@@ -117,6 +119,8 @@ const App: React.FC = () => {
117119
const PURPOSE_RENAME_MODULE = 'RenameModule';
118120
const PURPOSE_COPY_MODULE = 'CopyModule';
119121

122+
const { t } = useTranslation();
123+
120124
const ignoreEffect = () => {
121125
if (!import.meta.env.MODE || import.meta.env.MODE === 'development') {
122126
// Development mode.
@@ -209,9 +213,9 @@ const App: React.FC = () => {
209213
} catch (e) {
210214
console.log('Failed to load the list of modules. Caught the following error...');
211215
console.log(e);
212-
setAlertErrorMessage('Failed to load the list of modules.');
216+
setAlertErrorMessage(t("fail_list_modules"));
213217
setAlertErrorVisible(true);
214-
reject(new Error('Failed to load the list of modules.'));
218+
reject(new Error(t("fail_list_modules")));
215219
}
216220
});
217221
};
@@ -308,17 +312,17 @@ const App: React.FC = () => {
308312

309313
if (module != null) {
310314
if (module.moduleType == commonStorage.MODULE_TYPE_PROJECT) {
311-
setRenameTooltip('Rename Project');
312-
setCopyTooltip('Copy Project');
313-
setDeleteTooltip('Delete Project');
315+
setRenameTooltip(t("project_rename"));
316+
setCopyTooltip(t("project_copy"));
317+
setDeleteTooltip(t("project_delete"));
314318
} else if (module.moduleType == commonStorage.MODULE_TYPE_MECHANISM) {
315-
setRenameTooltip('Rename Mechanism');
316-
setCopyTooltip('Copy Mechanism');
317-
setDeleteTooltip('Delete Mechanism');
319+
setRenameTooltip(t("mechanism_rename"));
320+
setCopyTooltip(t("mechanism_copy"));
321+
setDeleteTooltip(t("mechanism_delete"));
318322
} else if (module.moduleType == commonStorage.MODULE_TYPE_OPMODE) {
319-
setRenameTooltip('Rename OpMode');
320-
setCopyTooltip('Copy OpMode');
321-
setDeleteTooltip('Delete OpMode');
323+
setRenameTooltip(t("opmode_rename"));
324+
setCopyTooltip(t("opmode_copy"));
325+
setDeleteTooltip(t("opmode_delete"));
322326
}
323327

324328
storage.saveEntry('mostRecentModulePath', currentModulePath);

src/i18n/config.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Porpoiseful LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
/**
19+
* @fileoverview Configuration for i18n
20+
* @author [email protected] (Alan Smith)
21+
*
22+
* This is mostly borrowed with a few adaptations from
23+
* https://phrase.com/blog/posts/localizing-react-apps-with-i18next/
24+
*/
25+
import i18n from "i18next";
26+
import HttpApi from "i18next-http-backend";
27+
import { initReactI18next } from "react-i18next";
28+
29+
i18n
30+
// Add backend as a plugin so we can load the needed translation at runtime
31+
.use(HttpApi)
32+
// Add React bindings as a plugin so it will re-render when language changes
33+
.use(initReactI18next)
34+
.init({
35+
// Config options
36+
37+
// Specifies the default language (locale) used
38+
// when a user visits our site for the first time.
39+
lng: "en",
40+
41+
// Fallback locale used when a translation is missing.
42+
fallbackLng: "en",
43+
44+
// Normally, we want `escapeValue: true` as it ensures that i18next escapes any code in
45+
// translation messages, safeguarding against XSS (cross-site scripting) attacks. However,
46+
// React does this escaping itself, so we turn it off in i18next.
47+
interpolation: {
48+
escapeValue: false,
49+
},
50+
});
51+
52+
export default i18n;

src/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from 'react';
22
import ReactDOM from 'react-dom/client';
33
import './index.css';
44
import App from './App';
5+
import "./i18n/config.ts"
56
import reportWebVitals from './reportWebVitals';
67

78
const root = ReactDOM.createRoot(

0 commit comments

Comments
 (0)