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')}`}

+
); } 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"