react-native-i18n-auto
Version:
Auto i18n tool for React Native with full TypeScript support
492 lines (465 loc) • 18.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.i18n = exports.SUPPORTED_LANGUAGES = void 0;
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
// Define supported language codes
var SUPPORTED_LANGUAGES = exports.SUPPORTED_LANGUAGES = {
ar: 'Arabic',
zh: 'Chinese',
en: 'English',
fr: 'French',
de: 'German',
hi: 'Hindi',
id: 'Indonesian',
it: 'Italian',
ja: 'Japanese',
ko: 'Korean',
ms: 'Malay',
pt: 'Portuguese',
ru: 'Russian',
es: 'Spanish',
th: 'Thai',
vi: 'Vietnamese',
cn: 'Chinese'
};
// Language code type
// Cache interface for translations
// Logger configuration
var LOG_PREFIX = '[i18n-auto]';
var log = {
info: function info() {
var _console;
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return (_console = console).log.apply(_console, [LOG_PREFIX].concat(args));
},
warn: function warn() {
var _console2;
for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
return (_console2 = console).warn.apply(_console2, [LOG_PREFIX].concat(args));
},
error: function error() {
var _console3;
for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
args[_key3] = arguments[_key3];
}
return (_console3 = console).error.apply(_console3, [LOG_PREFIX].concat(args));
}
};
/**
* React Native Internationalization (i18n) Manager
*
* A powerful and efficient i18n manager for React Native applications.
* Features include caching, memory optimization, and event handling.
*
* @example
* // 1. Basic Usage
* import { i18n } from 'react-native-i18n-auto';
*
*
* // Use translations
* console.log(i18n.t('HELLO')); // 'Hello'
*
* // Change language
* i18n.setLanguage('ko');
* console.log(i18n.t('HELLO')); // '안녕하세요'
*
* // 2. Event Listener Usage
* const unsubscribe = i18n.onLanguageChange(() => {
* console.log('Language changed to:', i18n.getLanguage());
* });
*
* // 3. Performance Optimization
* // Clear cache
* i18n.clearCache();
*
* // Optimize memory usage
* i18n.optimize();
*
* // 4. Language Support
* const supported = i18n.getSupportedLanguages();
* const available = i18n.getAvailableLanguages();
*/
var I18nManager = /*#__PURE__*/function () {
// Maximum cache items per language
function I18nManager() {
var defaultLang = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'en';
_classCallCheck(this, I18nManager);
_defineProperty(this, "translations", {});
_defineProperty(this, "listeners", new Set());
_defineProperty(this, "translationProxy", {});
// Cache and file tracking
_defineProperty(this, "cache", {});
_defineProperty(this, "loadedFiles", new Set());
_defineProperty(this, "CACHE_DURATION", 5 * 60 * 1000);
// 5 minutes cache duration
_defineProperty(this, "MAX_CACHE_SIZE", 1000);
// Memoized list of available languages
_defineProperty(this, "availableLanguagesCache", null);
this.currentLang = defaultLang;
log.info("Initialized with default language: ".concat(defaultLang));
this.setupProxy();
}
return _createClass(I18nManager, [{
key: "setupProxy",
value: function setupProxy() {
var _this = this;
this.translationProxy = new Proxy({}, {
get: function get(target, prop) {
if (typeof prop !== 'string') return undefined;
return _this.translate(prop);
}
});
}
}, {
key: "isValidLanguageCode",
value: function isValidLanguageCode(code) {
return code in SUPPORTED_LANGUAGES;
}
// Cache management
}, {
key: "manageCache",
value: function manageCache(lang) {
var _this2 = this;
if (!this.cache[lang]) return;
var now = Date.now();
var cache = this.cache[lang];
var entries = Object.entries(cache);
// Clean up cache if it exceeds maximum size or has expired items
if (entries.length > this.MAX_CACHE_SIZE) {
// Remove oldest entries first
var validEntries = entries.filter(function (_ref) {
var _ref2 = _slicedToArray(_ref, 2),
timestamp = _ref2[1].timestamp;
return now - timestamp < _this2.CACHE_DURATION;
}).sort(function (_ref3, _ref4) {
var _ref5 = _slicedToArray(_ref3, 2),
a = _ref5[1];
var _ref6 = _slicedToArray(_ref4, 2),
b = _ref6[1];
return b.timestamp - a.timestamp;
}).slice(0, this.MAX_CACHE_SIZE);
this.cache[lang] = Object.fromEntries(validEntries);
}
}
/**
* Get translation for a given key in the current language
* @param key - Translation key
* @returns Translated string or the key if translation not found
* @example
* i18n.t('HELLO'); // Returns 'Hello' if language is English
* // or using proxy
* i18n.t.HELLO // Returns 'Hello' if language is English
*/
}, {
key: "translate",
value: function translate(key) {
var _this$cache$lang;
var lang = this.currentLang;
// 1. Check cache
if ((_this$cache$lang = this.cache[lang]) !== null && _this$cache$lang !== void 0 && _this$cache$lang[key]) {
var cached = this.cache[lang][key];
if (Date.now() - cached.timestamp < this.CACHE_DURATION) {
return cached.value;
}
}
// 2. Look up translation with nested key support
var keyParts = key.split('.');
var translation = this.translations[lang];
var _iterator = _createForOfIteratorHelper(keyParts),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var part = _step.value;
if (!translation || _typeof(translation) !== 'object') {
log.warn("Translation not found for key: ".concat(key, " in language: ").concat(lang));
return key;
}
var nextValue = translation[part];
if (typeof nextValue === 'string') {
translation = nextValue;
} else if (nextValue && _typeof(nextValue) === 'object' && !Array.isArray(nextValue)) {
translation = nextValue;
} else {
log.warn("Translation not found for key: ".concat(key, " in language: ").concat(lang));
return key;
}
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
if (typeof translation !== 'string') {
log.warn("Invalid translation type for key: ".concat(key, " in language: ").concat(lang));
return key;
}
// 3. Cache and return result
if (!this.cache[lang]) {
this.cache[lang] = {};
}
this.cache[lang][key] = {
value: translation,
timestamp: Date.now()
};
this.manageCache(lang);
return translation;
}
// Make t accessible as both function and proxy
}, {
key: "t",
get: function get() {
var _this3 = this;
return new Proxy(this.translate.bind(this), {
get: function get(target, prop) {
if (typeof prop === 'string') {
return _this3.translate(prop);
}
return undefined;
}
});
}
/**
* Change the current language
* @param lang - Target language code
* @returns Boolean indicating if the language change was successful
* @example
* i18n.setLanguage('en'); // Changes language to English
*/
}, {
key: "setLanguage",
value: function setLanguage(lang) {
// Check if the language is supported and has translations
if (!this.translations[lang]) {
log.warn("Failed to set language: ".concat(lang, " - translations not available"));
return false;
}
// Only change language if it's different from current
if (this.currentLang !== lang) {
var previousLang = this.currentLang;
this.currentLang = lang;
log.info("Language changed from ".concat(previousLang, " to ").concat(lang));
this.listeners.forEach(function (listener) {
return listener();
});
}
return true;
}
/**
* Get the current language code
* @returns Current language code
* @example
* const currentLang = i18n.getLanguage(); // Returns 'en' for English
*/
}, {
key: "getLanguage",
value: function getLanguage() {
return this.currentLang;
}
}, {
key: "getAvailableLanguages",
value:
/**
* Get list of languages that have translations loaded
* @returns Array of available languages with their codes and names
* @example
* const languages = i18n.getAvailableLanguages();
* // Returns [{ code: 'en', name: 'English' }, { code: 'ko', name: 'Korean' }]
*/
function getAvailableLanguages() {
var _this4 = this;
if (!this.availableLanguagesCache) {
this.availableLanguagesCache = Object.entries(SUPPORTED_LANGUAGES).filter(function (_ref7) {
var _ref8 = _slicedToArray(_ref7, 1),
code = _ref8[0];
return code in _this4.translations;
}).map(function (_ref9) {
var _ref10 = _slicedToArray(_ref9, 2),
code = _ref10[0],
name = _ref10[1];
return {
code: code,
name: name
};
});
}
return this.availableLanguagesCache;
}
// Static list of all supported languages
}, {
key: "getSupportedLanguages",
value:
/**
* Get list of all supported languages
* @returns Array of all supported languages with their codes and names
* @example
* const languages = i18n.getSupportedLanguages();
* // Returns [{ code: 'en', name: 'English' }, ...]
*/
function getSupportedLanguages() {
return I18nManager.SUPPORTED_LANGUAGES_LIST;
}
/**
* Register a callback for language change events
* @param listener - Callback function to be called when language changes
* @returns Unsubscribe function to remove the listener
* @example
* const unsubscribe = i18n.onLanguageChange(() => {
* console.log('Language changed to:', i18n.getLanguage());
* });
*/
}, {
key: "onLanguageChange",
value: function onLanguageChange(listener) {
var _this5 = this;
this.listeners.add(listener);
return function () {
_this5.listeners["delete"](listener);
};
}
/**
* Clear the translation cache
* @example
* i18n.clearCache(); // Clears all cached translations
*/
}, {
key: "clearCache",
value: function clearCache() {
this.cache = {};
this.availableLanguagesCache = null;
}
/**
* Optimize memory usage by clearing cache and cleaning up listeners
* @example
* i18n.optimize(); // Optimizes memory usage
*/
}, {
key: "optimize",
value: function optimize() {
this.clearCache();
this.listeners = new Set(Array.from(this.listeners)); // Clean up listeners
}
/**
* Get list of loaded locale files
* @returns Array of loaded locale file names
*/
}, {
key: "getLoadedFiles",
value: function getLoadedFiles() {
return Array.from(this.loadedFiles);
}
/**
* Check if a locale file is loaded
* @param lang - Language code to check
* @returns boolean indicating if the locale file is loaded
*/
}, {
key: "isLocaleLoaded",
value: function isLocaleLoaded(lang) {
return this.loadedFiles.has("".concat(lang, ".json"));
}
/**
* Set translations for multiple languages
* @param locales - Object containing translations for each language
* @example
* i18n.setLocales({
* en: { HELLO: 'Hello' },
* ko: { HELLO: '안녕하세요' }
* });
*/
}, {
key: "setLocales",
value: function setLocales(locales) {
// Reset existing cache
this.cache = {};
this.availableLanguagesCache = null;
// Process translations with minimal copying
var processedTranslations = {};
for (var _i = 0, _Object$entries = Object.entries(locales); _i < _Object$entries.length; _i++) {
var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
lang = _Object$entries$_i[0],
data = _Object$entries$_i[1];
if (!this.isValidLanguageCode(lang)) {
log.warn("Invalid language code: ".concat(lang));
continue;
}
var processed = this.processLocaleData(data);
if (processed) {
processedTranslations[lang] = processed;
}
}
// Update translations
this.translations = processedTranslations;
// Ensure current language is available, switch to first available if not
if (!this.translations[this.currentLang]) {
var availableLang = Object.keys(this.translations)[0];
if (availableLang) {
var previousLang = this.currentLang;
this.currentLang = availableLang;
log.warn("Current language ".concat(previousLang, " not available, switched to: ").concat(availableLang));
}
}
}
}, {
key: "processLocaleData",
value: function processLocaleData(data) {
if (!data || _typeof(data) !== 'object') {
return null;
}
var result = {};
var _processNestedObject = function processNestedObject(obj) {
var prefix = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
for (var _i2 = 0, _Object$entries2 = Object.entries(obj); _i2 < _Object$entries2.length; _i2++) {
var _Object$entries2$_i = _slicedToArray(_Object$entries2[_i2], 2),
_key4 = _Object$entries2$_i[0],
value = _Object$entries2$_i[1];
var fullKey = prefix ? "".concat(prefix, ".").concat(_key4) : _key4;
if (typeof value === 'string') {
result[fullKey] = value;
} else if (value && _typeof(value) === 'object' && !Array.isArray(value)) {
_processNestedObject(value, fullKey);
}
}
};
_processNestedObject(data);
return result;
}
}], [{
key: "getInstance",
value: function getInstance() {
if (!I18nManager.instance) {
I18nManager.instance = new I18nManager();
}
return I18nManager.instance;
}
}]);
}(); // Export singleton instance
_defineProperty(I18nManager, "SUPPORTED_LANGUAGES_LIST", Object.entries(SUPPORTED_LANGUAGES).map(function (_ref11) {
var _ref12 = _slicedToArray(_ref11, 2),
code = _ref12[0],
name = _ref12[1];
return {
code: code,
name: name
};
}));
var i18n = exports.i18n = I18nManager.getInstance();
// Export types