i18ntk
Version:
i18n Tool Kit - Zero-dependency internationalization toolkit for setup, scanning, analysis, validation, auto translation, fixing, reporting, and runtime translation loading.
954 lines (810 loc) • 28.4 kB
JavaScript
// runtime/enhanced.js
// Enhanced runtime API with AES-256-GCM encryption and full TypeScript support
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const EventEmitter = require('events');
// Import secure error handling
const {
SecureError,
ValidationError,
SecurityError,
EncryptionError
} = require('../utils/secure-errors');
// Import existing runtime for backward compatibility
const baseRuntime = require('./index.js');
// Constants for AES-256-GCM encryption
const ALGORITHM = 'aes-256-gcm';
const KEY_LENGTH = 32;
const IV_LENGTH = 16;
const AUTH_TAG_LENGTH = 16;
const SALT_LENGTH = 32;
// Track active instances to ensure cleanup is registered only once
let activeInstances = new Set();
let processHandlersRegistered = false;
function registerProcessHandlers() {
if (processHandlersRegistered) return;
processHandlersRegistered = true;
process.on('exit', () => {
for (const instance of activeInstances) {
try { instance.cleanup(); } catch (_) { /* best-effort */ }
}
});
process.on('SIGINT', () => {
for (const instance of activeInstances) {
try { instance.cleanup(); } catch (_) { /* best-effort */ }
}
process.exit(0);
});
process.on('uncaughtException', () => {
for (const instance of activeInstances) {
try { instance.cleanup(); } catch (_) { /* best-effort */ }
}
process.exit(1);
});
}
class I18nEnhancedRuntime extends EventEmitter {
constructor() {
super();
// Generate a unique salt for this instance
const instanceSalt = crypto.randomBytes(16).toString('hex');
this.config = {
baseDir: './locales',
defaultLanguage: 'en',
fallbackLanguage: 'en',
keySeparator: '.',
preload: false,
encryption: {
enabled: false,
algorithm: ALGORITHM,
keyLength: KEY_LENGTH,
ivLength: IV_LENGTH,
authTagLength: AUTH_TAG_LENGTH,
salt: instanceSalt, // Store the instance-specific salt
saltRounds: 16, // Number of bytes for salt generation
},
cache: {
enabled: true,
maxSize: 1000, // Maximum number of entries
maxMemoryMB: 100, // Maximum memory in MB before eviction
ttl: 300000, // 5 minutes
checkFrequency: 100, // Check memory every 100 operations
entrySizeLimit: 1024 * 10, // 10KB max per entry
},
security: {
validateInputs: true,
sanitizeOutput: true,
maxKeyLength: 1000,
maxValueLength: 10000,
},
};
this.encryptionKey = null;
this.cache = new Map();
this.cacheSize = 0; // Track total cache size in bytes
this.cacheOpsSinceLastCheck = 0;
this.plugins = [];
this.metrics = {
totalTranslations: 0,
cacheHitRate: 0,
averageTranslationTime: 0,
encryptionTime: 0,
memoryUsage: 0,
cacheEvictions: 0,
cacheSizeBytes: 0,
cacheEntryCount: 0,
cacheHits: 0,
cacheMisses: 0,
};
this.namespaces = new Map();
// Setup periodic memory checks
this.memoryCheckInterval = setInterval(
() => this.checkMemoryUsage(),
30000 // Check every 30 seconds
);
if (typeof this.memoryCheckInterval.unref === 'function') {
this.memoryCheckInterval.unref();
}
// Register this instance for process-wide cleanup
activeInstances.add(this);
registerProcessHandlers();
// Add default translations namespace
this.addNamespace('default', {
en: {
greeting: 'Hello',
welcome: 'Welcome',
goodbye: 'Goodbye',
thank_you: 'Thank you',
yes: 'Yes',
no: 'No',
error: 'Error',
success: 'Success',
loading: 'Loading...',
submit: 'Submit'
}
});
}
// Encryption/decryption methods with secure error handling
async encryptData(data, key = this.encryptionKey) {
if (!key) {
throw new EncryptionError('Encryption key not set', {
operation: 'encrypt',
keyType: typeof key
});
}
const startTime = Date.now();
try {
if (typeof data !== 'string') {
try {
data = JSON.stringify(data);
} catch (e) {
throw new ValidationError('Failed to stringify data for encryption', {
dataType: typeof data,
error: e.message
});
}
}
const iv = crypto.randomBytes(IV_LENGTH);
const cipher = crypto.createCipheriv(ALGORITHM, Buffer.from(key, 'hex'), iv);
let encrypted;
try {
encrypted = cipher.update(data, 'utf8', 'hex');
encrypted += cipher.final('hex');
} catch (e) {
throw new EncryptionError('Failed to encrypt data', {
dataLength: data ? data.length : 0,
error: e.message
});
}
const authTag = cipher.getAuthTag();
const result = {
encrypted,
iv: iv.toString('hex'),
authTag: authTag.toString('hex'),
timestamp: Date.now(),
version: 1
};
this.metrics.encryptionTime += Date.now() - startTime;
try {
return JSON.stringify(result);
} catch (e) {
throw new EncryptionError('Failed to stringify encrypted result', {
resultType: typeof result,
error: e.message
});
}
} catch (error) {
if (error instanceof SecureError) throw error;
// Sanitize error message to prevent information leakage
throw new EncryptionError('Encryption failed', {
operation: 'encrypt',
errorId: crypto.randomBytes(4).toString('hex')
});
}
}
async decryptData(encryptedData, key = this.encryptionKey) {
if (!key) {
throw new EncryptionError('Encryption key not set', {
operation: 'decrypt',
keyType: typeof key
});
}
try {
let data;
try {
data = JSON.parse(encryptedData);
} catch (parseError) {
throw new EncryptionError('Failed to parse encrypted data', {
operation: 'decrypt',
error: parseError.message
});
}
if (!data || !data.iv || !data.authTag || !data.encrypted) {
throw new EncryptionError('Invalid encrypted data format', {
operation: 'decrypt',
missingFields: ['iv', 'authTag', 'encrypted'].filter(f => !(f in (data || {})))
});
}
const decipher = crypto.createDecipheriv(ALGORITHM, Buffer.from(key, 'hex'), Buffer.from(data.iv, 'hex'));
decipher.setAuthTag(Buffer.from(data.authTag, 'hex'));
let decrypted = decipher.update(data.encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
} catch (error) {
if (error instanceof SecureError) throw error;
throw new EncryptionError('Decryption failed', {
operation: 'decrypt',
errorId: crypto.randomBytes(4).toString('hex')
});
}
}
// Generate secure encryption key with optional salt
generateEncryptionKey(salt = null) {
// If no salt provided, generate a new one
if (!salt) {
salt = crypto.randomBytes(16).toString('hex');
}
// Derive key using scrypt for additional security
const key = crypto.scryptSync(
crypto.randomBytes(32).toString('hex'),
salt,
KEY_LENGTH
);
return {
key: key.toString('hex'),
salt: salt
};
}
// Hash key for storage with secure salt management
hashKey(key, salt = null) {
// Generate a new random salt if none provided
if (!salt) {
salt = crypto.randomBytes(16).toString('hex');
// Store the salt in config if not already set
if (!this.config.encryption.salt) {
this.config.encryption.salt = salt;
}
}
// Use scrypt with the salt (either provided, generated, or from config)
const derivedKey = crypto.scryptSync(
key,
salt || this.config.encryption.salt,
KEY_LENGTH
);
return {
key: derivedKey.toString('hex'),
salt: salt || this.config.encryption.salt
};
}
// Enhanced translation with TypeScript support and secure error handling
async translate(key, params = {}, options = {}) {
const startTime = Date.now();
try {
// Validate inputs with secure error handling
if (this.config.security.validateInputs) {
try {
this.validateTranslationKey(key);
this.validateParams(params);
} catch (error) {
throw new ValidationError('Invalid translation input', {
key: key ? 'Provided' : 'Missing',
params: params ? 'Provided' : 'Missing',
details: error.message
});
}
}
// Merge options with config
const mergedOptions = { ...this.config, ...options };
const language = mergedOptions.language || this.config.defaultLanguage;
const fallbackLanguage = mergedOptions.fallbackLanguage || this.config.fallbackLanguage;
// Check cache
const cacheKey = this.getCacheKey(key, params, language);
if (this.config.cache.enabled && this.cache.has(cacheKey)) {
const cached = this.cache.get(cacheKey);
if (Date.now() - cached.timestamp < this.config.cache.ttl) {
this.metrics.cacheHitRate++;
return cached.value;
}
}
// Get translation
let translation = await this.getTranslation(key, language, params);
// Fallback to fallback language
if (!translation && fallbackLanguage && fallbackLanguage !== language) {
translation = await this.getTranslation(key, fallbackLanguage, params);
}
// Use key as fallback
if (!translation) {
translation = key;
}
// Apply plugins
for (const plugin of this.plugins) {
if (plugin.transform) {
translation = plugin.transform(translation, params, mergedOptions);
}
}
// Sanitize output
if (this.config.security.sanitizeOutput) {
translation = this.sanitizeTranslation(translation);
}
// Cache result
if (this.config.cache.enabled) {
this.setCache(cacheKey, translation);
}
// Update metrics
this.metrics.totalTranslations++;
this.metrics.averageTranslationTime += Date.now() - startTime;
// Emit event
this.emit('translation', {
type: 'translation',
key,
language,
params,
result: translation,
timestamp: new Date(),
duration: Date.now() - startTime,
});
return translation;
} catch (error) {
this.emit('error', {
type: 'error',
key,
language: this.config.defaultLanguage,
params,
error: error.message,
timestamp: new Date(),
});
throw error;
}
}
// Async translate with encryption support
async translateEncrypted(key, params = {}, options = {}) {
const translation = await this.translate(key, params, options);
if (this.config.encryption.enabled && this.encryptionKey) {
return await this.encryptData(translation);
}
return translation;
}
// Batch translation
async translateBatch(keys, paramsArray = [], options = {}) {
const results = [];
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const params = paramsArray[i] || {};
const result = await this.translate(key, params, options);
results.push(result);
}
return results;
}
// Batch translation with encryption
async translateBatchEncrypted(keys, paramsArray = [], options = {}) {
const results = [];
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const params = paramsArray[i] || {};
const result = await this.translateEncrypted(key, params, options);
results.push(result);
}
return results;
}
// Get translation with namespace support
async getTranslation(key, language, params) {
// Check namespaces first
for (const [namespace, translations] of this.namespaces) {
if (translations[language] && translations[language][key]) {
return this.interpolate(translations[language][key], params);
}
}
// Fall back to base runtime
const baseInstance = baseRuntime.initRuntime({
baseDir: this.config.baseDir,
language,
fallbackLanguage: this.config.fallbackLanguage,
});
return baseInstance.translate(key, params);
}
// Interpolation with advanced features
interpolate(template, params) {
if (typeof template !== 'string') return template;
let result = template;
// Handle pluralization
if (params.count !== undefined) {
const pluralRules = this.getPluralRules(this.config.defaultLanguage);
const pluralForm = pluralRules.rule(params.count);
// Simple pluralization support
const pluralKey = result.includes('|') ?
result.split('|')[pluralForm] || result.split('|')[0] :
result;
result = pluralKey;
}
// Standard interpolation
result = result.replace(/\{\{?(\w+)\}?\}/g, (match, key) => {
return params[key] !== undefined ? String(params[key]) : match;
});
return result;
}
// Plural rules for different languages
getPluralRules(language) {
const rules = {
en: { rule: (n) => n === 1 ? 0 : 1, examples: ['one', 'other'] },
es: { rule: (n) => n === 1 ? 0 : 1, examples: ['one', 'other'] },
fr: { rule: (n) => n <= 1 ? 0 : 1, examples: ['one', 'other'] },
de: { rule: (n) => n === 1 ? 0 : 1, examples: ['one', 'other'] },
ja: { rule: () => 0, examples: ['other'] },
ru: { rule: (n) => {
const rem = n % 10;
const rem100 = n % 100;
if (rem === 1 && rem100 !== 11) return 0;
if (rem >= 2 && rem <= 4 && (rem100 < 10 || rem100 >= 20)) return 1;
return 2;
}, examples: ['one', 'few', 'other'] },
zh: { rule: () => 0, examples: ['other'] },
};
return rules[language] || rules.en;
}
// Validation methods
validateTranslationKey(key) {
if (typeof key !== 'string') {
throw new Error('Translation key must be a string');
}
if (key.length > this.config.security.maxKeyLength) {
throw new Error(`Translation key too long (max ${this.config.security.maxKeyLength})`);
}
return true;
}
validateParams(params) {
if (typeof params !== 'object' || params === null) {
throw new Error('Translation params must be an object');
}
return true;
}
sanitizeTranslation(text) {
if (typeof text !== 'string') return text;
// Basic HTML sanitization
return text
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
// Cache management with secure key generation
getCacheKey(key, params, language) {
// Sanitize inputs
const sanitizedKey = this.sanitizeCacheKey(key);
const sanitizedLanguage = this.sanitizeLanguageCode(language);
// Create a stable string representation of params
let paramsString = '';
if (params && typeof params === 'object') {
try {
// Sort keys to ensure consistent ordering
const sortedParams = {};
Object.keys(params).sort().forEach(k => {
if (params[k] !== undefined && params[k] !== null) {
sortedParams[k] = params[k];
}
});
paramsString = JSON.stringify(sortedParams);
} catch (e) {
// If we can't stringify params, use a hash of the params object
paramsString = crypto.createHash('sha256')
.update(JSON.stringify(params) || '')
.digest('hex')
.substring(0, 16);
}
}
// Create a hash of the combined components to prevent injection
const cacheKey = crypto.createHash('sha256')
.update(`${sanitizedLanguage}:${sanitizedKey}:${paramsString}`)
.digest('hex');
return `i18n:${cacheKey}`;
}
// Sanitize cache key input
sanitizeCacheKey(key) {
if (typeof key !== 'string') {
throw new ValidationError('Cache key must be a string');
}
// Remove any potentially dangerous characters
return key.replace(/[^\w\-.:@]/g, '_');
}
// Sanitize language code
sanitizeLanguageCode(lang) {
if (typeof lang !== 'string') {
return 'en'; // Default to English
}
// Only allow letters and hyphens, convert to lowercase
return lang.replace(/[^a-zA-Z\-]/g, '').toLowerCase();
}
// Calculate the approximate size of an object in bytes
getObjectSize(obj) {
if (obj === null || obj === undefined) return 0;
let bytes = 0;
if (typeof obj === 'string') {
// UTF-16 uses 2 bytes per character
bytes = obj.length * 2;
} else if (typeof obj === 'number') {
// Numbers are 8 bytes (64 bits)
bytes = 8;
} else if (typeof obj === 'boolean') {
// Booleans are 4 bytes
bytes = 4;
} else if (typeof obj === 'object') {
// For objects and arrays, sum the size of all properties
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
bytes += this.getObjectSize(obj[key]);
// Add the size of the key itself
bytes += key.length * 2; // UTF-16
}
}
}
return bytes;
}
// Check if we need to free up memory
checkMemoryUsage(force = false) {
this.cacheOpsSinceLastCheck++;
// Only check every N operations or if forced
if (!force && this.cacheOpsSinceLastCheck < this.config.cache.checkFrequency) {
return;
}
this.cacheOpsSinceLastCheck = 0;
// Check memory usage
const memoryUsage = process.memoryUsage();
const heapUsedMB = memoryUsage.heapUsed / (1024 * 1024);
// If we're using too much memory, clear some cache entries
if (heapUsedMB > this.config.cache.maxMemoryMB || force) {
const targetReduction = Math.ceil(this.cache.size * 0.2); // Remove 20% of entries
let removed = 0;
// Sort entries by last access time (oldest first)
const entries = Array.from(this.cache.entries())
.sort((a, b) => a[1].timestamp - b[1].timestamp);
// Remove oldest entries
for (const [key, entry] of entries) {
if (removed >= targetReduction) break;
// Reduce cache size
this.cacheSize -= this.getObjectSize(entry);
this.cache.delete(key);
removed++;
this.metrics.cacheEvictions++;
}
this.emit('cachePruned', {
timestamp: new Date(),
entriesRemoved: removed,
remainingEntries: this.cache.size,
heapUsedMB,
maxMemoryMB: this.config.cache.maxMemoryMB
});
}
// Update metrics
this.metrics.memoryUsage = heapUsedMB;
this.metrics.cacheSizeBytes = this.cacheSize;
this.metrics.cacheEntryCount = this.cache.size;
}
// Clean up resources
cleanup() {
if (this.memoryCheckInterval) {
clearInterval(this.memoryCheckInterval);
this.memoryCheckInterval = null;
}
// Clear all caches
this.cache.clear();
this.cacheSize = 0;
// Clear namespaces
this.namespaces.clear();
// Clear encryption key from memory
if (this.encryptionKey) {
this.encryptionKey = null;
}
// Clear any sensitive data from config
if (this.config.encryption) {
this.config.encryption.salt = null;
}
activeInstances.delete(this);
}
// Add or update a cache entry
setCache(key, value) {
// Check entry size limit
const entrySize = this.getObjectSize(value);
if (entrySize > this.config.cache.entrySizeLimit) {
this.emit('cacheReject', {
reason: 'entry_too_large',
key,
size: entrySize,
maxSize: this.config.cache.entrySizeLimit
});
return false;
}
// Check if we need to make space
if (this.cache.size >= this.config.cache.maxSize) {
// Remove the least recently used entry
const lruKey = this.cache.keys().next().value;
const lruEntry = this.cache.get(lruKey);
if (lruEntry) {
this.cacheSize -= this.getObjectSize(lruEntry);
this.cache.delete(lruKey);
this.metrics.cacheEvictions++;
}
}
// Add the new entry
const entry = {
value,
timestamp: Date.now(),
size: entrySize
};
// Update cache size
this.cacheSize += entrySize;
// Store the entry
this.cache.set(key, entry);
// Check memory usage periodically
this.checkMemoryUsage();
return true;
}
// Configuration management
async updateConfig(updates) {
this.config = { ...this.config, ...updates };
this.cache.clear();
if (updates.encryption?.enabled && !this.encryptionKey) {
const { key, salt } = this.generateEncryptionKey(updates.encryption.salt);
this.encryptionKey = key;
this.config.encryption.salt = salt;
}
}
getConfig() {
return { ...this.config };
}
// Namespace management
addNamespace(name, translations) {
this.namespaces.set(name, translations);
}
removeNamespace(name) {
this.namespaces.delete(name);
}
getNamespace(name) {
return this.namespaces.get(name) || null;
}
listNamespaces() {
return Array.from(this.namespaces.keys());
}
// Plugin system
addPlugin(plugin) {
this.plugins.push(plugin);
}
removePlugin(pluginName) {
this.plugins = this.plugins.filter(p => p.name !== pluginName);
}
// Performance metrics
getMetrics() {
return { ...this.metrics };
}
resetMetrics() {
this.metrics = {
totalTranslations: 0,
cacheHitRate: 0,
averageTranslationTime: 0,
encryptionTime: 0,
memoryUsage: process.memoryUsage().heapUsed,
cacheHits: 0,
cacheMisses: 0,
cacheEvictions: 0,
};
}
getCacheInfo() {
const memoryUsage = process.memoryUsage();
return {
entries: this.cache.size,
maxEntries: this.config.cache.maxSize,
sizeBytes: this.cacheSize,
maxMemoryMB: this.config.cache.maxMemoryMB,
entrySizeLimit: this.config.cache.entrySizeLimit,
hits: this.metrics.cacheHits,
misses: this.metrics.cacheMisses,
hitRate: this.metrics.cacheHits / (this.metrics.cacheHits + this.metrics.cacheMisses) || 0,
evictions: this.metrics.cacheEvictions,
memoryUsage: {
heapUsedMB: memoryUsage.heapUsed / (1024 * 1024),
heapTotalMB: memoryUsage.heapTotal / (1024 * 1024),
externalMB: memoryUsage.external / (1024 * 1024) || 0,
arrayBuffersMB: memoryUsage.arrayBuffers / (1024 * 1024) || 0,
rssMB: memoryUsage.rss / (1024 * 1024)
},
ttl: this.config.cache.ttl,
lastChecked: new Date().toISOString()
};
}
clearCache() {
this.cache.clear();
}
}
// Global runtime instance
let runtimeInstance = null;
// Enhanced initialization
async function initI18nRuntime(options = {}) {
if (!runtimeInstance) {
runtimeInstance = new I18nEnhancedRuntime();
}
await runtimeInstance.updateConfig(options);
return {
t: runtimeInstance.translate.bind(runtimeInstance),
translate: runtimeInstance.translate.bind(runtimeInstance),
translateEncrypted: runtimeInstance.translateEncrypted.bind(runtimeInstance),
translateBatch: runtimeInstance.translateBatch.bind(runtimeInstance),
translateBatchEncrypted: runtimeInstance.translateBatchEncrypted.bind(runtimeInstance),
setLanguage: async (lang) => {
await runtimeInstance.updateConfig({ defaultLanguage: lang });
},
getLanguage: () => runtimeInstance.config.defaultLanguage,
getAvailableLanguages: () => {
// This would scan the base directory for available languages
return ['en', 'es', 'fr', 'de', 'ja', 'ru', 'zh'];
},
refresh: async (lang) => {
runtimeInstance.cache.clear();
},
getConfig: () => runtimeInstance.getConfig(),
updateConfig: runtimeInstance.updateConfig.bind(runtimeInstance),
setEncryptionKey: (key) => {
runtimeInstance.encryptionKey = key;
},
getEncryptionStatus: () => runtimeInstance.config.encryption.enabled && !!runtimeInstance.encryptionKey,
encryptData: runtimeInstance.encryptData.bind(runtimeInstance),
decryptData: runtimeInstance.decryptData.bind(runtimeInstance),
validateTranslationKey: runtimeInstance.validateTranslationKey.bind(runtimeInstance),
sanitizeTranslation: runtimeInstance.sanitizeTranslation.bind(runtimeInstance),
getTranslationMetadata: (key) => ({
text: key,
language: runtimeInstance.config.defaultLanguage,
key,
params: {},
encrypted: runtimeInstance.config.encryption.enabled,
}),
createTypedTranslator: () => ({
t: runtimeInstance.translate.bind(runtimeInstance),
translate: runtimeInstance.translate.bind(runtimeInstance),
}),
// Additional methods
addNamespace: runtimeInstance.addNamespace.bind(runtimeInstance),
removeNamespace: runtimeInstance.removeNamespace.bind(runtimeInstance),
getNamespace: runtimeInstance.getNamespace.bind(runtimeInstance),
listNamespaces: runtimeInstance.listNamespaces.bind(runtimeInstance),
addPlugin: runtimeInstance.addPlugin.bind(runtimeInstance),
removePlugin: runtimeInstance.removePlugin.bind(runtimeInstance),
getMetrics: runtimeInstance.getMetrics.bind(runtimeInstance),
resetMetrics: runtimeInstance.resetMetrics.bind(runtimeInstance),
};
}
// Backward compatibility
function translate(key, params, options) {
if (!runtimeInstance) {
runtimeInstance = new I18nEnhancedRuntime();
}
return runtimeInstance.translate(key, params, options);
}
const t = translate;
const tTyped = translate;
async function translateBatch(keys, paramsArray, options) {
if (!runtimeInstance) {
runtimeInstance = new I18nEnhancedRuntime();
}
return runtimeInstance.translateBatch(keys, paramsArray, options);
}
async function translateBatchEncrypted(keys, paramsArray, options) {
if (!runtimeInstance) {
runtimeInstance = new I18nEnhancedRuntime();
}
return runtimeInstance.translateBatchEncrypted(keys, paramsArray, options);
}
async function translateEncrypted(key, params, options) {
if (!runtimeInstance) {
runtimeInstance = new I18nEnhancedRuntime();
}
return runtimeInstance.translateEncrypted(key, params, options);
}
// Export for both CommonJS and ES modules
module.exports = {
initI18nRuntime,
translate,
t,
tTyped,
translateEncrypted,
translateBatch,
translateBatchEncrypted,
// TypeScript compatibility exports
I18nEnhancedRuntime,
// Encryption utilities
generateEncryptionKey: () => {
const runtime = new I18nEnhancedRuntime();
return runtime.generateEncryptionKey();
},
// Constants
ALGORITHM,
KEY_LENGTH,
IV_LENGTH,
AUTH_TAG_LENGTH,
};
// ES module support
module.exports.default = {
initI18nRuntime,
translate,
t,
tTyped,
translateEncrypted,
translateBatch,
translateBatchEncrypted,
};