@lobehub/chat
Version:
Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.
186 lines (152 loc) • 4.89 kB
text/typescript
import { app } from 'electron';
import i18next from 'i18next';
import { App } from '@/core/App';
import { loadResources } from '@/locales/resources';
import { createLogger } from '@/utils/logger';
// Create logger
const logger = createLogger('core:I18nManager');
export class I18nManager {
private i18n: typeof i18next;
private initialized: boolean = false;
private app: App;
constructor(app: App) {
logger.debug('Initializing I18nManager');
this.app = app;
this.i18n = i18next.createInstance();
}
/**
* Initialize i18next instance
*/
async init(lang?: string) {
if (this.initialized) {
logger.debug('I18nManager already initialized, skipping');
return this.i18n;
}
// Priority: parameter language > stored locale > system language
const storedLocale = this.app.storeManager.get('locale', 'auto') as string;
const defaultLanguage =
lang || (storedLocale !== 'auto' ? storedLocale : app.getLocale()) || 'en-US';
logger.info(
`Initializing i18n, app locale: ${defaultLanguage}, stored locale: ${storedLocale}`,
);
await this.i18n.init({
defaultNS: 'menu',
fallbackLng: 'en-US',
// Load resources as needed
initAsync: true,
interpolation: {
escapeValue: false,
},
lng: defaultLanguage,
ns: ['menu', 'dialog', 'common'],
partialBundledLanguages: true,
});
logger.info(`i18n initialized, language: ${this.i18n.language}`);
// Preload base namespaces
await this.loadLocale(this.i18n.language);
this.initialized = true;
this.refreshMainUI();
// Listen for language change events
this.i18n.on('languageChanged', this.handleLanguageChanged);
return this.i18n;
}
/**
* Basic translation function
*/
t = (key: string, options?: any) => {
const result = this.i18n.t(key, options) as string;
// If translation result is the same as key, translation might be missing
if (result === key) {
logger.warn(`${this.i18n.language} key: ${key} is not found`);
}
return result;
};
/**
* Create a translation function bound to a specific namespace
* @param namespace Namespace
* @returns Translation function bound to namespace
*/
createNamespacedT(namespace: string) {
return (key: string, options: any = {}) => {
// Copy options to avoid modifying the original object
const mergedOptions = { ...options };
// Set namespace
mergedOptions.ns = namespace;
return this.t(key, mergedOptions);
};
}
/**
* Get translation function by namespace
* Provides a more convenient calling method
*/
ns = (namespace: string) => this.createNamespacedT(namespace);
/**
* Get current language
*/
getCurrentLanguage() {
return this.i18n.language;
}
/**
* Change application language
* @param lng Target language
*/
public async changeLanguage(lng: string): Promise<void> {
logger.info(`Changing language to: ${lng}`);
if (!this.initialized) {
await this.init();
}
await this.i18n.changeLanguage(lng);
// Language change event will trigger handleLanguageChanged
}
/**
* Handle language change event
*/
private handleLanguageChanged = async (lang: string) => {
logger.info(`Language changed to: ${lang}`);
await this.loadLocale(lang);
// Notify other parts of main process to refresh UI
this.refreshMainUI();
};
/**
* Refresh main process UI (menus, etc.)
*/
private refreshMainUI() {
logger.debug('Refreshing main UI after language change');
this.app.menuManager.refreshMenus();
}
/**
* Notify renderer process that language has changed
*/
private notifyRendererProcess(lng: string) {
logger.debug(`Notifying renderer process of language change: ${lng}`);
// Send language change event to all windows
// const windows = this.app.browserManager.windows;
//
// if (windows && windows.length > 0) {
// windows.forEach((window) => {
// if (window?.webContents) {
// window.webContents.send('language-changed', lng);
// }
// });
// }
}
private async loadLocale(language: string) {
logger.debug(`Loading locale for language: ${language}`);
// Preload base namespaces
await Promise.all(['menu', 'dialog', 'common'].map((ns) => this.loadNamespace(language, ns)));
}
/**
* Load translation resources for specific namespace
*/
private async loadNamespace(lng: string, ns: string) {
try {
logger.debug(`Loading namespace: ${lng}/${ns}`);
const resources = await loadResources(lng, ns);
this.i18n.addResourceBundle(lng, ns, resources, true, true);
return true;
} catch (error) {
logger.error(`Failed to load namespace: ${lng}/${ns}`, error);
return false;
}
}
}