mirror of
https://github.com/hexastack/hexabot
synced 2025-06-26 18:27:28 +00:00
feat: fetch remote i18n
This commit is contained in:
28
api/src/i18n/controllers/i18n.controller.ts
Normal file
28
api/src/i18n/controllers/i18n.controller.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright © 2024 Hexastack. All rights reserved.
|
||||
*
|
||||
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||
*/
|
||||
|
||||
import { Controller, Get, UseInterceptors } from '@nestjs/common';
|
||||
|
||||
import { CsrfInterceptor } from '@/interceptors/csrf.interceptor';
|
||||
|
||||
import { I18nService } from '../services/i18n.service';
|
||||
|
||||
@UseInterceptors(CsrfInterceptor)
|
||||
@Controller('i18n')
|
||||
export class I18nController {
|
||||
constructor(private readonly i18nService: I18nService) {}
|
||||
|
||||
/**
|
||||
* Retrieves translations of all the installed extensions.
|
||||
* @returns An nested object that holds the translations grouped by language and extension name.
|
||||
*/
|
||||
@Get()
|
||||
getTranslations() {
|
||||
return this.i18nService.getExtensionI18nTranslations();
|
||||
}
|
||||
}
|
||||
@@ -46,10 +46,10 @@ export class LanguageController extends BaseController<Language> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a paginated list of categories based on provided filters and pagination settings.
|
||||
* Retrieves a paginated list of languages based on provided filters and pagination settings.
|
||||
* @param pageQuery - The pagination settings.
|
||||
* @param filters - The filters to apply to the language search.
|
||||
* @returns A Promise that resolves to a paginated list of categories.
|
||||
* @returns A Promise that resolves to a paginated list of languages.
|
||||
*/
|
||||
@Get()
|
||||
async findPage(
|
||||
@@ -61,8 +61,8 @@ export class LanguageController extends BaseController<Language> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the filtered number of categories.
|
||||
* @returns A promise that resolves to an object representing the filtered number of categories.
|
||||
* Counts the filtered number of languages.
|
||||
* @returns A promise that resolves to an object representing the filtered number of languages.
|
||||
*/
|
||||
@Get('count')
|
||||
async filterCount(
|
||||
|
||||
@@ -26,6 +26,7 @@ import { Observable } from 'rxjs';
|
||||
|
||||
import { ChatModule } from '@/chat/chat.module';
|
||||
|
||||
import { I18nController } from './controllers/i18n.controller';
|
||||
import { LanguageController } from './controllers/language.controller';
|
||||
import { TranslationController } from './controllers/translation.controller';
|
||||
import { LanguageRepository } from './repositories/language.repository';
|
||||
@@ -62,6 +63,7 @@ export class I18nModule extends NativeI18nModule {
|
||||
controllers: (controllers || []).concat([
|
||||
LanguageController,
|
||||
TranslationController,
|
||||
I18nController,
|
||||
]),
|
||||
providers: providers.concat([
|
||||
I18nService,
|
||||
|
||||
@@ -6,8 +6,13 @@
|
||||
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||
*/
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { promises as fs } from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
import { Injectable, OnModuleInit } from '@nestjs/common';
|
||||
import {
|
||||
I18nJsonLoader,
|
||||
I18nTranslation,
|
||||
I18nService as NativeI18nService,
|
||||
Path,
|
||||
PathValue,
|
||||
@@ -19,11 +24,22 @@ import { config } from '@/config';
|
||||
import { Translation } from '@/i18n/schemas/translation.schema';
|
||||
|
||||
@Injectable()
|
||||
export class I18nService<
|
||||
K = Record<string, unknown>,
|
||||
> extends NativeI18nService<K> {
|
||||
export class I18nService<K = Record<string, unknown>>
|
||||
extends NativeI18nService<K>
|
||||
implements OnModuleInit
|
||||
{
|
||||
private dynamicTranslations: Record<string, Record<string, string>> = {};
|
||||
|
||||
private extensionTranslations: I18nTranslation = {};
|
||||
|
||||
onModuleInit() {
|
||||
this.loadExtensionI18nTranslations();
|
||||
}
|
||||
|
||||
getExtensionI18nTranslations() {
|
||||
return this.extensionTranslations;
|
||||
}
|
||||
|
||||
t<P extends Path<K> = any, R = PathValue<K, P>>(
|
||||
key: P,
|
||||
options?: TranslateOptions,
|
||||
@@ -66,4 +82,48 @@ export class I18nService<
|
||||
return acc;
|
||||
}, this.dynamicTranslations);
|
||||
}
|
||||
|
||||
async loadExtensionI18nTranslations() {
|
||||
const extensionsDir = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
'extensions',
|
||||
'channels',
|
||||
);
|
||||
try {
|
||||
const extensionFolders = await fs.readdir(extensionsDir, {
|
||||
withFileTypes: true,
|
||||
});
|
||||
|
||||
for (const folder of extensionFolders) {
|
||||
if (folder.isDirectory()) {
|
||||
const i18nPath = path.join(extensionsDir, folder.name, 'i18n');
|
||||
const extensionName = folder.name.replaceAll('-', '_');
|
||||
try {
|
||||
// Check if the i18n directory exists
|
||||
await fs.access(i18nPath);
|
||||
|
||||
// Load and merge translations
|
||||
const i18nLoader = new I18nJsonLoader({ path: i18nPath });
|
||||
const translations = await i18nLoader.load();
|
||||
for (const lang in translations) {
|
||||
if (!this.extensionTranslations[lang]) {
|
||||
this.extensionTranslations[lang] = {
|
||||
[extensionName]: translations[lang],
|
||||
};
|
||||
} else {
|
||||
this.extensionTranslations[lang][extensionName] =
|
||||
translations[lang];
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// If the i18n folder does not exist or error in reading, skip this folder
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to read extensions directory: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { I18nService } from '@/i18n/services/i18n.service';
|
||||
import { Settings } from '@/setting/schemas/types';
|
||||
import { SettingService } from '@/setting/services/setting.service';
|
||||
|
||||
import { Block } from '../../chat/schemas/block.schema';
|
||||
@@ -64,7 +63,7 @@ describe('TranslationService', () => {
|
||||
global_fallback: true,
|
||||
fallback_message: ['Global fallback message'],
|
||||
},
|
||||
} as Settings),
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user