1: <?php
2:
3: namespace LaravelUi5\Core\Introspection;
4:
5: use Illuminate\Support\Facades\File;
6: use LogicException;
7:
8: final readonly class Ui5I18n
9: {
10: /**
11: * @param array<string, array<string,string>> $entries
12: */
13: private function __construct(
14: private array $entries
15: )
16: {
17: }
18:
19: /* -- API -------------------------------------------------------------- */
20:
21: public function get(string $key, ?string $locale = null): ?string
22: {
23: if (null !== $locale && isset($this->entries[$locale][$key])) {
24: return $this->entries[$locale][$key];
25: }
26:
27: return $this->entries['default'][$key] ?? null;
28: }
29:
30: public function getTitle(?string $locale = null): string
31: {
32: return $this->get('appTitle', $locale)
33: ?? throw new LogicException('Missing i18n key: appTitle');
34: }
35:
36: public function getDescription(?string $locale = null): string
37: {
38: return $this->get('appDescription', $locale)
39: ?? throw new LogicException('Missing i18n key: appDescription');
40: }
41:
42: /**
43: * @return array<string,string>
44: */
45: public function all(?string $locale = null): array
46: {
47: return $locale ? $this->entries[$locale] : [];
48: }
49:
50: /**
51: * Returns the list of available locales derived from i18n*.properties files.
52: *
53: * The implicit base locale (i18n.properties) is intentionally excluded, as it
54: * represents the default fallback and not a concrete locale.
55: *
56: * @return string[] List of locale identifiers (e.g. ['de', 'en'])
57: */
58: public function getAvailableLocales(): array
59: {
60: return array_values(
61: array_filter(
62: array_keys($this->entries),
63: fn (string $locale) => $locale !== 'default'
64: )
65: );
66: }
67:
68: /* -- Factory ---------------------------------------------------------- */
69:
70: public static function fromI18nProperties(string $path): self
71: {
72: $dir = "{$path}/i18n";
73:
74: if (!is_dir($dir)) {
75: throw new LogicException("i18n directory not found at {$dir}");
76: }
77:
78: $locales = [];
79:
80: foreach (glob($dir . '/i18n*.properties') as $file) {
81: $locale = self::resolveLocaleFromFilename($file);
82: $locales[$locale] = self::parsePropertiesFile($file);
83: }
84:
85: if (!isset($locales['default'])) {
86: throw new LogicException('Missing base i18n.properties file');
87: }
88:
89: return new self($locales);
90: }
91:
92: public static function fromMessageBundles(string $path, string $namespace): self
93: {
94: $srcDir = $path
95: . '/dist/resources/'
96: . str_replace('.', '/', $namespace);
97:
98: if (!File::exists($srcDir)) {
99: throw new LogicException("Missing .library file at {$path}. Run builder first.");
100: }
101:
102: return self::fromBundles($srcDir);
103: }
104:
105: public static function fromBundles(string $path): self
106: {
107: $locales = [];
108:
109: foreach (glob($path . '/messagebundle*.properties') as $file) {
110: $locale = self::resolveLocaleFromFilename($file);
111: $locales[$locale] = self::parsePropertiesFile($file);
112: }
113:
114: if (!isset($locales['default'])) {
115: throw new LogicException('Missing base i18n.properties file');
116: }
117:
118: return new self($locales);
119: }
120:
121: private static function resolveLocaleFromFilename(string $file): string
122: {
123: if ('i18n.properties' === basename($file) || 'messagebundle.properties' === basename($file)) {
124: return 'default';
125: }
126:
127: if (preg_match('/i18n_([a-zA-Z_]+)\.properties$/', $file, $m)) {
128: return strtolower($m[1]);
129: }
130:
131: if (preg_match('/messagebundle_([a-zA-Z_]+)\.properties$/', $file, $m)) {
132: return strtolower($m[1]);
133: }
134:
135: return 'default';
136: }
137:
138: /**
139: * @return array<string,string>
140: */
141: private static function parsePropertiesFile(string $file): array
142: {
143: $entries = [];
144:
145: foreach (file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) {
146: $line = trim($line);
147:
148: if ($line === '' || str_starts_with($line, '#')) {
149: continue;
150: }
151:
152: if (!str_contains($line, '=')) {
153: continue;
154: }
155:
156: [$key, $value] = explode('=', $line, 2);
157: $entries[trim($key)] = trim($value);
158: }
159:
160: return $entries;
161: }
162: }
163: