kodedphp /
i18n
| 1 | <?php declare(strict_types=1); |
||
| 2 | |||
| 3 | namespace Koded\I18n; |
||
| 4 | |||
| 5 | use Koded\Stdlib\Config; |
||
| 6 | use Throwable; |
||
| 7 | use function error_log; |
||
| 8 | use function ini_set; |
||
| 9 | use function strtr; |
||
| 10 | use function substr_count; |
||
| 11 | use function vsprintf; |
||
| 12 | |||
| 13 | interface I18nFormatter |
||
| 14 | { |
||
| 15 | /** |
||
| 16 | * Message formatter for argument replacement in the message. |
||
| 17 | * |
||
| 18 | * @param string $string |
||
| 19 | * @param array $arguments |
||
| 20 | * @return string The message with applied arguments (if any) |
||
| 21 | */ |
||
| 22 | public function format(string $string, array $arguments): string; |
||
| 23 | } |
||
| 24 | |||
| 25 | final class StrtrFormatter implements I18nFormatter |
||
| 26 | { |
||
| 27 | 1 | public function format(string $string, array $arguments): string |
|
| 28 | { |
||
| 29 | 1 | return $arguments ? strtr($string, $arguments) : $string; |
|
| 30 | } |
||
| 31 | } |
||
| 32 | |||
| 33 | final class DefaultFormatter implements I18nFormatter |
||
| 34 | { |
||
| 35 | 8 | public function format(string $string, array $arguments): string |
|
| 36 | { |
||
| 37 | 8 | return $arguments ? vsprintf($string, $arguments) : $string; |
|
| 38 | } |
||
| 39 | } |
||
| 40 | |||
| 41 | class I18n |
||
| 42 | { |
||
| 43 | public const DEFAULT_LOCALE = 'en_US'; |
||
| 44 | |||
| 45 | /* |
||
| 46 | * Default configuration parameters for all catalogs. |
||
| 47 | * Values are taken by the explicitly set default locale, |
||
| 48 | * or the first loaded catalog instance. |
||
| 49 | */ |
||
| 50 | private static ?string $catalog = null; |
||
| 51 | private static ?string $formatter = null; |
||
| 52 | private static ?string $directory = null; |
||
| 53 | private static ?string $locale = null; |
||
| 54 | |||
| 55 | /** @var array<string, I18nCatalog> */ |
||
| 56 | private static array $catalogs = []; |
||
| 57 | |||
| 58 | // @codeCoverageIgnoreStart |
||
| 59 | private function __construct() {} |
||
| 60 | // @codeCoverageIgnoreEnd |
||
| 61 | |||
| 62 | 9 | public static function translate( |
|
| 63 | string $string, |
||
| 64 | array $arguments = [], |
||
| 65 | string $locale = null): string |
||
| 66 | { |
||
| 67 | try { |
||
| 68 | 9 | return static::$catalogs[$locale]->translate('messages', $string, $arguments); |
|
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||
| 69 | 9 | } catch (Throwable) { |
|
| 70 | 9 | static::registerCatalog($locale ??= static::locale()); |
|
| 71 | 9 | return static::$catalogs[$locale]->translate('messages', $string, $arguments); |
|
| 72 | } |
||
| 73 | } |
||
| 74 | |||
| 75 | 6 | public static function locale(): string |
|
| 76 | { |
||
| 77 | 6 | return static::$locale ??= static::normalizeLocale(\Locale::getDefault()); |
|
|
0 ignored issues
–
show
|
|||
| 78 | } |
||
| 79 | |||
| 80 | /** |
||
| 81 | * @return array{string, I18nCatalog} |
||
|
1 ignored issue
–
show
|
|||
| 82 | */ |
||
| 83 | 9 | public static function catalogs(): array |
|
| 84 | { |
||
| 85 | 9 | return static::$catalogs; |
|
|
0 ignored issues
–
show
|
|||
| 86 | } |
||
| 87 | |||
| 88 | /** |
||
| 89 | * @return array{locale:string, catalogs:<string, array{class: string, formatter:string, dir:string, locale:string}>} |
||
|
1 ignored issue
–
show
|
|||
| 90 | */ |
||
| 91 | 1 | public static function info(): array |
|
| 92 | { |
||
| 93 | 1 | $catalogs = []; |
|
| 94 | 1 | foreach (static::$catalogs as $locale => $instance) { |
|
|
0 ignored issues
–
show
|
|||
| 95 | 1 | $catalogs[$locale] = [ |
|
| 96 | 1 | 'class' => $instance::class, |
|
| 97 | 1 | 'formatter' => $instance->formatter()::class, |
|
| 98 | 1 | 'dir' => $instance->directory(), |
|
| 99 | 1 | 'locale' => $instance->locale(), |
|
| 100 | ]; |
||
| 101 | } |
||
| 102 | return [ |
||
| 103 | 1 | 'locale' => static::$locale, |
|
|
0 ignored issues
–
show
|
|||
| 104 | 1 | 'catalogs' => $catalogs, |
|
| 105 | ]; |
||
| 106 | } |
||
| 107 | |||
| 108 | 15 | public static function register( |
|
| 109 | I18nCatalog $catalog, |
||
| 110 | bool $asDefault = false): void |
||
| 111 | { |
||
| 112 | 15 | $locale = $catalog->locale(); |
|
| 113 | 15 | if ($asDefault || empty(static::$catalogs)) { |
|
|
0 ignored issues
–
show
|
|||
| 114 | 15 | static::setDefaultLocale($locale); |
|
| 115 | 15 | static::$directory = $catalog->directory(); |
|
|
0 ignored issues
–
show
|
|||
| 116 | 15 | static::$formatter = $catalog->formatter()::class; |
|
|
0 ignored issues
–
show
|
|||
| 117 | 15 | static::$catalog = $catalog::class; |
|
|
0 ignored issues
–
show
|
|||
| 118 | } |
||
| 119 | 15 | static::$catalogs[$locale] = $catalog; |
|
| 120 | 15 | } |
|
| 121 | |||
| 122 | 15 | public static function flush(): void |
|
| 123 | { |
||
| 124 | 15 | static::$catalogs = []; |
|
|
0 ignored issues
–
show
|
|||
| 125 | 15 | static::$directory = null; |
|
|
0 ignored issues
–
show
|
|||
| 126 | 15 | static::$formatter = null; |
|
|
0 ignored issues
–
show
|
|||
| 127 | 15 | static::$catalog = null; |
|
|
0 ignored issues
–
show
|
|||
| 128 | 15 | static::$locale = null; |
|
|
0 ignored issues
–
show
|
|||
| 129 | 15 | ini_set('intl.default_locale', ''); |
|
| 130 | 15 | \Locale::setDefault(''); |
|
| 131 | 15 | } |
|
| 132 | |||
| 133 | 9 | private static function registerCatalog(string $locale): void |
|
| 134 | { |
||
| 135 | 9 | if (isset(static::$catalogs[$locale])) { |
|
|
0 ignored issues
–
show
|
|||
| 136 | 5 | return; |
|
| 137 | } |
||
| 138 | 5 | static::$catalogs[$locale] = I18nCatalog::new((new Config) |
|
| 139 | 5 | ->set('translation.locale', $locale) |
|
| 140 | 5 | ->set('translation.dir', static::$directory) |
|
|
0 ignored issues
–
show
|
|||
| 141 | 5 | ->set('translation.formatter', static::$formatter) |
|
|
0 ignored issues
–
show
|
|||
| 142 | 5 | ->set('translation.catalog', static::$catalog) |
|
|
0 ignored issues
–
show
|
|||
| 143 | ); |
||
| 144 | 5 | } |
|
| 145 | |||
| 146 | 15 | private static function setDefaultLocale(string $locale): void |
|
| 147 | { |
||
| 148 | 15 | static::$locale = $locale; |
|
|
0 ignored issues
–
show
|
|||
| 149 | 15 | ini_set('intl.default_locale', $locale); |
|
| 150 | 15 | \Locale::setDefault($locale); |
|
| 151 | 15 | } |
|
| 152 | |||
| 153 | private static function normalizeLocale(string $locale): string |
||
| 154 | { |
||
| 155 | if (substr_count($locale, '_') > 1) { |
||
| 156 | $locale = explode('_', $locale); |
||
| 157 | $locale = "$locale[0]_$locale[1]"; |
||
| 158 | } |
||
| 159 | return $locale; |
||
| 160 | } |
||
| 161 | } |
||
| 162 |