Passed
Push — master ( 2b1ac9...97cf13 )
by Mihail
02:13
created

I18n.php (21 issues)

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
Since $catalogs is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $catalogs to at least protected.
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
Since $locale is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $locale to at least protected.
Loading history...
78
    }
79
80
    /**
81
     * @return array{string, I18nCatalog}
1 ignored issue
show
Documentation Bug introduced by
The doc comment array{string, I18nCatalog} at position 2 could not be parsed: Expected ':' at position 2, but found 'string'.
Loading history...
82
     */
83 9
    public static function catalogs(): array
84
    {
85 9
        return static::$catalogs;
0 ignored issues
show
Since $catalogs is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $catalogs to at least protected.
Loading history...
86
    }
87
88
    /**
89
     * @return array{locale:string, catalogs:<string, array{class: string, formatter:string, dir:string, locale:string}>}
1 ignored issue
show
Documentation Bug introduced by
The doc comment array{locale:string, cat...tring, locale:string}>} at position 8 could not be parsed: Unknown type name '<' at position 8 in array{locale:string, catalogs:<string, array{class: string, formatter:string, dir:string, locale:string}>}.
Loading history...
90
     */
91 1
    public static function info(): array
92
    {
93 1
        $catalogs = [];
94 1
        foreach (static::$catalogs as $locale => $instance) {
0 ignored issues
show
Since $catalogs is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $catalogs to at least protected.
Loading history...
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
Since $locale is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $locale to at least protected.
Loading history...
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
Since $catalogs is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $catalogs to at least protected.
Loading history...
114 15
            static::setDefaultLocale($locale);
115 15
            static::$directory = $catalog->directory();
0 ignored issues
show
Since $directory is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $directory to at least protected.
Loading history...
116 15
            static::$formatter = $catalog->formatter()::class;
0 ignored issues
show
Since $formatter is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $formatter to at least protected.
Loading history...
117 15
            static::$catalog = $catalog::class;
0 ignored issues
show
Since $catalog is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $catalog to at least protected.
Loading history...
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
Since $catalogs is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $catalogs to at least protected.
Loading history...
125 15
        static::$directory = null;
0 ignored issues
show
Since $directory is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $directory to at least protected.
Loading history...
126 15
        static::$formatter = null;
0 ignored issues
show
Since $formatter is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $formatter to at least protected.
Loading history...
127 15
        static::$catalog = null;
0 ignored issues
show
Since $catalog is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $catalog to at least protected.
Loading history...
128 15
        static::$locale = null;
0 ignored issues
show
Since $locale is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $locale to at least protected.
Loading history...
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
Since $catalogs is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $catalogs to at least protected.
Loading history...
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
Since $directory is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $directory to at least protected.
Loading history...
141 5
            ->set('translation.formatter', static::$formatter)
0 ignored issues
show
Since $formatter is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $formatter to at least protected.
Loading history...
142 5
            ->set('translation.catalog', static::$catalog)
0 ignored issues
show
Since $catalog is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $catalog to at least protected.
Loading history...
143
        );
144 5
    }
145
146 15
    private static function setDefaultLocale(string $locale): void
147
    {
148 15
        static::$locale = $locale;
0 ignored issues
show
Since $locale is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $locale to at least protected.
Loading history...
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