diff --git a/app/components/Home.tsx b/app/components/Home.tsx
index ab35397..8679746 100644
--- a/app/components/Home.tsx
+++ b/app/components/Home.tsx
@@ -1,4 +1,5 @@
import React, { useState, useEffect } from 'react';
+import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { ipcRenderer } from 'electron';
import routes from '../constants/routes.json';
@@ -8,22 +9,30 @@ import styles from './Home.css';
export default function Home(): JSX.Element {
const [signalingServerPort, setSignalingServerPort] = useState('0000');
+ const { t, i18n } = useTranslation();
// Example of how to get signaling server port from main process in renderer process
// following this practice, you can also get local server ip address
useEffect(() => {
ipcRenderer.on('sending-port-from-main', (event, message) => {
- console.log(message);
setSignalingServerPort(message);
});
ipcRenderer.invoke('get-signaling-server-port');
}, []);
+ const onButtonClick = () => {
+ console.log(t('Language'));
+ };
+
return (
Home
to Counter
{`Signaling server is running on port: ${signalingServerPort}`}
+ {`Locales test ${t('Language')}`}
+
+ CLICK ME!
+
);
}
diff --git a/app/configs/app.config.ts b/app/configs/app.config.ts
new file mode 100644
index 0000000..761115b
--- /dev/null
+++ b/app/configs/app.config.ts
@@ -0,0 +1,5 @@
+export default {
+ languages: ['ru', 'en'],
+ fallbackLng: 'en',
+ namespace: 'translation',
+};
diff --git a/app/configs/i18next.config.client.ts b/app/configs/i18next.config.client.ts
new file mode 100644
index 0000000..4251f1f
--- /dev/null
+++ b/app/configs/i18next.config.client.ts
@@ -0,0 +1,40 @@
+import i18n from 'i18next';
+import { initReactI18next } from 'react-i18next';
+import SyncBackend from 'i18next-sync-fs-backend';
+import { join } from 'path';
+import isDev from 'electron-is-dev';
+import config from './app.config';
+
+const i18nextOptions = {
+ interpolation: {
+ escapeValue: false,
+ },
+ backend: {
+ // path where resources get loaded from
+ loadPath: isDev
+ ? join(__dirname, './locales/{{lng}}/{{ns}}.json')
+ : 'locales/{{lng}}/{{ns}}.json',
+ // path to post missing resources
+ addPath: isDev
+ ? join(__dirname, './locales/{{lng}}/{{ns}}.missing.json')
+ : 'locales/{{lng}}/{{ns}}.json',
+ // jsonIndent to use when storing json files
+ jsonIndent: 2,
+ },
+ saveMissing: true,
+ lng: 'en',
+ fallbackLng: config.fallbackLng,
+ whitelist: config.languages,
+ react: {
+ wait: false,
+ },
+};
+i18n.use(SyncBackend);
+i18n.use(initReactI18next);
+
+// initialize if not already initialized
+if (!i18n.isInitialized) {
+ i18n.init(i18nextOptions);
+}
+
+export default i18n;
diff --git a/app/configs/i18next.config.ts b/app/configs/i18next.config.ts
new file mode 100644
index 0000000..24872c3
--- /dev/null
+++ b/app/configs/i18next.config.ts
@@ -0,0 +1,58 @@
+// const i18n = require('i18next');
+// const i18nextBackend = require('i18next-fs-backend');
+// const { join } = require('path');
+// const config = require('./app.config');
+
+import i18n from 'i18next';
+import i18nextBackend from 'i18next-node-fs-backend';
+import { join } from 'path';
+import isDev from 'electron-is-dev';
+import config from './app.config';
+
+const i18nextOptions = {
+ fallbackLng: config.fallbackLng,
+ lng: 'en',
+ ns: 'translation',
+ defaultNS: 'translation',
+ backend: {
+ // path where resources get loaded from
+ loadPath: isDev
+ ? join(__dirname, '../locales/{{lng}}/{{ns}}.json')
+ : 'locales/{{lng}}/{{ns}}.json',
+ // path to post missing resources
+ addPath: isDev
+ ? join(__dirname, '../locales/{{lng}}/{{ns}}.missing.json')
+ : 'locales/{{lng}}/{{ns}}.json',
+ // jsonIndent to use when storing json files
+ jsonIndent: 2,
+ },
+ interpolation: {
+ escapeValue: false,
+ },
+ saveMissing: true,
+ whitelist: config.languages,
+ react: {
+ wait: false,
+ },
+};
+i18n.use(i18nextBackend);
+// initialize if not already initialized
+if (!i18n.isInitialized) {
+ i18n.init(i18nextOptions);
+ // i18n.init({
+ // lng: 'en',
+ // debug: true,
+ // resources: {
+ // en: {
+ // translation: {
+ // Language: 'Jjdjjdjd',
+ // },
+ // },
+ // },
+ // });
+ console.log('\n\n\n\n INTITIALIZING I18N ----');
+}
+
+console.log(i18n.t('Language'));
+
+export default i18n;
diff --git a/app/index.tsx b/app/index.tsx
index 4e5c01e..2b2af69 100644
--- a/app/index.tsx
+++ b/app/index.tsx
@@ -1,6 +1,10 @@
-import React, { Fragment } from 'react';
+import React, { Fragment, Suspense } from 'react';
import { render } from 'react-dom';
import { AppContainer as ReactHotAppContainer } from 'react-hot-loader';
+// import { ipcRenderer } from 'electron';
+// import { I18nextProvider } from 'react-i18next';
+// import i18n from './configs/i18next.config.client';
+import './configs/i18next.config.client';
import { history, configuredStore } from './store';
import './app.global.css';
@@ -8,12 +12,16 @@ const store = configuredStore();
const AppContainer = process.env.PLAIN_HMR ? Fragment : ReactHotAppContainer;
+// let initialI18nStore = ipcRenderer.sendSync('get-initial-translations');
+
document.addEventListener('DOMContentLoaded', () => {
// eslint-disable-next-line global-require
const Root = require('./containers/Root').default;
render(
-
+
+
+
,
document.getElementById('root')
);
diff --git a/app/locales/en/translation.json b/app/locales/en/translation.json
new file mode 100644
index 0000000..dca41ee
--- /dev/null
+++ b/app/locales/en/translation.json
@@ -0,0 +1,5 @@
+{
+ "Language": "π Language",
+ "ru": "Π ΡΡΡΠΊΠΈΠΉ",
+ "en": "English"
+}
diff --git a/app/locales/en/translation.missing.json b/app/locales/en/translation.missing.json
new file mode 100644
index 0000000..7266a1c
--- /dev/null
+++ b/app/locales/en/translation.missing.json
@@ -0,0 +1,4 @@
+{
+ "ru": "ru",
+ "en": "en"
+}
diff --git a/app/locales/ru/translation.json b/app/locales/ru/translation.json
new file mode 100644
index 0000000..ee636c1
--- /dev/null
+++ b/app/locales/ru/translation.json
@@ -0,0 +1,5 @@
+{
+ "Language": "π Π―Π·ΡΠΊ",
+ "ru": "Π ΡΡΡΠΊΠΈΠΉ",
+ "en": "English"
+}
diff --git a/app/locales/ru/translation.missing.json b/app/locales/ru/translation.missing.json
new file mode 100644
index 0000000..e69de29
diff --git a/app/main.dev.ts b/app/main.dev.ts
index 8ba0a5b..24c93d1 100644
--- a/app/main.dev.ts
+++ b/app/main.dev.ts
@@ -11,14 +11,19 @@
import 'core-js/stable';
import 'regenerator-runtime/runtime';
import path from 'path';
-import { app, BrowserWindow, ipcMain } from 'electron';
+import { app, BrowserWindow, ipcMain, Menu } from 'electron';
import { autoUpdater } from 'electron-updater';
import log from 'electron-log';
+import config from './configs/app.config';
+import i18n from './configs/i18next.config';
import signalingServer from './server/signalingServer';
import MenuBuilder from './menu';
signalingServer.start();
+console.log('\n\n\n\n\n APP PATH');
+console.log(app.getPath('app/locales'));
+
export default class AppUpdater {
constructor() {
log.transports.file.level = 'info';
@@ -28,6 +33,7 @@ export default class AppUpdater {
}
let mainWindow: BrowserWindow | null = null;
+let menuBuilder: MenuBuilder | null = null;
if (process.env.NODE_ENV === 'production') {
const sourceMapSupport = require('source-map-support');
@@ -95,9 +101,20 @@ const createWindow = async () => {
mainWindow = null;
});
- const menuBuilder = new MenuBuilder(mainWindow);
+ menuBuilder = new MenuBuilder(mainWindow, i18n);
menuBuilder.buildMenu();
+ i18n.on('loaded', (loaded) => {
+ i18n.changeLanguage('en');
+ i18n.off('loaded');
+ });
+
+ i18n.on('languageChanged', (lng) => {
+ menuBuilder = new MenuBuilder(mainWindow, i18n);
+ menuBuilder.buildMenu();
+ console.log(`Language changed! ${lng}`);
+ });
+
// Remove this if your app does not use auto updates
// eslint-disable-next-line
new AppUpdater();
@@ -130,10 +147,21 @@ app.on('activate', () => {
// TODO: get locale of app and load appropriate menu texts and app texts( ISO 3166 COUNTRY CODES )
console.log('\n\n\n\n\n\n GETTING OS LOCALE: ');
-console.log(app.getLocaleCountryCode());
+console.log(app.getLocale());
ipcMain.handle('get-signaling-server-port', () => {
console.log('printing port');
console.log(signalingServer.port);
mainWindow.webContents.send('sending-port-from-main', signalingServer.port);
});
+
+ipcMain.on('get-initial-translations', (event, arg) => {
+ i18n.loadLanguages('en', (err, t) => {
+ const initial = {
+ en: {
+ translation: i18n.getResourceBundle('en', config.namespace),
+ },
+ };
+ event.returnValue = initial;
+ });
+});
diff --git a/app/menu.ts b/app/menu.ts
index 56315f1..77fff19 100644
--- a/app/menu.ts
+++ b/app/menu.ts
@@ -6,6 +6,8 @@ import {
MenuItemConstructorOptions,
} from 'electron';
+import config from './configs/app.config';
+
import signalingServer from './server/signalingServer';
interface DarwinMenuItemConstructorOptions extends MenuItemConstructorOptions {
@@ -16,8 +18,11 @@ interface DarwinMenuItemConstructorOptions extends MenuItemConstructorOptions {
export default class MenuBuilder {
mainWindow: BrowserWindow;
- constructor(mainWindow: BrowserWindow) {
+ i18n: any;
+
+ constructor(mainWindow: BrowserWindow, i18n: any) {
this.mainWindow = mainWindow;
+ this.i18n = i18n;
}
buildMenu(): Menu {
@@ -192,7 +197,33 @@ export default class MenuBuilder {
? subMenuViewDev
: subMenuViewProd;
- return [subMenuAbout, subMenuEdit, subMenuView, subMenuWindow, subMenuHelp];
+ const languageSubmenu = config.languages.map((languageCode) => {
+ return {
+ label: this.i18n.t(languageCode),
+ type: 'radio',
+ checked: this.i18n.language === languageCode,
+ click: () => {
+ this.i18n.changeLanguage(languageCode);
+ },
+ };
+ });
+
+ const languageMenu: MenuItemConstructorOptions = {
+ label: this.i18n.t('Language'),
+ submenu: languageSubmenu,
+ };
+
+ console.log('\n\n\n\n\nprinting stufff!!!!!');
+ console.log(this.i18n.t('Language'));
+
+ return [
+ subMenuAbout,
+ subMenuEdit,
+ subMenuView,
+ subMenuWindow,
+ subMenuHelp,
+ languageMenu,
+ ];
}
buildDefaultTemplate() {
diff --git a/package.json b/package.json
index 3dc84bf..4f74cd9 100644
--- a/package.json
+++ b/package.json
@@ -195,6 +195,7 @@
"@types/enzyme-adapter-react-16": "^1.0.6",
"@types/express": "^4.17.7",
"@types/history": "^4.7.6",
+ "@types/i18next-node-fs-backend": "^2.1.0",
"@types/jest": "^26.0.5",
"@types/node": "12",
"@types/react": "^16.9.44",
@@ -281,14 +282,20 @@
"axios": "^0.19.2",
"connected-react-router": "^6.6.1",
"electron-debug": "^3.1.0",
+ "electron-is-dev": "^1.2.0",
"electron-log": "^4.2.2",
"electron-updater": "^4.3.1",
"express": "^4.17.1",
"get-port": "^5.1.1",
"history": "^4.7.2",
+ "i18next": "^19.6.3",
+ "i18next-fs-backend": "^1.0.7",
+ "i18next-node-fs-backend": "^2.1.3",
+ "i18next-sync-fs-backend": "^1.1.1",
"react": "^16.13.1",
"react-dom": "^16.12.0",
"react-hot-loader": "^4.12.21",
+ "react-i18next": "^11.7.0",
"react-redux": "^7.2.0",
"react-router-dom": "^5.2.0",
"redux": "^4.0.5",
diff --git a/yarn.lock b/yarn.lock
index 7f1fa86..8b58b7c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1085,7 +1085,7 @@
core-js-pure "^3.0.0"
regenerator-runtime "^0.13.4"
-"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4":
+"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4":
version "7.11.2"
resolved "https://registry.npm.taobao.org/@babel/runtime/download/@babel/runtime-7.11.2.tgz?cache=0&sync_timestamp=1596637793207&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fruntime%2Fdownload%2F%40babel%2Fruntime-7.11.2.tgz#f549c13c754cc40b87644b9fa9f09a6a95fe0736"
integrity sha1-9UnBPHVMxAuHZEufqfCaapX+BzY=
@@ -1620,6 +1620,13 @@
"@types/react" "*"
hoist-non-react-statics "^3.3.0"
+"@types/i18next-node-fs-backend@^2.1.0":
+ version "2.1.0"
+ resolved "https://registry.npm.taobao.org/@types/i18next-node-fs-backend/download/@types/i18next-node-fs-backend-2.1.0.tgz#83616bc8589f155438f150b83ec59b92e5347668"
+ integrity sha1-g2FryFifFVQ48VC4PsWbkuU0dmg=
+ dependencies:
+ i18next ">=17.0.11"
+
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1":
version "2.0.3"
resolved "https://registry.npm.taobao.org/@types/istanbul-lib-coverage/download/@types/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762"
@@ -5475,7 +5482,7 @@ electron-is-accelerator@^0.1.0:
resolved "https://registry.npm.taobao.org/electron-is-accelerator/download/electron-is-accelerator-0.1.2.tgz#509e510c26a56b55e17f863a4b04e111846ab27b"
integrity sha1-UJ5RDCala1Xhf4Y6SwThEYRqsns=
-electron-is-dev@^1.1.0:
+electron-is-dev@^1.1.0, electron-is-dev@^1.2.0:
version "1.2.0"
resolved "https://registry.npm.taobao.org/electron-is-dev/download/electron-is-dev-1.2.0.tgz#2e5cea0a1b3ccf1c86f577cee77363ef55deb05e"
integrity sha1-LlzqChs8zxyG9XfO53Nj71XesF4=
@@ -7275,6 +7282,13 @@ html-escaper@^2.0.0:
resolved "https://registry.npm.taobao.org/html-escaper/download/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
integrity sha1-39YAJ9o2o238viNiYsAKWCJoFFM=
+html-parse-stringify2@2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npm.taobao.org/html-parse-stringify2/download/html-parse-stringify2-2.0.1.tgz#dc5670b7292ca158b7bc916c9a6735ac8872834a"
+ integrity sha1-3FZwtyksoVi3vJFsmmc1rIhyg0o=
+ dependencies:
+ void-elements "^2.0.1"
+
html-tags@^3.1.0:
version "3.1.0"
resolved "https://registry.npm.taobao.org/html-tags/download/html-tags-3.1.0.tgz#7b5e6f7e665e9fb41f30007ed9e0d41e97fb2140"
@@ -7377,6 +7391,34 @@ husky@^4.2.5:
slash "^3.0.0"
which-pm-runs "^1.0.0"
+i18next-fs-backend@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.npm.taobao.org/i18next-fs-backend/download/i18next-fs-backend-1.0.7.tgz#00ca4587e306f8948740408389dda73461a5d07f"
+ integrity sha1-AMpFh+MG+JSHQECDid2nNGGl0H8=
+
+i18next-node-fs-backend@^2.1.3:
+ version "2.1.3"
+ resolved "https://registry.npm.taobao.org/i18next-node-fs-backend/download/i18next-node-fs-backend-2.1.3.tgz#483fa9eda4c152d62a3a55bcae2a5727ba887559"
+ integrity sha1-SD+p7aTBUtYqOlW8ripXJ7qIdVk=
+ dependencies:
+ js-yaml "3.13.1"
+ json5 "2.0.0"
+
+i18next-sync-fs-backend@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.npm.taobao.org/i18next-sync-fs-backend/download/i18next-sync-fs-backend-1.1.1.tgz#d1fb28545918e899b59feccc61adb4b9f5593a04"
+ integrity sha1-0fsoVFkY6Jm1n+zMYa20ufVZOgQ=
+ dependencies:
+ js-yaml "3.13.1"
+ json5 "0.5.0"
+
+i18next@>=17.0.11, i18next@^19.6.3:
+ version "19.6.3"
+ resolved "https://registry.npm.taobao.org/i18next/download/i18next-19.6.3.tgz#ce2346161b35c4c5ab691b0674119c7b349c0817"
+ integrity sha1-ziNGFhs1xMWraRsGdBGcezScCBc=
+ dependencies:
+ "@babel/runtime" "^7.10.1"
+
iconv-lite@0.4.24:
version "0.4.24"
resolved "https://registry.npm.taobao.org/iconv-lite/download/iconv-lite-0.4.24.tgz?cache=0&sync_timestamp=1594184266261&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ficonv-lite%2Fdownload%2Ficonv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
@@ -8549,6 +8591,14 @@ js-tokens@^3.0.0, js-tokens@^3.0.2:
resolved "https://registry.npm.taobao.org/js-tokens/download/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha1-GSA/tZmR35jjoocFDUZHzerzJJk=
+js-yaml@3.13.1:
+ version "3.13.1"
+ resolved "https://registry.npm.taobao.org/js-yaml/download/js-yaml-3.13.1.tgz?cache=0&sync_timestamp=1593091110355&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjs-yaml%2Fdownload%2Fjs-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
+ integrity sha1-r/FRswv9+o5J4F2iLnQV6d+jeEc=
+ dependencies:
+ argparse "^1.0.7"
+ esprima "^4.0.0"
+
js-yaml@^3.13.1, js-yaml@^3.14.0:
version "3.14.0"
resolved "https://registry.npm.taobao.org/js-yaml/download/js-yaml-3.14.0.tgz?cache=0&sync_timestamp=1593091110355&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjs-yaml%2Fdownload%2Fjs-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482"
@@ -8644,6 +8694,18 @@ json3@^3.3.2:
resolved "https://registry.npm.taobao.org/json3/download/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81"
integrity sha1-f8EON1/FrkLEcFpcwKpvYr4wW4E=
+json5@0.5.0:
+ version "0.5.0"
+ resolved "https://registry.npm.taobao.org/json5/download/json5-0.5.0.tgz#9b20715b026cbe3778fd769edccd822d8332a5b2"
+ integrity sha1-myBxWwJsvjd4/Xae3M2CLYMypbI=
+
+json5@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npm.taobao.org/json5/download/json5-2.0.0.tgz#b61abf97aa178c4b5853a66cc8eecafd03045d78"
+ integrity sha1-thq/l6oXjEtYU6ZsyO7K/QMEXXg=
+ dependencies:
+ minimist "^1.2.0"
+
json5@2.x, json5@^2.1.0, json5@^2.1.2:
version "2.1.3"
resolved "https://registry.npm.taobao.org/json5/download/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43"
@@ -11175,6 +11237,14 @@ react-hot-loader@^4.12.21:
shallowequal "^1.1.0"
source-map "^0.7.3"
+react-i18next@^11.7.0:
+ version "11.7.0"
+ resolved "https://registry.npm.taobao.org/react-i18next/download/react-i18next-11.7.0.tgz#f27c4c237a274e007a48ac1210db83e33719908b"
+ integrity sha1-8nxMI3onTgB6SKwSENuD4zcZkIs=
+ dependencies:
+ "@babel/runtime" "^7.3.1"
+ html-parse-stringify2 "2.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.6, react-is@^16.9.0:
version "16.13.1"
resolved "https://registry.npm.taobao.org/react-is/download/react-is-16.13.1.tgz?cache=0&sync_timestamp=1596207163827&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-is%2Fdownload%2Freact-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@@ -13882,6 +13952,11 @@ vm-browserify@^1.0.1:
resolved "https://registry.npm.taobao.org/vm-browserify/download/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
integrity sha1-eGQcSIuObKkadfUR56OzKobl3aA=
+void-elements@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npm.taobao.org/void-elements/download/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
+ integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=
+
w3c-hr-time@^1.0.2:
version "1.0.2"
resolved "https://registry.npm.taobao.org/w3c-hr-time/download/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"