""" InsightFlow Phase 8 - 全球化与本地化管理模块 功能: 1. 多语言支持(i18n,支持10+语言) 2. 区域数据中心配置(北美、欧洲、亚太) 3. 本地化支付方式管理 4. 时区与日历本地化 作者: InsightFlow Team """ import json import logging import sqlite3 import uuid from dataclasses import dataclass from datetime import datetime from enum import StrEnum from typing import Any try: import pytz PYTZ_AVAILABLE = True except ImportError: PYTZ_AVAILABLE = False try: from babel import Locale, dates, numbers BABEL_AVAILABLE = True except ImportError: BABEL_AVAILABLE = False logger = logging.getLogger(__name__) class LanguageCode(StrEnum): """支持的语言代码""" EN = "en" ZH_CN = "zh_CN" ZH_TW = "zh_TW" JA = "ja" KO = "ko" DE = "de" FR = "fr" ES = "es" PT = "pt" RU = "ru" AR = "ar" HI = "hi" class RegionCode(StrEnum): """区域代码""" GLOBAL = "global" NORTH_AMERICA = "na" EUROPE = "eu" ASIA_PACIFIC = "apac" CHINA = "cn" LATIN_AMERICA = "latam" MIDDLE_EAST = "me" class DataCenterRegion(StrEnum): """数据中心区域""" US_EAST = "us-east" US_WEST = "us-west" EU_WEST = "eu-west" EU_CENTRAL = "eu-central" AP_SOUTHEAST = "ap-southeast" AP_NORTHEAST = "ap-northeast" AP_SOUTH = "ap-south" CN_NORTH = "cn-north" CN_EAST = "cn-east" class PaymentProvider(StrEnum): """支付提供商""" STRIPE = "stripe" ALIPAY = "alipay" WECHAT_PAY = "wechat_pay" PAYPAL = "paypal" APPLE_PAY = "apple_pay" GOOGLE_PAY = "google_pay" KLARNA = "klarna" IDEAL = "ideal" BANCONTACT = "bancontact" GIROPAY = "giropay" SEPA = "sepa" UNIONPAY = "unionpay" class CalendarType(StrEnum): """日历类型""" GREGORIAN = "gregorian" CHINESE_LUNAR = "chinese_lunar" ISLAMIC = "islamic" HEBREW = "hebrew" INDIAN = "indian" PERSIAN = "persian" BUDDHIST = "buddhist" @dataclass class Translation: id: str key: str language: str value: str namespace: str context: str | None created_at: datetime updated_at: datetime is_reviewed: bool reviewed_by: str | None reviewed_at: datetime | None @dataclass class LanguageConfig: code: str name: str name_local: str is_rtl: bool is_active: bool is_default: bool fallback_language: str | None date_format: str time_format: str datetime_format: str number_format: str currency_format: str first_day_of_week: int calendar_type: str @dataclass class DataCenter: id: str region_code: str name: str location: str endpoint: str status: str priority: int supported_regions: list[str] capabilities: dict[str, Any] created_at: datetime updated_at: datetime @dataclass class TenantDataCenterMapping: id: str tenant_id: str primary_dc_id: str secondary_dc_id: str | None region_code: str data_residency: str created_at: datetime updated_at: datetime @dataclass class LocalizedPaymentMethod: id: str provider: str name: str name_local: dict[str, str] supported_countries: list[str] supported_currencies: list[str] is_active: bool config: dict[str, Any] icon_url: str | None display_order: int min_amount: float | None max_amount: float | None created_at: datetime updated_at: datetime @dataclass class CountryConfig: code: str code3: str name: str name_local: dict[str, str] region: str default_language: str supported_languages: list[str] default_currency: str supported_currencies: list[str] timezone: str calendar_type: str date_format: str | None time_format: str | None number_format: str | None address_format: str | None phone_format: str | None vat_rate: float | None is_active: bool @dataclass class TimezoneConfig: id: str timezone: str utc_offset: str dst_offset: str | None country_code: str region: str is_active: bool @dataclass class CurrencyConfig: code: str name: str name_local: dict[str, str] symbol: str decimal_places: int decimal_separator: str thousands_separator: str is_active: bool @dataclass class LocalizationSettings: id: str tenant_id: str default_language: str supported_languages: list[str] default_currency: str supported_currencies: list[str] default_timezone: str default_date_format: str | None default_time_format: str | None default_number_format: str | None calendar_type: str first_day_of_week: int region_code: str data_residency: str created_at: datetime updated_at: datetime class LocalizationManager: DEFAULT_LANGUAGES = { LanguageCode.EN: { "name": "English", "name_local": "English", "is_rtl": False, "date_format": "MM/dd/yyyy", "time_format": "h:mm a", "datetime_format": "MM/dd/yyyy h:mm a", "number_format": "#,##0.##", "currency_format": "$#,##0.00", "first_day_of_week": 0, "calendar_type": CalendarType.GREGORIAN.value, }, LanguageCode.ZH_CN: { "name": "Chinese (Simplified)", "name_local": "简体中文", "is_rtl": False, "date_format": "yyyy-MM-dd", "time_format": "HH:mm", "datetime_format": "yyyy-MM-dd HH:mm", "number_format": "#,##0.##", "currency_format": "¥#,##0.00", "first_day_of_week": 1, "calendar_type": CalendarType.GREGORIAN.value, }, LanguageCode.ZH_TW: { "name": "Chinese (Traditional)", "name_local": "繁體中文", "is_rtl": False, "date_format": "yyyy/MM/dd", "time_format": "HH:mm", "datetime_format": "yyyy/MM/dd HH:mm", "number_format": "#,##0.##", "currency_format": "NT$#,##0.00", "first_day_of_week": 0, "calendar_type": CalendarType.GREGORIAN.value, }, LanguageCode.JA: { "name": "Japanese", "name_local": "日本語", "is_rtl": False, "date_format": "yyyy/MM/dd", "time_format": "HH:mm", "datetime_format": "yyyy/MM/dd HH:mm", "number_format": "#,##0.##", "currency_format": "¥#,##0", "first_day_of_week": 0, "calendar_type": CalendarType.GREGORIAN.value, }, LanguageCode.KO: { "name": "Korean", "name_local": "한국어", "is_rtl": False, "date_format": "yyyy. MM. dd", "time_format": "HH:mm", "datetime_format": "yyyy. MM. dd HH:mm", "number_format": "#,##0.##", "currency_format": "₩#,##0", "first_day_of_week": 0, "calendar_type": CalendarType.GREGORIAN.value, }, LanguageCode.DE: { "name": "German", "name_local": "Deutsch", "is_rtl": False, "date_format": "dd.MM.yyyy", "time_format": "HH:mm", "datetime_format": "dd.MM.yyyy HH:mm", "number_format": "#,##0.##", "currency_format": "#,##0.00 €", "first_day_of_week": 1, "calendar_type": CalendarType.GREGORIAN.value, }, LanguageCode.FR: { "name": "French", "name_local": "Français", "is_rtl": False, "date_format": "dd/MM/yyyy", "time_format": "HH:mm", "datetime_format": "dd/MM/yyyy HH:mm", "number_format": "#,##0.##", "currency_format": "#,##0.00 €", "first_day_of_week": 1, "calendar_type": CalendarType.GREGORIAN.value, }, LanguageCode.ES: { "name": "Spanish", "name_local": "Español", "is_rtl": False, "date_format": "dd/MM/yyyy", "time_format": "HH:mm", "datetime_format": "dd/MM/yyyy HH:mm", "number_format": "#,##0.##", "currency_format": "#,##0.00 €", "first_day_of_week": 1, "calendar_type": CalendarType.GREGORIAN.value, }, LanguageCode.PT: { "name": "Portuguese", "name_local": "Português", "is_rtl": False, "date_format": "dd/MM/yyyy", "time_format": "HH:mm", "datetime_format": "dd/MM/yyyy HH:mm", "number_format": "#,##0.##", "currency_format": "R$#,##0.00", "first_day_of_week": 0, "calendar_type": CalendarType.GREGORIAN.value, }, LanguageCode.RU: { "name": "Russian", "name_local": "Русский", "is_rtl": False, "date_format": "dd.MM.yyyy", "time_format": "HH:mm", "datetime_format": "dd.MM.yyyy HH:mm", "number_format": "#,##0.##", "currency_format": "#,##0.00 ₽", "first_day_of_week": 1, "calendar_type": CalendarType.GREGORIAN.value, }, LanguageCode.AR: { "name": "Arabic", "name_local": "العربية", "is_rtl": True, "date_format": "dd/MM/yyyy", "time_format": "hh:mm a", "datetime_format": "dd/MM/yyyy hh:mm a", "number_format": "#,##0.##", "currency_format": "#,##0.00 ر.س", "first_day_of_week": 6, "calendar_type": CalendarType.ISLAMIC.value, }, LanguageCode.HI: { "name": "Hindi", "name_local": "हिन्दी", "is_rtl": False, "date_format": "dd/MM/yyyy", "time_format": "hh:mm a", "datetime_format": "dd/MM/yyyy hh:mm a", "number_format": "#,##0.##", "currency_format": "₹#,##0.00", "first_day_of_week": 0, "calendar_type": CalendarType.INDIAN.value, }, } DEFAULT_DATA_CENTERS = { DataCenterRegion.US_EAST: { "name": "US East (Virginia)", "location": "Virginia, USA", "endpoint": "https://api-us-east.insightflow.io", "priority": 1, "supported_regions": [RegionCode.NORTH_AMERICA.value, RegionCode.GLOBAL.value], "capabilities": {"storage": True, "compute": True, "ml": True}, }, DataCenterRegion.US_WEST: { "name": "US West (California)", "location": "California, USA", "endpoint": "https://api-us-west.insightflow.io", "priority": 2, "supported_regions": [RegionCode.NORTH_AMERICA.value, RegionCode.GLOBAL.value], "capabilities": {"storage": True, "compute": True, "ml": False}, }, DataCenterRegion.EU_WEST: { "name": "EU West (Ireland)", "location": "Dublin, Ireland", "endpoint": "https://api-eu-west.insightflow.io", "priority": 1, "supported_regions": [RegionCode.EUROPE.value, RegionCode.GLOBAL.value], "capabilities": {"storage": True, "compute": True, "ml": True}, }, DataCenterRegion.EU_CENTRAL: { "name": "EU Central (Frankfurt)", "location": "Frankfurt, Germany", "endpoint": "https://api-eu-central.insightflow.io", "priority": 2, "supported_regions": [RegionCode.EUROPE.value, RegionCode.GLOBAL.value], "capabilities": {"storage": True, "compute": True, "ml": False}, }, DataCenterRegion.AP_SOUTHEAST: { "name": "Asia Pacific (Singapore)", "location": "Singapore", "endpoint": "https://api-ap-southeast.insightflow.io", "priority": 1, "supported_regions": [RegionCode.ASIA_PACIFIC.value, RegionCode.GLOBAL.value], "capabilities": {"storage": True, "compute": True, "ml": True}, }, DataCenterRegion.AP_NORTHEAST: { "name": "Asia Pacific (Tokyo)", "location": "Tokyo, Japan", "endpoint": "https://api-ap-northeast.insightflow.io", "priority": 2, "supported_regions": [RegionCode.ASIA_PACIFIC.value, RegionCode.GLOBAL.value], "capabilities": {"storage": True, "compute": True, "ml": False}, }, DataCenterRegion.AP_SOUTH: { "name": "Asia Pacific (Mumbai)", "location": "Mumbai, India", "endpoint": "https://api-ap-south.insightflow.io", "priority": 3, "supported_regions": [RegionCode.ASIA_PACIFIC.value, RegionCode.GLOBAL.value], "capabilities": {"storage": True, "compute": True, "ml": False}, }, DataCenterRegion.CN_NORTH: { "name": "China (Beijing)", "location": "Beijing, China", "endpoint": "https://api-cn-north.insightflow.cn", "priority": 1, "supported_regions": [RegionCode.CHINA.value], "capabilities": {"storage": True, "compute": True, "ml": True}, }, DataCenterRegion.CN_EAST: { "name": "China (Shanghai)", "location": "Shanghai, China", "endpoint": "https://api-cn-east.insightflow.cn", "priority": 2, "supported_regions": [RegionCode.CHINA.value], "capabilities": {"storage": True, "compute": True, "ml": False}, }, } DEFAULT_PAYMENT_METHODS = { PaymentProvider.STRIPE: { "name": "Credit Card", "name_local": { "en": "Credit Card", "zh_CN": "信用卡", "zh_TW": "信用卡", "ja": "クレジットカード", "ko": "신용카드", "de": "Kreditkarte", "fr": "Carte de crédit", "es": "Tarjeta de crédito", "pt": "Cartão de crédito", "ru": "Кредитная карта", }, "supported_countries": ["*"], "supported_currencies": ["USD", "EUR", "GBP", "CAD", "AUD", "JPY"], "display_order": 1, }, PaymentProvider.ALIPAY: { "name": "Alipay", "name_local": {"en": "Alipay", "zh_CN": "支付宝", "zh_TW": "支付寶"}, "supported_countries": ["CN", "HK", "MO", "TW", "SG", "MY", "TH"], "supported_currencies": ["CNY", "HKD", "USD"], "display_order": 2, }, PaymentProvider.WECHAT_PAY: { "name": "WeChat Pay", "name_local": {"en": "WeChat Pay", "zh_CN": "微信支付", "zh_TW": "微信支付"}, "supported_countries": ["CN", "HK", "MO"], "supported_currencies": ["CNY", "HKD"], "display_order": 3, }, PaymentProvider.PAYPAL: { "name": "PayPal", "name_local": {"en": "PayPal"}, "supported_countries": ["*"], "supported_currencies": ["USD", "EUR", "GBP", "CAD", "AUD", "JPY"], "display_order": 4, }, PaymentProvider.APPLE_PAY: { "name": "Apple Pay", "name_local": {"en": "Apple Pay"}, "supported_countries": ["US", "CA", "GB", "AU", "JP", "DE", "FR"], "supported_currencies": ["USD", "EUR", "GBP", "CAD", "AUD", "JPY"], "display_order": 5, }, PaymentProvider.GOOGLE_PAY: { "name": "Google Pay", "name_local": {"en": "Google Pay"}, "supported_countries": ["US", "CA", "GB", "AU", "JP", "DE", "FR"], "supported_currencies": ["USD", "EUR", "GBP", "CAD", "AUD", "JPY"], "display_order": 6, }, PaymentProvider.KLARNA: { "name": "Klarna", "name_local": {"en": "Klarna", "de": "Klarna", "fr": "Klarna"}, "supported_countries": ["DE", "AT", "NL", "BE", "FI", "SE", "NO", "DK", "GB"], "supported_currencies": ["EUR", "GBP"], "display_order": 7, }, PaymentProvider.IDEAL: { "name": "iDEAL", "name_local": {"en": "iDEAL", "de": "iDEAL"}, "supported_countries": ["NL"], "supported_currencies": ["EUR"], "display_order": 8, }, PaymentProvider.BANCONTACT: { "name": "Bancontact", "name_local": {"en": "Bancontact", "de": "Bancontact"}, "supported_countries": ["BE"], "supported_currencies": ["EUR"], "display_order": 9, }, PaymentProvider.GIROPAY: { "name": "giropay", "name_local": {"en": "giropay", "de": "giropay"}, "supported_countries": ["DE"], "supported_currencies": ["EUR"], "display_order": 10, }, PaymentProvider.SEPA: { "name": "SEPA Direct Debit", "name_local": {"en": "SEPA Direct Debit", "de": "SEPA-Lastschrift"}, "supported_countries": ["DE", "AT", "NL", "BE", "FR", "ES", "IT"], "supported_currencies": ["EUR"], "display_order": 11, }, PaymentProvider.UNIONPAY: { "name": "UnionPay", "name_local": {"en": "UnionPay", "zh_CN": "银联", "zh_TW": "銀聯"}, "supported_countries": ["CN", "HK", "MO", "TW"], "supported_currencies": ["CNY", "USD"], "display_order": 12, }, } DEFAULT_COUNTRIES = { "US": { "name": "United States", "name_local": {"en": "United States"}, "region": RegionCode.NORTH_AMERICA.value, "default_language": LanguageCode.EN.value, "supported_languages": [LanguageCode.EN.value], "default_currency": "USD", "supported_currencies": ["USD"], "timezone": "America/New_York", "calendar_type": CalendarType.GREGORIAN.value, "vat_rate": None, }, "CN": { "name": "China", "name_local": {"zh_CN": "中国"}, "region": RegionCode.CHINA.value, "default_language": LanguageCode.ZH_CN.value, "supported_languages": [LanguageCode.ZH_CN.value, LanguageCode.EN.value], "default_currency": "CNY", "supported_currencies": ["CNY", "USD"], "timezone": "Asia/Shanghai", "calendar_type": CalendarType.GREGORIAN.value, "vat_rate": 0.13, }, "JP": { "name": "Japan", "name_local": {"ja": "日本"}, "region": RegionCode.ASIA_PACIFIC.value, "default_language": LanguageCode.JA.value, "supported_languages": [LanguageCode.JA.value, LanguageCode.EN.value], "default_currency": "JPY", "supported_currencies": ["JPY", "USD"], "timezone": "Asia/Tokyo", "calendar_type": CalendarType.GREGORIAN.value, "vat_rate": 0.10, }, "DE": { "name": "Germany", "name_local": {"de": "Deutschland"}, "region": RegionCode.EUROPE.value, "default_language": LanguageCode.DE.value, "supported_languages": [LanguageCode.DE.value, LanguageCode.EN.value], "default_currency": "EUR", "supported_currencies": ["EUR", "USD"], "timezone": "Europe/Berlin", "calendar_type": CalendarType.GREGORIAN.value, "vat_rate": 0.19, }, "GB": { "name": "United Kingdom", "name_local": {"en": "United Kingdom"}, "region": RegionCode.EUROPE.value, "default_language": LanguageCode.EN.value, "supported_languages": [LanguageCode.EN.value], "default_currency": "GBP", "supported_currencies": ["GBP", "EUR", "USD"], "timezone": "Europe/London", "calendar_type": CalendarType.GREGORIAN.value, "vat_rate": 0.20, }, "FR": { "name": "France", "name_local": {"fr": "France"}, "region": RegionCode.EUROPE.value, "default_language": LanguageCode.FR.value, "supported_languages": [LanguageCode.FR.value, LanguageCode.EN.value], "default_currency": "EUR", "supported_currencies": ["EUR", "USD"], "timezone": "Europe/Paris", "calendar_type": CalendarType.GREGORIAN.value, "vat_rate": 0.20, }, "SG": { "name": "Singapore", "name_local": {"en": "Singapore"}, "region": RegionCode.ASIA_PACIFIC.value, "default_language": LanguageCode.EN.value, "supported_languages": [LanguageCode.EN.value, LanguageCode.ZH_CN.value], "default_currency": "SGD", "supported_currencies": ["SGD", "USD"], "timezone": "Asia/Singapore", "calendar_type": CalendarType.GREGORIAN.value, "vat_rate": 0.08, }, "AU": { "name": "Australia", "name_local": {"en": "Australia"}, "region": RegionCode.ASIA_PACIFIC.value, "default_language": LanguageCode.EN.value, "supported_languages": [LanguageCode.EN.value], "default_currency": "AUD", "supported_currencies": ["AUD", "USD"], "timezone": "Australia/Sydney", "calendar_type": CalendarType.GREGORIAN.value, "vat_rate": 0.10, }, "CA": { "name": "Canada", "name_local": {"en": "Canada", "fr": "Canada"}, "region": RegionCode.NORTH_AMERICA.value, "default_language": LanguageCode.EN.value, "supported_languages": [LanguageCode.EN.value, LanguageCode.FR.value], "default_currency": "CAD", "supported_currencies": ["CAD", "USD"], "timezone": "America/Toronto", "calendar_type": CalendarType.GREGORIAN.value, "vat_rate": 0.05, }, "BR": { "name": "Brazil", "name_local": {"pt": "Brasil"}, "region": RegionCode.LATIN_AMERICA.value, "default_language": LanguageCode.PT.value, "supported_languages": [LanguageCode.PT.value, LanguageCode.EN.value], "default_currency": "BRL", "supported_currencies": ["BRL", "USD"], "timezone": "America/Sao_Paulo", "calendar_type": CalendarType.GREGORIAN.value, "vat_rate": 0.17, }, "IN": { "name": "India", "name_local": {"hi": "भारत"}, "region": RegionCode.ASIA_PACIFIC.value, "default_language": LanguageCode.EN.value, "supported_languages": [LanguageCode.EN.value, LanguageCode.HI.value], "default_currency": "INR", "supported_currencies": ["INR", "USD"], "timezone": "Asia/Kolkata", "calendar_type": CalendarType.GREGORIAN.value, "vat_rate": 0.18, }, "AE": { "name": "United Arab Emirates", "name_local": {"ar": "الإمارات العربية المتحدة"}, "region": RegionCode.MIDDLE_EAST.value, "default_language": LanguageCode.EN.value, "supported_languages": [LanguageCode.EN.value, LanguageCode.AR.value], "default_currency": "AED", "supported_currencies": ["AED", "USD"], "timezone": "Asia/Dubai", "calendar_type": CalendarType.ISLAMIC.value, "vat_rate": 0.05, }, } def __init__(self, db_path: str = "insightflow.db"): self.db_path = db_path self._is_memory_db = db_path == ":memory:" self._conn = None self._init_db() self._init_default_data() def _get_connection(self) -> sqlite3.Connection: if self._is_memory_db: if self._conn is None: self._conn = sqlite3.connect(self.db_path) self._conn.row_factory = sqlite3.Row return self._conn conn = sqlite3.connect(self.db_path) conn.row_factory = sqlite3.Row return conn def _close_if_file_db(self, conn): if not self._is_memory_db: conn.close() def _init_db(self): conn = self._get_connection() try: cursor = conn.cursor() cursor.execute(""" CREATE TABLE IF NOT EXISTS translations ( id TEXT PRIMARY KEY, key TEXT NOT NULL, language TEXT NOT NULL, value TEXT NOT NULL, namespace TEXT DEFAULT 'common', context TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, is_reviewed INTEGER DEFAULT 0, reviewed_by TEXT, reviewed_at TIMESTAMP, UNIQUE(key, language, namespace) ) """) cursor.execute(""" CREATE TABLE IF NOT EXISTS language_configs ( code TEXT PRIMARY KEY, name TEXT NOT NULL, name_local TEXT NOT NULL, is_rtl INTEGER DEFAULT 0, is_active INTEGER DEFAULT 1, is_default INTEGER DEFAULT 0, fallback_language TEXT, date_format TEXT, time_format TEXT, datetime_format TEXT, number_format TEXT, currency_format TEXT, first_day_of_week INTEGER DEFAULT 1, calendar_type TEXT DEFAULT 'gregorian' ) """) cursor.execute(""" CREATE TABLE IF NOT EXISTS data_centers ( id TEXT PRIMARY KEY, region_code TEXT NOT NULL UNIQUE, name TEXT NOT NULL, location TEXT NOT NULL, endpoint TEXT NOT NULL, status TEXT DEFAULT 'active', priority INTEGER DEFAULT 1, supported_regions TEXT DEFAULT '[]', capabilities TEXT DEFAULT '{}', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) """) cursor.execute(""" CREATE TABLE IF NOT EXISTS tenant_data_center_mappings ( id TEXT PRIMARY KEY, tenant_id TEXT NOT NULL UNIQUE, primary_dc_id TEXT NOT NULL, secondary_dc_id TEXT, region_code TEXT NOT NULL, data_residency TEXT DEFAULT 'regional', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE, FOREIGN KEY (primary_dc_id) REFERENCES data_centers(id), FOREIGN KEY (secondary_dc_id) REFERENCES data_centers(id) ) """) cursor.execute(""" CREATE TABLE IF NOT EXISTS localized_payment_methods ( id TEXT PRIMARY KEY, provider TEXT NOT NULL UNIQUE, name TEXT NOT NULL, name_local TEXT DEFAULT '{}', supported_countries TEXT DEFAULT '[]', supported_currencies TEXT DEFAULT '[]', is_active INTEGER DEFAULT 1, config TEXT DEFAULT '{}', icon_url TEXT, display_order INTEGER DEFAULT 0, min_amount REAL, max_amount REAL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) """) cursor.execute(""" CREATE TABLE IF NOT EXISTS country_configs ( code TEXT PRIMARY KEY, code3 TEXT NOT NULL, name TEXT NOT NULL, name_local TEXT DEFAULT '{}', region TEXT NOT NULL, default_language TEXT NOT NULL, supported_languages TEXT DEFAULT '[]', default_currency TEXT NOT NULL, supported_currencies TEXT DEFAULT '[]', timezone TEXT NOT NULL, calendar_type TEXT DEFAULT 'gregorian', date_format TEXT, time_format TEXT, number_format TEXT, address_format TEXT, phone_format TEXT, vat_rate REAL, is_active INTEGER DEFAULT 1 ) """) cursor.execute(""" CREATE TABLE IF NOT EXISTS timezone_configs ( id TEXT PRIMARY KEY, timezone TEXT NOT NULL UNIQUE, utc_offset TEXT NOT NULL, dst_offset TEXT, country_code TEXT NOT NULL, region TEXT NOT NULL, is_active INTEGER DEFAULT 1 ) """) cursor.execute(""" CREATE TABLE IF NOT EXISTS currency_configs ( code TEXT PRIMARY KEY, name TEXT NOT NULL, name_local TEXT DEFAULT '{}', symbol TEXT NOT NULL, decimal_places INTEGER DEFAULT 2, decimal_separator TEXT DEFAULT '.', thousands_separator TEXT DEFAULT ',', is_active INTEGER DEFAULT 1 ) """) cursor.execute(""" CREATE TABLE IF NOT EXISTS localization_settings ( id TEXT PRIMARY KEY, tenant_id TEXT NOT NULL UNIQUE, default_language TEXT DEFAULT 'en', supported_languages TEXT DEFAULT '["en"]', default_currency TEXT DEFAULT 'USD', supported_currencies TEXT DEFAULT '["USD"]', default_timezone TEXT DEFAULT 'UTC', default_date_format TEXT, default_time_format TEXT, default_number_format TEXT, calendar_type TEXT DEFAULT 'gregorian', first_day_of_week INTEGER DEFAULT 1, region_code TEXT DEFAULT 'global', data_residency TEXT DEFAULT 'regional', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE ) """) cursor.execute("CREATE INDEX IF NOT EXISTS idx_translations_key ON translations(key)") cursor.execute( "CREATE INDEX IF NOT EXISTS idx_translations_lang ON translations(language)" ) cursor.execute( "CREATE INDEX IF NOT EXISTS idx_translations_ns ON translations(namespace)" ) cursor.execute("CREATE INDEX IF NOT EXISTS idx_dc_region ON data_centers(region_code)") cursor.execute("CREATE INDEX IF NOT EXISTS idx_dc_status ON data_centers(status)") cursor.execute( "CREATE INDEX IF NOT EXISTS idx_tenant_dc ON tenant_data_center_mappings(tenant_id)" ) cursor.execute( "CREATE INDEX IF NOT EXISTS idx_payment_provider ON localized_payment_methods(provider)" ) cursor.execute( "CREATE INDEX IF NOT EXISTS idx_payment_active ON localized_payment_methods(is_active)" ) cursor.execute( "CREATE INDEX IF NOT EXISTS idx_country_region ON country_configs(region)" ) cursor.execute( "CREATE INDEX IF NOT EXISTS idx_tz_country ON timezone_configs(country_code)" ) cursor.execute( "CREATE INDEX IF NOT EXISTS idx_locale_settings_tenant ON localization_settings(tenant_id)" ) conn.commit() logger.info("Localization tables initialized successfully") except Exception as e: logger.error(f"Error initializing localization tables: {e}") raise finally: self._close_if_file_db(conn) def _init_default_data(self): conn = self._get_connection() try: cursor = conn.cursor() for code, config in self.DEFAULT_LANGUAGES.items(): cursor.execute( """ INSERT OR IGNORE INTO language_configs (code, name, name_local, is_rtl, is_active, is_default, fallback_language, date_format, time_format, datetime_format, number_format, currency_format, first_day_of_week, calendar_type) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( code.value, config["name"], config["name_local"], int(config["is_rtl"]), 1, 1 if code == LanguageCode.EN else 0, "en" if code != LanguageCode.EN else None, config["date_format"], config["time_format"], config["datetime_format"], config["number_format"], config["currency_format"], config["first_day_of_week"], config["calendar_type"], ), ) for region_code, config in self.DEFAULT_DATA_CENTERS.items(): dc_id = str(uuid.uuid4()) cursor.execute( """ INSERT OR IGNORE INTO data_centers (id, region_code, name, location, endpoint, priority, supported_regions, capabilities) VALUES (?, ?, ?, ?, ?, ?, ?, ?) """, ( dc_id, region_code.value, config["name"], config["location"], config["endpoint"], config["priority"], json.dumps(config["supported_regions"]), json.dumps(config["capabilities"]), ), ) for provider, config in self.DEFAULT_PAYMENT_METHODS.items(): pm_id = str(uuid.uuid4()) cursor.execute( """ INSERT OR IGNORE INTO localized_payment_methods (id, provider, name, name_local, supported_countries, supported_currencies, is_active, display_order) VALUES (?, ?, ?, ?, ?, ?, ?, ?) """, ( pm_id, provider.value, config["name"], json.dumps(config["name_local"]), json.dumps(config["supported_countries"]), json.dumps(config["supported_currencies"]), 1, config["display_order"], ), ) for code, config in self.DEFAULT_COUNTRIES.items(): cursor.execute( """ INSERT OR IGNORE INTO country_configs (code, code3, name, name_local, region, default_language, supported_languages, default_currency, supported_currencies, timezone, calendar_type, vat_rate) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( code, code, config["name"], json.dumps(config["name_local"]), config["region"], config["default_language"], json.dumps(config["supported_languages"]), config["default_currency"], json.dumps(config["supported_currencies"]), config["timezone"], config["calendar_type"], config["vat_rate"], ), ) conn.commit() logger.info("Default localization data initialized") except Exception as e: logger.error(f"Error initializing default localization data: {e}") finally: self._close_if_file_db(conn) def get_translation( self, key: str, language: str, namespace: str = "common", fallback: bool = True ) -> str | None: conn = self._get_connection() try: cursor = conn.cursor() cursor.execute( "SELECT value FROM translations WHERE key = ? AND language = ? AND namespace = ?", (key, language, namespace), ) row = cursor.fetchone() if row: return row["value"] if fallback: lang_config = self.get_language_config(language) if lang_config and lang_config.fallback_language: return self.get_translation( key, lang_config.fallback_language, namespace, False ) if language != "en": return self.get_translation(key, "en", namespace, False) return None finally: self._close_if_file_db(conn) def set_translation( self, key: str, language: str, value: str, namespace: str = "common", context: str | None = None, ) -> Translation: conn = self._get_connection() try: translation_id = str(uuid.uuid4()) now = datetime.now() cursor = conn.cursor() cursor.execute( """ INSERT INTO translations (id, key, language, value, namespace, context, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(key, language, namespace) DO UPDATE SET value = excluded.value, context = excluded.context, updated_at = excluded.updated_at, is_reviewed = 0 """, (translation_id, key, language, value, namespace, context, now, now), ) conn.commit() return self._get_translation_internal(conn, key, language, namespace) finally: self._close_if_file_db(conn) def _get_translation_internal( self, conn: sqlite3.Connection, key: str, language: str, namespace: str ) -> Translation | None: cursor = conn.cursor() cursor.execute( "SELECT * FROM translations WHERE key = ? AND language = ? AND namespace = ?", (key, language, namespace), ) row = cursor.fetchone() if row: return self._row_to_translation(row) return None def delete_translation(self, key: str, language: str, namespace: str = "common") -> bool: conn = self._get_connection() try: cursor = conn.cursor() cursor.execute( "DELETE FROM translations WHERE key = ? AND language = ? AND namespace = ?", (key, language, namespace), ) conn.commit() return cursor.rowcount > 0 finally: self._close_if_file_db(conn) def list_translations( self, language: str | None = None, namespace: str | None = None, limit: int = 1000, offset: int = 0, ) -> list[Translation]: conn = self._get_connection() try: cursor = conn.cursor() query = "SELECT * FROM translations WHERE 1=1" params = [] if language: query += " AND language = ?" params.append(language) if namespace: query += " AND namespace = ?" params.append(namespace) query += " ORDER BY namespace, key LIMIT ? OFFSET ?" params.extend([limit, offset]) cursor.execute(query, params) rows = cursor.fetchall() return [self._row_to_translation(row) for row in rows] finally: self._close_if_file_db(conn) def get_language_config(self, code: str) -> LanguageConfig | None: conn = self._get_connection() try: cursor = conn.cursor() cursor.execute("SELECT * FROM language_configs WHERE code = ?", (code,)) row = cursor.fetchone() if row: return self._row_to_language_config(row) return None finally: self._close_if_file_db(conn) def list_language_configs(self, active_only: bool = True) -> list[LanguageConfig]: conn = self._get_connection() try: cursor = conn.cursor() query = "SELECT * FROM language_configs" if active_only: query += " WHERE is_active = 1" query += " ORDER BY name" cursor.execute(query) rows = cursor.fetchall() return [self._row_to_language_config(row) for row in rows] finally: self._close_if_file_db(conn) def get_data_center(self, dc_id: str) -> DataCenter | None: conn = self._get_connection() try: cursor = conn.cursor() cursor.execute("SELECT * FROM data_centers WHERE id = ?", (dc_id,)) row = cursor.fetchone() if row: return self._row_to_data_center(row) return None finally: self._close_if_file_db(conn) def get_data_center_by_region(self, region_code: str) -> DataCenter | None: conn = self._get_connection() try: cursor = conn.cursor() cursor.execute("SELECT * FROM data_centers WHERE region_code = ?", (region_code,)) row = cursor.fetchone() if row: return self._row_to_data_center(row) return None finally: self._close_if_file_db(conn) def list_data_centers( self, status: str | None = None, region: str | None = None ) -> list[DataCenter]: conn = self._get_connection() try: cursor = conn.cursor() query = "SELECT * FROM data_centers WHERE 1=1" params = [] if status: query += " AND status = ?" params.append(status) if region: query += " AND supported_regions LIKE ?" params.append(f'%"{region}"%') query += " ORDER BY priority" cursor.execute(query, params) rows = cursor.fetchall() return [self._row_to_data_center(row) for row in rows] finally: self._close_if_file_db(conn) def get_tenant_data_center(self, tenant_id: str) -> TenantDataCenterMapping | None: conn = self._get_connection() try: cursor = conn.cursor() cursor.execute( "SELECT * FROM tenant_data_center_mappings WHERE tenant_id = ?", (tenant_id,) ) row = cursor.fetchone() if row: return self._row_to_tenant_dc_mapping(row) return None finally: self._close_if_file_db(conn) def set_tenant_data_center( self, tenant_id: str, region_code: str, data_residency: str = "regional" ) -> TenantDataCenterMapping: conn = self._get_connection() try: cursor = conn.cursor() cursor.execute( """ SELECT * FROM data_centers WHERE supported_regions LIKE ? AND status = 'active' ORDER BY priority LIMIT 1 """, (f'%"{region_code}"%',), ) row = cursor.fetchone() if not row: cursor.execute(""" SELECT * FROM data_centers WHERE supported_regions LIKE '%"global"%' AND status = 'active' ORDER BY priority LIMIT 1 """) row = cursor.fetchone() if not row: raise ValueError(f"No data center available for region: {region_code}") primary_dc_id = row["id"] cursor.execute( """ SELECT * FROM data_centers WHERE id != ? AND status = 'active' ORDER BY priority LIMIT 1 """, (primary_dc_id,), ) secondary_row = cursor.fetchone() secondary_dc_id = secondary_row["id"] if secondary_row else None mapping_id = str(uuid.uuid4()) now = datetime.now() cursor.execute( """ INSERT INTO tenant_data_center_mappings (id, tenant_id, primary_dc_id, secondary_dc_id, region_code, data_residency, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(tenant_id) DO UPDATE SET primary_dc_id = excluded.primary_dc_id, secondary_dc_id = excluded.secondary_dc_id, region_code = excluded.region_code, data_residency = excluded.data_residency, updated_at = excluded.updated_at """, ( mapping_id, tenant_id, primary_dc_id, secondary_dc_id, region_code, data_residency, now, now, ), ) conn.commit() return self.get_tenant_data_center(tenant_id) finally: self._close_if_file_db(conn) def get_payment_method(self, provider: str) -> LocalizedPaymentMethod | None: conn = self._get_connection() try: cursor = conn.cursor() cursor.execute( "SELECT * FROM localized_payment_methods WHERE provider = ?", (provider,) ) row = cursor.fetchone() if row: return self._row_to_payment_method(row) return None finally: self._close_if_file_db(conn) def list_payment_methods( self, country_code: str | None = None, currency: str | None = None, active_only: bool = True ) -> list[LocalizedPaymentMethod]: conn = self._get_connection() try: cursor = conn.cursor() query = "SELECT * FROM localized_payment_methods WHERE 1=1" params = [] if active_only: query += " AND is_active = 1" if country_code: query += " AND (supported_countries LIKE ? OR supported_countries LIKE '%\"*\"%')" params.append(f'%"{country_code}"%') if currency: query += " AND supported_currencies LIKE ?" params.append(f'%"{currency}"%') query += " ORDER BY display_order" cursor.execute(query, params) rows = cursor.fetchall() return [self._row_to_payment_method(row) for row in rows] finally: self._close_if_file_db(conn) def get_localized_payment_methods( self, country_code: str, language: str = "en" ) -> list[dict[str, Any]]: methods = self.list_payment_methods(country_code=country_code) result = [] for method in methods: name_local = method.name_local.get(language, method.name) result.append( { "id": method.id, "provider": method.provider, "name": name_local, "icon_url": method.icon_url, "min_amount": method.min_amount, "max_amount": method.max_amount, "supported_currencies": method.supported_currencies, } ) return result def get_country_config(self, code: str) -> CountryConfig | None: conn = self._get_connection() try: cursor = conn.cursor() cursor.execute("SELECT * FROM country_configs WHERE code = ?", (code,)) row = cursor.fetchone() if row: return self._row_to_country_config(row) return None finally: self._close_if_file_db(conn) def list_country_configs( self, region: str | None = None, active_only: bool = True ) -> list[CountryConfig]: conn = self._get_connection() try: cursor = conn.cursor() query = "SELECT * FROM country_configs WHERE 1=1" params = [] if active_only: query += " AND is_active = 1" if region: query += " AND region = ?" params.append(region) query += " ORDER BY name" cursor.execute(query, params) rows = cursor.fetchall() return [self._row_to_country_config(row) for row in rows] finally: self._close_if_file_db(conn) def format_datetime( self, dt: datetime, language: str = "en", timezone: str | None = None, format_type: str = "datetime", ) -> str: try: if timezone and PYTZ_AVAILABLE: tz = pytz.timezone(timezone) if dt.tzinfo is None: dt = pytz.UTC.localize(dt) dt = dt.astimezone(tz) lang_config = self.get_language_config(language) if not lang_config: lang_config = self.get_language_config("en") if format_type == "date": fmt = lang_config.date_format if lang_config else "%Y-%m-%d" elif format_type == "time": fmt = lang_config.time_format if lang_config else "%H:%M" else: fmt = lang_config.datetime_format if lang_config else "%Y-%m-%d %H:%M" if BABEL_AVAILABLE: try: locale = Locale.parse(language.replace("_", "-")) if format_type == "date": return dates.format_date(dt, locale=locale) elif format_type == "time": return dates.format_time(dt, locale=locale) else: return dates.format_datetime(dt, locale=locale) except BaseException: pass return dt.strftime(fmt) except Exception as e: logger.error(f"Error formatting datetime: {e}") return dt.strftime("%Y-%m-%d %H:%M") def format_number( self, number: float, language: str = "en", decimal_places: int | None = None ) -> str: try: if BABEL_AVAILABLE: try: locale = Locale.parse(language.replace("_", "-")) return numbers.format_decimal( number, locale=locale, decimal_quantization=(decimal_places is not None) ) except BaseException: pass if decimal_places is not None: return f"{number:,.{decimal_places}f}" return f"{number:,}" except Exception as e: logger.error(f"Error formatting number: {e}") return str(number) def format_currency(self, amount: float, currency: str, language: str = "en") -> str: try: if BABEL_AVAILABLE: try: locale = Locale.parse(language.replace("_", "-")) return numbers.format_currency(amount, currency, locale=locale) except BaseException: pass return f"{currency} {amount:,.2f}" except Exception as e: logger.error(f"Error formatting currency: {e}") return f"{currency} {amount:.2f}" def convert_timezone(self, dt: datetime, from_tz: str, to_tz: str) -> datetime: try: if PYTZ_AVAILABLE: from_zone = pytz.timezone(from_tz) to_zone = pytz.timezone(to_tz) if dt.tzinfo is None: dt = from_zone.localize(dt) return dt.astimezone(to_zone) return dt except Exception as e: logger.error(f"Error converting timezone: {e}") return dt def get_calendar_info(self, calendar_type: str, year: int, month: int) -> dict[str, Any]: import calendar cal = calendar.Calendar() month_days = cal.monthdayscalendar(year, month) return { "calendar_type": calendar_type, "year": year, "month": month, "month_name": calendar.month_name[month], "days_in_month": calendar.monthrange(year, month)[1], "first_day_of_week": calendar.monthrange(year, month)[0], "weeks": month_days, } def get_localization_settings(self, tenant_id: str) -> LocalizationSettings | None: conn = self._get_connection() try: cursor = conn.cursor() cursor.execute("SELECT * FROM localization_settings WHERE tenant_id = ?", (tenant_id,)) row = cursor.fetchone() if row: return self._row_to_localization_settings(row) return None finally: self._close_if_file_db(conn) def create_localization_settings( self, tenant_id: str, default_language: str = "en", supported_languages: list[str] | None = None, default_currency: str = "USD", supported_currencies: list[str] | None = None, default_timezone: str = "UTC", region_code: str = "global", data_residency: str = "regional", ) -> LocalizationSettings: conn = self._get_connection() try: settings_id = str(uuid.uuid4()) now = datetime.now() supported_languages = supported_languages or [default_language] supported_currencies = supported_currencies or [default_currency] lang_config = self.get_language_config(default_language) cursor = conn.cursor() cursor.execute( """ INSERT INTO localization_settings (id, tenant_id, default_language, supported_languages, default_currency, supported_currencies, default_timezone, default_date_format, default_time_format, default_number_format, calendar_type, first_day_of_week, region_code, data_residency, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( settings_id, tenant_id, default_language, json.dumps(supported_languages), default_currency, json.dumps(supported_currencies), default_timezone, lang_config.date_format if lang_config else "%Y-%m-%d", lang_config.time_format if lang_config else "%H:%M", lang_config.number_format if lang_config else "#,##0.##", lang_config.calendar_type if lang_config else CalendarType.GREGORIAN.value, lang_config.first_day_of_week if lang_config else 1, region_code, data_residency, now, now, ), ) conn.commit() return self.get_localization_settings(tenant_id) finally: self._close_if_file_db(conn) def update_localization_settings(self, tenant_id: str, **kwargs) -> LocalizationSettings | None: conn = self._get_connection() try: settings = self.get_localization_settings(tenant_id) if not settings: return None updates = [] params = [] allowed_fields = [ "default_language", "supported_languages", "default_currency", "supported_currencies", "default_timezone", "default_date_format", "default_time_format", "default_number_format", "calendar_type", "first_day_of_week", "region_code", "data_residency", ] for key, value in kwargs.items(): if key in allowed_fields: updates.append(f"{key} = ?") if key in ["supported_languages", "supported_currencies"]: params.append(json.dumps(value) if value else "[]") elif key == "first_day_of_week": params.append(int(value)) else: params.append(value) if not updates: return settings updates.append("updated_at = ?") params.append(datetime.now()) params.append(tenant_id) cursor = conn.cursor() cursor.execute( f"UPDATE localization_settings SET {', '.join(updates)} WHERE tenant_id = ?", params ) conn.commit() return self.get_localization_settings(tenant_id) finally: self._close_if_file_db(conn) def detect_user_preferences( self, accept_language: str | None = None, ip_country: str | None = None ) -> dict[str, str]: preferences = {"language": "en", "country": "US", "timezone": "UTC", "currency": "USD"} if accept_language: langs = accept_language.split(",") for lang in langs: lang_code = lang.split(";")[0].strip().replace("-", "_") lang_config = self.get_language_config(lang_code) if lang_config and lang_config.is_active: preferences["language"] = lang_code break if ip_country: country = self.get_country_config(ip_country) if country: preferences["country"] = ip_country preferences["currency"] = country.default_currency preferences["timezone"] = country.timezone if country.default_language not in preferences["language"]: preferences["language"] = country.default_language return preferences def _row_to_translation(self, row: sqlite3.Row) -> Translation: return Translation( id=row["id"], key=row["key"], language=row["language"], value=row["value"], namespace=row["namespace"], context=row["context"], created_at=( datetime.fromisoformat(row["created_at"]) if isinstance(row["created_at"], str) else row["created_at"] ), updated_at=( datetime.fromisoformat(row["updated_at"]) if isinstance(row["updated_at"], str) else row["updated_at"] ), is_reviewed=bool(row["is_reviewed"]), reviewed_by=row["reviewed_by"], reviewed_at=( datetime.fromisoformat(row["reviewed_at"]) if row["reviewed_at"] and isinstance(row["reviewed_at"], str) else row["reviewed_at"] ), ) def _row_to_language_config(self, row: sqlite3.Row) -> LanguageConfig: return LanguageConfig( code=row["code"], name=row["name"], name_local=row["name_local"], is_rtl=bool(row["is_rtl"]), is_active=bool(row["is_active"]), is_default=bool(row["is_default"]), fallback_language=row["fallback_language"], date_format=row["date_format"], time_format=row["time_format"], datetime_format=row["datetime_format"], number_format=row["number_format"], currency_format=row["currency_format"], first_day_of_week=row["first_day_of_week"], calendar_type=row["calendar_type"], ) def _row_to_data_center(self, row: sqlite3.Row) -> DataCenter: return DataCenter( id=row["id"], region_code=row["region_code"], name=row["name"], location=row["location"], endpoint=row["endpoint"], status=row["status"], priority=row["priority"], supported_regions=json.loads(row["supported_regions"] or "[]"), capabilities=json.loads(row["capabilities"] or "{}"), created_at=( datetime.fromisoformat(row["created_at"]) if isinstance(row["created_at"], str) else row["created_at"] ), updated_at=( datetime.fromisoformat(row["updated_at"]) if isinstance(row["updated_at"], str) else row["updated_at"] ), ) def _row_to_tenant_dc_mapping(self, row: sqlite3.Row) -> TenantDataCenterMapping: return TenantDataCenterMapping( id=row["id"], tenant_id=row["tenant_id"], primary_dc_id=row["primary_dc_id"], secondary_dc_id=row["secondary_dc_id"], region_code=row["region_code"], data_residency=row["data_residency"], created_at=( datetime.fromisoformat(row["created_at"]) if isinstance(row["created_at"], str) else row["created_at"] ), updated_at=( datetime.fromisoformat(row["updated_at"]) if isinstance(row["updated_at"], str) else row["updated_at"] ), ) def _row_to_payment_method(self, row: sqlite3.Row) -> LocalizedPaymentMethod: return LocalizedPaymentMethod( id=row["id"], provider=row["provider"], name=row["name"], name_local=json.loads(row["name_local"] or "{}"), supported_countries=json.loads(row["supported_countries"] or "[]"), supported_currencies=json.loads(row["supported_currencies"] or "[]"), is_active=bool(row["is_active"]), config=json.loads(row["config"] or "{}"), icon_url=row["icon_url"], display_order=row["display_order"], min_amount=row["min_amount"], max_amount=row["max_amount"], created_at=( datetime.fromisoformat(row["created_at"]) if isinstance(row["created_at"], str) else row["created_at"] ), updated_at=( datetime.fromisoformat(row["updated_at"]) if isinstance(row["updated_at"], str) else row["updated_at"] ), ) def _row_to_country_config(self, row: sqlite3.Row) -> CountryConfig: return CountryConfig( code=row["code"], code3=row["code3"], name=row["name"], name_local=json.loads(row["name_local"] or "{}"), region=row["region"], default_language=row["default_language"], supported_languages=json.loads(row["supported_languages"] or "[]"), default_currency=row["default_currency"], supported_currencies=json.loads(row["supported_currencies"] or "[]"), timezone=row["timezone"], calendar_type=row["calendar_type"], date_format=row["date_format"], time_format=row["time_format"], number_format=row["number_format"], address_format=row["address_format"], phone_format=row["phone_format"], vat_rate=row["vat_rate"], is_active=bool(row["is_active"]), ) def _row_to_localization_settings(self, row: sqlite3.Row) -> LocalizationSettings: return LocalizationSettings( id=row["id"], tenant_id=row["tenant_id"], default_language=row["default_language"], supported_languages=json.loads(row["supported_languages"] or '["en"]'), default_currency=row["default_currency"], supported_currencies=json.loads(row["supported_currencies"] or '["USD"]'), default_timezone=row["default_timezone"], default_date_format=row["default_date_format"], default_time_format=row["default_time_format"], default_number_format=row["default_number_format"], calendar_type=row["calendar_type"], first_day_of_week=row["first_day_of_week"], region_code=row["region_code"], data_residency=row["data_residency"], created_at=( datetime.fromisoformat(row["created_at"]) if isinstance(row["created_at"], str) else row["created_at"] ), updated_at=( datetime.fromisoformat(row["updated_at"]) if isinstance(row["updated_at"], str) else row["updated_at"] ), ) _localization_manager = None def get_localization_manager(db_path: str = "insightflow.db") -> LocalizationManager: global _localization_manager if _localization_manager is None: _localization_manager = LocalizationManager(db_path) return _localization_manager