defaults = $this->getDefaultConfig(); $this->config = array_merge($this->defaults, $config); $this->loader = new ConfigLoader($this->config); $this->validator = new ConfigValidator($this->config); $this->cache = new ConfigCache($this->config); $this->currentEnvironment = $this->config['environment'] ?? $this->detectEnvironment(); } /** * Load I18n configuration. */ public function load(): void { if ($this->loaded) { return; } // Load base configuration $baseConfig = $this->loader->loadBase(); // Load environment-specific configuration $envConfig = $this->loader->loadEnvironment($this->currentEnvironment); // Load local configuration $localConfig = $this->loader->loadLocal(); // Merge configurations $this->config = array_merge_recursive( $this->defaults, $baseConfig, $envConfig, $localConfig ); // Validate configuration $validation = $this->validator->validate($this->config); if (!$validation['valid']) { throw new \InvalidArgumentException( 'Invalid I18n configuration: ' . implode(', ', $validation['errors']) ); } // Process configuration $this->processConfig(); $this->loaded = true; } /** * Get configuration value. */ public function get(string $key, mixed $default = null): mixed { $this->ensureLoaded(); return $this->getNestedValue($this->config, $key, $default); } /** * Set configuration value. */ public function set(string $key, mixed $value): void { $this->ensureLoaded(); $this->setNestedValue($this->config, $key, $value); $this->cache->clear(); } /** * Check if configuration key exists. */ public function has(string $key): bool { $this->ensureLoaded(); return $this->hasNestedValue($this->config, $key); } /** * Get all configuration. */ public function all(): array { $this->ensureLoaded(); return $this->config; } /** * Get supported languages. */ public function getSupportedLanguages(): array { return $this->get('supported_languages', []); } /** * Get default language. */ public function getDefaultLanguage(): string { return $this->get('default_language', 'en'); } /** * Get fallback language. */ public function getFallbackLanguage(): string { return $this->get('fallback_language', 'en'); } /** * Get current language. */ public function getCurrentLanguage(): string { return $this->get('current_language', $this->getDefaultLanguage()); } /** * Set current language. */ public function setCurrentLanguage(string $language): void { if (!in_array($language, $this->getSupportedLanguages())) { throw new \InvalidArgumentException("Language '{$language}' is not supported"); } $this->set('current_language', $language); } /** * Get timezone configuration. */ public function getTimezoneConfig(): array { return $this->get('timezone', [ 'default' => 'UTC', 'allow_user_override' => true, 'supported_timezones' => [] ]); } /** * Get currency configuration. */ public function getCurrencyConfig(): array { return $this->get('currency', [ 'default' => 'USD', 'allow_user_override' => true, 'supported_currencies' => [], 'precision' => 2, 'decimal_separator' => '.', 'thousands_separator' => ',' ]); } /** * Get date/time format configuration. */ public function getDateTimeConfig(): array { return $this->get('datetime', [ 'date_format' => 'Y-m-d', 'time_format' => 'H:i:s', 'datetime_format' => 'Y-m-d H:i:s', 'timezone' => 'UTC', 'locale' => 'en' ]); } /** * Get number format configuration. */ public function getNumberConfig(): array { return $this->get('number', [ 'decimal_separator' => '.', 'thousands_separator' => ',', 'precision' => 2 ]); } /** * Get translation paths. */ public function getTranslationPaths(): array { return $this->get('translation_paths', []); } /** * Get cache configuration. */ public function getCacheConfig(): array { return $this->get('cache', [ 'enabled' => true, 'driver' => 'file', 'prefix' => 'i18n', 'ttl' => 3600 ]); } /** * Get language switcher configuration. */ public function getSwitcherConfig(): array { return $this->get('switcher', [ 'strategies' => ['url', 'parameter', 'header', 'session', 'cookie'], 'url_parameter' => 'lang', 'cookie_name' => 'language', 'cookie_expires' => 86400 * 30 ]); } /** * Get fallback configuration. */ public function getFallbackConfig(): array { return $this->get('fallback', [ 'enabled' => true, 'chains' => [], 'max_depth' => 5 ]); } /** * Get validation configuration. */ public function getValidationConfig(): array { return $this->get('validation', [ 'strict_mode' => false, 'log_missing' => true, 'throw_on_missing' => false ]); } /** * Add supported language. */ public function addSupportedLanguage(string $code, string $name, array $options = []): void { $languages = $this->getSupportedLanguages(); $languages[$code] = array_merge([ 'name' => $name, 'native_name' => $name, 'direction' => 'ltr', 'enabled' => true ], $options); $this->set('supported_languages', $languages); } /** * Remove supported language. */ public function removeSupportedLanguage(string $code): void { $languages = $this->getSupportedLanguages(); unset($languages[$code]); $this->set('supported_languages', $languages); } /** * Add translation path. */ public function addTranslationPath(string $path, int $priority = 0): void { $paths = $this->getTranslationPaths(); $paths[] = ['path' => $path, 'priority' => $priority]; // Sort by priority (higher first) usort($paths, fn($a, $b) => $b['priority'] - $a['priority']); $this->set('translation_paths', $paths); } /** * Remove translation path. */ public function removeTranslationPath(string $path): void { $paths = $this->getTranslationPaths(); $paths = array_filter($paths, fn($p) => $p['path'] !== $path); $this->set('translation_paths', array_values($paths)); } /** * Set timezone configuration. */ public function setTimezoneConfig(array $config): void { $this->set('timezone', array_merge($this->getTimezoneConfig(), $config)); } /** * Set currency configuration. */ public function setCurrencyConfig(array $config): void { $this->set('currency', array_merge($this->getCurrencyConfig(), $config)); } /** * Set date/time format configuration. */ public function setDateTimeConfig(array $config): void { $this->set('datetime', array_merge($this->getDateTimeConfig(), $config)); } /** * Set number format configuration. */ public function setNumberConfig(array $config): void { $this->set('number', array_merge($this->getNumberConfig(), $config)); } /** * Enable/disable cache. */ public function setCacheEnabled(bool $enabled): void { $this->set('cache.enabled', $enabled); } /** * Set cache TTL. */ public function setCacheTtl(int $ttl): void { $this->set('cache.ttl', $ttl); } /** * Enable/disable strict validation. */ public function setStrictValidation(bool $strict): void { $this->set('validation.strict_mode', $strict); } /** * Get environment-specific configuration. */ public function getEnvironmentConfig(string $environment): array { return $this->environments[$environment] ?? []; } /** * Set environment-specific configuration. */ public function setEnvironmentConfig(string $environment, array $config): void { $this->environments[$environment] = $config; } /** * Get current environment. */ public function getCurrentEnvironment(): string { return $this->currentEnvironment; } /** * Set current environment. */ public function setCurrentEnvironment(string $environment): void { $this->currentEnvironment = $environment; $this->loaded = false; // Force reload with new environment } /** * Detect current environment. */ protected function detectEnvironment(): string { // Check environment variable $env = getenv('APP_ENV') ?: getenv('ENVIRONMENT'); if ($env) { return $env; } // Check server variable if (isset($_SERVER['APP_ENV'])) { return $_SERVER['APP_ENV']; } // Check for common indicators if (isset($_SERVER['SERVER_NAME'])) { $serverName = $_SERVER['SERVER_NAME']; if (str_contains($serverName, 'localhost') || str_contains($serverName, 'dev')) { return 'development'; } elseif (str_contains($serverName, 'staging') || str_contains($serverName, 'test')) { return 'staging'; } } // Default to production return 'production'; } /** * Process configuration after loading. */ protected function processConfig(): void { // Set default timezone $timezone = $this->get('timezone.default', 'UTC'); date_default_timezone_set($timezone); // Process supported languages $languages = $this->getSupportedLanguages(); foreach ($languages as $code => $info) { if (!isset($info['name'])) { $languages[$code]['name'] = $code; } if (!isset($info['native_name'])) { $languages[$code]['native_name'] = $info['name']; } if (!isset($info['direction'])) { $languages[$code]['direction'] = 'ltr'; } if (!isset($info['enabled'])) { $languages[$code]['enabled'] = true; } } $this->set('supported_languages', $languages); // Process translation paths $paths = $this->getTranslationPaths(); foreach ($paths as $index => $path) { if (is_string($path)) { $paths[$index] = ['path' => $path, 'priority' => 0]; } } usort($paths, fn($a, $b) => $b['priority'] - $a['priority']); $this->set('translation_paths', $paths); // Validate current language $currentLanguage = $this->getCurrentLanguage(); if (!in_array($currentLanguage, array_keys($languages))) { $this->setCurrentLanguage($this->getDefaultLanguage()); } } /** * Get nested value from array. */ protected function getNestedValue(array $array, string $key, mixed $default = null): mixed { $keys = explode('.', $key); $current = $array; foreach ($keys as $k) { if (!is_array($current) || !array_key_exists($k, $current)) { return $default; } $current = $current[$k]; } return $current; } /** * Set nested value in array. */ protected function setNestedValue(array &$array, string $key, mixed $value): void { $keys = explode('.', $key); $current = &$array; foreach ($keys as $k) { if (!is_array($current)) { $current = []; } if (!array_key_exists($k, $current)) { $current[$k] = []; } $current = &$current[$k]; } $current = $value; } /** * Check if nested value exists. */ protected function hasNestedValue(array $array, string $key): bool { $keys = explode('.', $key); $current = $array; foreach ($keys as $k) { if (!is_array($current) || !array_key_exists($k, $current)) { return false; } $current = $current[$k]; } return true; } /** * Ensure configuration is loaded. */ protected function ensureLoaded(): void { if (!$this->loaded) { $this->load(); } } /** * Reload configuration. */ public function reload(): void { $this->loaded = false; $this->cache->clear(); $this->load(); } /** * Save configuration to file. */ public function save(string $filename = null): bool { $this->ensureLoaded(); $filename = $filename ?? $this->get('config_file', 'i18n.php'); $content = 'config, true) . ';'; return file_put_contents($filename, $content) !== false; } /** * Export configuration. */ public function export(string $format = 'php'): string { $this->ensureLoaded(); return match ($format) { 'php' => 'config, true) . ';', 'json' => json_encode($this->config, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE), 'yaml' => $this->toYaml($this->config), default => throw new \InvalidArgumentException("Unsupported export format: {$format}") }; } /** * Import configuration. */ public function import(string $data, string $format = 'php'): void { $config = match ($format) { 'php' => include 'data://text/plain,' . urlencode($data), 'json' => json_decode($data, true), 'yaml' => $this->fromYaml($data), default => throw new \InvalidArgumentException("Unsupported import format: {$format}") }; if (!is_array($config)) { throw new \InvalidArgumentException('Invalid configuration data'); } $this->config = array_merge($this->defaults, $config); $this->processConfig(); $this->cache->clear(); } /** * Convert to YAML. */ protected function toYaml(array $data, int $depth = 0): string { $yaml = ''; $indent = str_repeat(' ', $depth); foreach ($data as $key => $value) { if (is_array($value)) { $yaml .= "{$indent}{$key}:\n"; $yaml .= $this->toYaml($value, $depth + 1); } else { $escapedValue = str_replace(["\n", "\r"], ['\\n', '\\r'], (string) $value); $yaml .= "{$indent}{$key}: \"{$escapedValue}\"\n"; } } return $yaml; } /** * Parse from YAML. */ protected function fromYaml(string $yaml): array { $data = []; $lines = explode("\n", $yaml); $stack = [&$data]; $currentIndent = 0; foreach ($lines as $line) { $line = trim($line); if (empty($line) || str_starts_with($line, '#')) { continue; } $indent = strlen($line) - strlen(ltrim($line)); $line = trim($line); if (preg_match('/^(\w+):\s*$/', $line, $matches)) { $key = $matches[1]; $newArray = []; // Adjust stack depth while ($indent < $currentIndent) { array_pop($stack); $currentIndent -= 2; } $current = &$stack[count($stack) - 1]; $current[$key] = &$newArray; $stack[] = &$newArray; $currentIndent = $indent; } elseif (preg_match('/^(\w+):\s*"(.*)"$/', $line, $matches)) { $key = $matches[1]; $value = stripslashes($matches[2]); // Adjust stack depth while ($indent < $currentIndent) { array_pop($stack); $currentIndent -= 2; } $current = &$stack[count($stack) - 1]; $current[$key] = $value; } } return $data; } /** * Validate configuration. */ public function validate(): array { $this->ensureLoaded(); return $this->validator->validate($this->config); } /** * Get configuration summary. */ public function getSummary(): array { $this->ensureLoaded(); return [ 'environment' => $this->currentEnvironment, 'default_language' => $this->getDefaultLanguage(), 'current_language' => $this->getCurrentLanguage(), 'fallback_language' => $this->getFallbackLanguage(), 'supported_languages' => count($this->getSupportedLanguages()), 'translation_paths' => count($this->getTranslationPaths()), 'cache_enabled' => $this->get('cache.enabled', false), 'timezone' => $this->get('timezone.default', 'UTC'), 'currency' => $this->get('currency.default', 'USD') ]; } /** * Get default configuration. */ protected function getDefaultConfig(): array { return [ 'environment' => 'production', 'default_language' => 'en', 'fallback_language' => 'en', 'current_language' => 'en', 'supported_languages' => [ 'en' => [ 'name' => 'English', 'native_name' => 'English', 'direction' => 'ltr', 'enabled' => true ] ], 'translation_paths' => [ ['path' => 'resources/lang', 'priority' => 100] ], 'cache' => [ 'enabled' => true, 'driver' => 'file', 'prefix' => 'i18n', 'ttl' => 3600 ], 'switcher' => [ 'strategies' => ['url', 'parameter', 'header', 'session', 'cookie'], 'url_parameter' => 'lang', 'cookie_name' => 'language', 'cookie_expires' => 86400 * 30, 'track_switches' => true ], 'fallback' => [ 'enabled' => true, 'chains' => [], 'max_depth' => 5 ], 'timezone' => [ 'default' => 'UTC', 'allow_user_override' => true, 'supported_timezones' => [] ], 'currency' => [ 'default' => 'USD', 'allow_user_override' => true, 'supported_currencies' => [], 'precision' => 2, 'decimal_separator' => '.', 'thousands_separator' => ',' ], 'datetime' => [ 'date_format' => 'Y-m-d', 'time_format' => 'H:i:s', 'datetime_format' => 'Y-m-d H:i:s', 'timezone' => 'UTC', 'locale' => 'en' ], 'number' => [ 'decimal_separator' => '.', 'thousands_separator' => ',', 'precision' => 2 ], 'validation' => [ 'strict_mode' => false, 'log_missing' => true, 'throw_on_missing' => false ] ]; } /** * Create I18n config manager instance. */ public static function create(array $config = []): self { return new self($config); } /** * Create for development environment. */ public static function forDevelopment(): self { return new self([ 'environment' => 'development', 'cache' => ['enabled' => false], 'validation' => ['strict_mode' => true, 'log_missing' => true] ]); } /** * Create for testing environment. */ public static function forTesting(): self { return new self([ 'environment' => 'testing', 'cache' => ['enabled' => false], 'validation' => ['strict_mode' => true] ]); } /** * Create for production environment. */ public static function forProduction(): self { return new self([ 'environment' => 'production', 'cache' => ['enabled' => true, 'ttl' => 7200], 'validation' => ['strict_mode' => false, 'log_missing' => false] ]); } }