Skip to content

Commit

Permalink
Minimal i18next setup
Browse files Browse the repository at this point in the history
  • Loading branch information
martpie committed Dec 28, 2021
1 parent c4f7ea1 commit 3b1acbb
Show file tree
Hide file tree
Showing 11 changed files with 928 additions and 1,580 deletions.
5 changes: 5 additions & 0 deletions locales/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"settings": {
"key": ":D"
}
}
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@
"font-awesome": "^4.7.0",
"globby": "^11.0.4",
"history": "^5.2.0",
"i18next": "^20.3.5",
"i18next-electron-language-detector": "^0.0.10",
"i18next-multiload-backend-adapter": "^1.0.0",

"iconv-lite": "^0.6.3",
"level-js": "^4.0.1",
"linvodb3": "^3.26.0",
Expand All @@ -60,6 +64,7 @@
"react-dnd-html5-backend": "^14.0.2",
"react-dom": "^17.0.2",
"react-fontawesome": "^1.7.1",
"react-i18next": "^11.11.4",
"react-keybinding-component": "^1.0.0",
"react-onclickout": "^2.0.8",
"react-rangeslider": "^2.2.0",
Expand Down
4 changes: 3 additions & 1 deletion src/main/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import SleepBlockerModule from './modules/sleep-blocker';
import DialogsModule from './modules/dialogs';
import NativeThemeModule from './modules/native-theme';
import DevtoolsModule from './modules/devtools';
import I18nModule from './modules/i18n';

import * as ModulesManager from './lib/modules-manager';
import { checkBounds } from './lib/utils';
Expand Down Expand Up @@ -91,6 +92,7 @@ app.on('ready', async () => {
new SleepBlockerModule(mainWindow),
new DialogsModule(mainWindow),
new NativeThemeModule(mainWindow, configModule),
new DevtoolsModule(mainWindow)
new DevtoolsModule(mainWindow),
new I18nModule(mainWindow, configModule)
).catch(console.error);
});
40 changes: 40 additions & 0 deletions src/main/modules/i18n.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Module in charge of giving back i18next resources to the renderer process
*/

import fs from 'fs';
import path from 'path';
import { app, ipcMain } from 'electron';

import channels from '../../shared/lib/ipc-channels';
import { LANGUAGES } from '../../shared/lib/languages';
import ConfigModule from './config';
import ModuleWindow from './module-window';

class I18nModule extends ModuleWindow {
protected config: ConfigModule;

constructor(window: Electron.BrowserWindow, config: ConfigModule) {
super(window);

this.config = config;
}

async load(): Promise<void> {
console.log(app.getLocale(), app.getLocaleCountryCode());
console.log('========================');
const localesRoot = path.join(__dirname, '..', '..', 'locales');

ipcMain.handle(channels.I18N_GET_RESOURCES, (_event) => {
const resources: Record<string, string> = {};

LANGUAGES.forEach((lang) => {
resources[lang] = JSON.parse(fs.readFileSync(path.join(localesRoot, `${lang}.json`), { encoding: 'utf8' }));
});

return resources;
});
}
}

export default I18nModule;
66 changes: 66 additions & 0 deletions src/renderer/lib/i18n.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* i18next instance
*/

import { ipcRenderer } from 'electron';
import i18next, { BackendModule } from 'i18next';
import { initReactI18next, useTranslation as useI18nextTranslation, UseTranslationResponse } from 'react-i18next';
import BackendAdapter from 'i18next-multiload-backend-adapter';
import LanguageDetector from 'i18next-electron-language-detector';

import { DEFAULT_LANGUAGE, LANGUAGES, NAMESPACES } from '../../shared/lib/languages';
import channels from '../../shared/lib/ipc-channels';

const ipcBackend: BackendModule = {
type: 'backend',
init: function (_services, _backendOptions, _i18nextOptions) {
/* use services and options */
},
read: function (_language, _namespace, callback) {
callback(
new Error(
'i18next ipcBackend should not be using `read`, is i18next-multiload-backend-adapter correctly configured'
),
null
);
},
readMulti: function (languages, namespaces, callback) {
console.info('request MULTI', languages, namespaces);

ipcRenderer
.invoke(channels.I18N_GET_RESOURCES)
.then((resources: Record<string, Record<string, string>>) => {
console.log('RECEIVED: ', resources);
callback(null, resources);
})
.catch((err) => {
console.log('NOTRECEIVED');
callback(err, null);
});
},
};

const i18n = i18next
.createInstance({
backend: {
backend: ipcBackend,
},

// lng: 'en', // use detector instead
supportedLngs: LANGUAGES,
fallbackLng: DEFAULT_LANGUAGE,
ns: NAMESPACES,
defaultNS: '',

debug: process.env.NODE_ENV === 'development',
})
.use(LanguageDetector)
.use(BackendAdapter)
.use(initReactI18next);

export default i18n;

/**
* Custom useTranslation hook to avoid suspense
*/
export const useTranslation = (): UseTranslationResponse<''> => useI18nextTranslation('', { useSuspense: false });
33 changes: 22 additions & 11 deletions src/renderer/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@

import React from 'react';
import * as ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { Provider as ReduxProvider } from 'react-redux';
import { I18nextProvider } from 'react-i18next';

import Root from './Root';
import Router from './Router';
import store from './store/store';
import i18n from './lib/i18n';

/*
|--------------------------------------------------------------------------
Expand All @@ -31,13 +33,22 @@ import './styles/main.module.css';
|--------------------------------------------------------------------------
*/

ReactDOM.render(
<Root>
<Provider store={store}>
<DndProvider backend={HTML5Backend}>
<Router />
</DndProvider>
</Provider>
</Root>,
document.getElementById('wrap')
);
i18n
.init()
.then(() => {
ReactDOM.render(
<Root>
<ReduxProvider store={store}>
<DndProvider backend={HTML5Backend}>
<I18nextProvider i18n={i18n}>
<Router />
</I18nextProvider>
</DndProvider>
</ReduxProvider>
</Root>,
document.getElementById('wrap')
);
})
.catch((err: unknown) => {
throw new Error(`Failed to initialize i18next:\n${err}`);
});
15 changes: 15 additions & 0 deletions src/shared/lib/__tests__/languages.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import fs from 'fs';
import path from 'path';

import { LANGUAGES } from '../languages';

describe('languages', () => {
test('should all have a valid JSON file containing translations', () => {
LANGUAGES.forEach((lang) => {
expect(() => {
const content = fs.readFileSync(path.join(process.cwd(), 'locales', `${lang}.json`), { encoding: 'utf8' });
JSON.parse(content);
}).not.toThrow();
});
});
});
2 changes: 2 additions & 0 deletions src/shared/lib/ipc-channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ const channels = {
MENU_GO_TO_LIBRARY: 'MENU_GO_TO_LIBRARY',
MENU_GO_TO_PLAYLISTS: 'MENU_GO_TO_PLAYLISTS',
MENU_JUMP_TO_PLAYING_TRACK: 'MENU_JUMP_TO_PLAYING_TRACK',

I18N_GET_RESOURCES: 'I18N_GET_RESOURCES',
};

export default channels;
7 changes: 7 additions & 0 deletions src/shared/lib/languages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* Edit me if you add or remove a language
*/

export const LANGUAGES = ['en'];
export const DEFAULT_LANGUAGE = 'en';
export const NAMESPACES = ['settings', 'library', 'playlists'];
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare module 'i18next-electron-language-detector';
Loading

0 comments on commit 3b1acbb

Please sign in to comment.