Files
MetonaEditor/src/i18n.js
T
thzxx f8b9f4a761 release: v0.1.4 — parser enhancement, new syntax, performance optimization
feat(parser): comprehensive Markdown parser enhancement
- Add nested list support (multi-level unordered/ordered, mixed nesting)
- Add Setext headings (=== / ---)
- Add indented code blocks (4-space indent)
- Add HTML comment passthrough (<!-- -->)
- Add entity reference protection (&amp; &#169;)
- Add ordered list start attribute
- Add link title single-quote support
- Add LaTeX math formulas ($inline$ / $$block$$)
- Add footnote support ([^1] ref + [^1]: definition)
- Add definition lists (term\n: definition)
- Extend emoji map from 85 to 150+ common emojis
- Fix backtick code block matching (CommonMark spec)
- Fix italic regex character truncation (lookbehind assertions)
- Fix superscript/subscript/strikethrough ambiguity
- Fix blockquote prefix handling (>text without space)
- Enhance safeUrl filtering (additional protocol checks)
- Enhance slugify (Unicode NFKC normalization)

perf(parser): single-pass inline scanning, render cache, pre-compiled regex
- Replace 14-step chained regex with unified single-pass scanInline()
- Add cachedRenderInline() with FIFO LRU eviction (300-entry cap)
- Pre-compile 15+ static regex constants for block boundary detection
- Skip redundant style='text-align:left' on default-aligned table cells

style(parser): add CSS for footnotes, definition lists, math formulas, sup/sub

test(parser): 44 new test cases covering all v0.1.4 syntax additions (469 total)

chore: bump version 0.1.3 → 0.1.4 across all source files, docs, site pages
2026-07-24 09:35:51 +08:00

393 lines
7.9 KiB
JavaScript

/**
* MetonaEditor i18n - 国际化管理
* @module i18n
* @version 0.1.4
* @description 多语言支持、语言切换和翻译管理
*/
import { LOCALES } from './constants.js';
let currentLocale = 'zh-CN';
let localeListeners = new Set();
let fallbackLocale = 'zh-CN';
/**
* 获取当前语言
*/
export const getCurrentLocale = () => currentLocale;
/**
* 设置当前语言
*/
export const setCurrentLocale = (locale) => {
let target = locale;
if (!LOCALES[target]) {
console.warn(`Locale "${target}" not found, falling back to "${fallbackLocale}"`);
target = fallbackLocale;
}
currentLocale = target;
notifyLocaleListeners(target);
saveLocale(target);
};
/**
* 翻译函数
*/
export const t = (key, params = {}) => {
const currentTranslation = getTranslation(currentLocale, key);
if (currentTranslation !== undefined) {
return interpolate(currentTranslation, params);
}
if (currentLocale !== fallbackLocale) {
const fallbackTranslation = getTranslation(fallbackLocale, key);
if (fallbackTranslation !== undefined) {
return interpolate(fallbackTranslation, params);
}
}
console.warn(`Translation missing for key "${key}" in locale "${currentLocale}"`);
return key;
};
/**
* 获取翻译
*/
const getTranslation = (locale, key) => {
const localeData = LOCALES[locale];
if (!localeData) return undefined;
const keys = key.split('.');
let result = localeData;
for (const k of keys) {
if (result && typeof result === 'object' && k in result) {
result = result[k];
} else {
return undefined;
}
}
return typeof result === 'string' ? result : undefined;
};
/**
* 插值函数
*/
const interpolate = (str, params) => {
return str.replace(/\{(\w+)\}/g, (match, key) => {
return params[key] !== undefined ? params[key] : match;
});
};
/**
* 检查翻译是否存在
*/
export const hasTranslation = (key) => {
return getTranslation(currentLocale, key) !== undefined ||
getTranslation(fallbackLocale, key) !== undefined;
};
/**
* 获取所有翻译
*/
export const getTranslations = (locale) => {
return LOCALES[locale] || {};
};
/**
* 添加翻译
*/
export const addTranslations = (locale, translations) => {
if (!LOCALES[locale]) {
LOCALES[locale] = {};
}
deepMerge(LOCALES[locale], translations);
};
/**
* 深度合并对象
*/
const deepMerge = (target, source) => {
for (const key in source) {
if (source[key] instanceof Object && key in target && target[key] instanceof Object) {
deepMerge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
return target;
};
/**
* 获取支持的语言列表
*/
export const getSupportedLocales = () => {
return Object.keys(LOCALES);
};
/**
* 检查语言是否支持
*/
export const isLocaleSupported = (locale) => {
return locale in LOCALES;
};
/**
* 获取语言名称
*/
export const getLocaleName = (locale) => {
const names = {
'zh-CN': '简体中文',
'zh-TW': '繁體中文',
'en-US': 'English (US)',
'en-GB': 'English (UK)',
'ja': '日本語',
'ko': '한국어',
'fr': 'Français',
'de': 'Deutsch',
'es': 'Español',
'pt': 'Português',
'ru': 'Русский',
'ar': 'العربية',
'hi': 'हिन्दी',
'th': 'ไทย',
'vi': 'Tiếng Việt',
'id': 'Bahasa Indonesia',
'ms': 'Bahasa Melayu',
'tr': 'Türkçe',
'it': 'Italiano',
'nl': 'Nederlands',
'pl': 'Polski',
};
return names[locale] || locale;
};
/**
* 获取语言方向
*/
export const getLocaleDirection = (locale) => {
const rtlLocales = ['ar', 'he', 'fa', 'ur', 'yi', 'ps', 'sd', 'ug'];
return rtlLocales.includes(locale) ? 'rtl' : 'ltr';
};
/**
* 格式化数字
*/
export const formatNumber = (number, options = {}) => {
try {
return new Intl.NumberFormat(currentLocale, options).format(number);
} catch (e) {
return number.toString();
}
};
/**
* 格式化货币
*/
export const formatCurrency = (amount, currency = 'USD', options = {}) => {
try {
return new Intl.NumberFormat(currentLocale, {
style: 'currency',
currency,
...options,
}).format(amount);
} catch (e) {
return amount.toString();
}
};
/**
* 格式化日期
*/
export const formatDate = (date, options = {}) => {
try {
const dateObj = date instanceof Date ? date : new Date(date);
return new Intl.DateTimeFormat(currentLocale, options).format(dateObj);
} catch (e) {
return date.toString();
}
};
/**
* 添加语言监听器
*/
export const addLocaleListener = (listener) => {
localeListeners.add(listener);
return () => {
localeListeners.delete(listener);
};
};
/**
* 移除语言监听器
*/
export const removeLocaleListener = (listener) => {
localeListeners.delete(listener);
};
/**
* 通知语言监听器
*/
const notifyLocaleListeners = (locale) => {
localeListeners.forEach((listener) => {
try {
listener(locale);
} catch (e) {
console.error('Locale listener error:', e);
}
});
};
/**
* 清除所有语言监听器
*/
export const clearLocaleListeners = () => {
localeListeners.clear();
};
/**
* 保存语言到本地存储
*/
export const saveLocale = (locale) => {
if (typeof localStorage !== 'undefined') {
try {
localStorage.setItem('metona-editor-locale', locale);
} catch (e) {
console.warn('Failed to save locale:', e);
}
}
};
/**
* 从本地存储加载语言
*/
export const loadLocale = () => {
if (typeof localStorage !== 'undefined') {
try {
return localStorage.getItem('metona-editor-locale') || getDefaultLocale();
} catch (e) {
console.warn('Failed to load locale:', e);
}
}
return getDefaultLocale();
};
/**
* 获取默认语言
*/
export const getDefaultLocale = () => {
if (typeof navigator !== 'undefined') {
const browserLocale = navigator.language || navigator.userLanguage;
if (browserLocale && isLocaleSupported(browserLocale)) {
return browserLocale;
}
const shortLocale = browserLocale?.split('-')[0];
if (shortLocale && isLocaleSupported(shortLocale)) {
return shortLocale;
}
}
return fallbackLocale;
};
/**
* 初始化国际化系统
*/
export const initI18n = () => {
const savedLocale = loadLocale();
setCurrentLocale(savedLocale);
};
/**
* 切换语言
*/
export const switchLocale = (locale) => {
setCurrentLocale(locale);
};
/**
* 国际化工具 — 代理层
*/
export const i18nUtils = {
t,
getCurrentLocale,
setCurrentLocale,
switchLocale,
getFallbackLocale: () => fallbackLocale,
setFallbackLocale: (locale) => { fallbackLocale = locale; },
hasTranslation,
getTranslations,
addTranslations,
getSupportedLocales,
isLocaleSupported,
getLocaleName,
getLocaleDirection,
formatNumber,
formatCurrency,
formatDate,
addLocaleListener,
removeLocaleListener,
clearLocaleListeners,
initI18n,
saveLocale,
loadLocale,
getDefaultLocale,
};
/**
* 预设语言包
*/
export const presetLocales = {
'zh-CN': {
name: '简体中文',
nativeName: '简体中文',
direction: 'ltr',
translations: LOCALES['zh-CN'],
},
'en-US': {
name: 'English (US)',
nativeName: 'English (US)',
direction: 'ltr',
translations: LOCALES['en-US'],
},
};
/**
* 创建国际化管理器
*/
export const createI18nManager = () => {
return {
t,
getCurrentLocale,
setCurrentLocale,
switchLocale,
getFallbackLocale: () => fallbackLocale,
setFallbackLocale: (locale) => { fallbackLocale = locale; },
hasTranslation,
getTranslations,
addTranslations,
getSupportedLocales,
isLocaleSupported,
getLocaleName,
getLocaleDirection,
formatNumber,
formatCurrency,
formatDate,
addLocaleListener,
removeLocaleListener,
clearLocaleListeners,
initI18n,
saveLocale,
loadLocale,
getDefaultLocale,
};
};
export { i18nUtils as default };