19declare(strict_types=1);
24use Gettext\Generator\MoGenerator;
25use Gettext\Generator\PoGenerator;
26use Gettext\Loader\MoLoader;
27use Gettext\Loader\StrictPoLoader;
28use Gettext\Translation;
29use Gettext\Translations;
31use InvalidArgumentException;
61 private const string UTF8_BOM =
"\xEF\xBB\xBF";
67 $this->translations = Translations::create();
81 $content = is_file($file) && is_readable($file) ? @file_get_contents($file) :
false;
82 if ($content ===
false) {
83 throw new RuntimeException(sprintf(
'Could not read PO file "%s".', $file));
88 }
catch (RuntimeException
$e) {
89 throw new RuntimeException(sprintf(
'Invalid PO file "%s": %s', $file,
$e->getMessage()), 0,
$e);
98 if (str_starts_with($content, self::UTF8_BOM)) {
99 $content = substr($content, strlen(self::UTF8_BOM));
102 if (!mb_check_encoding($content,
'UTF-8')) {
103 throw new RuntimeException(
'The content is not valid UTF-8.');
107 static function () use ($content): Translations {
108 $loader =
new StrictPoLoader();
109 $loader->displayErrorLine =
true;
110 return $loader->loadString($content);
113 foreach (
$translations->getHeaders()->toArray() as $name => $value) {
115 if (strpbrk($value,
"\r\n") !==
false) {
116 throw new RuntimeException(sprintf(
'The value of header "%s" contains a line break.', $name));
119 $content_type =
$translations->getHeaders()->get(
'Content-Type');
121 $content_type !==
null
122 && preg_match(
'/charset\s*=\s*"?([^\s;"]+)/i', $content_type, $matches) === 1
123 && !in_array(strtolower($matches[1]), [
'utf-8',
'utf8'],
true)
125 throw new RuntimeException(sprintf(
'Unsupported charset "%s" - only UTF-8 is supported.', $matches[1]));
128 if ($translation->isDisabled()) {
133 $catalog =
new self();
148 static fn(): Translations => (
new MoLoader())->loadString($content)
151 if ($translation->getTranslation() ===
"0\0") {
152 $translation->translate(
'0');
156 $catalog =
new self();
167 return $this->translations->getHeaders()->toArray();
172 return $this->translations->getHeaders()->get($name);
181 public function setHeader(
string $name,
string $value): void
183 if (strpbrk($value,
"\r\n") !==
false) {
184 throw new InvalidArgumentException(sprintf(
'The value of header "%s" must not contain a line break.', $name));
186 $this->translations->getHeaders()->set($name, $value);
191 $translation = $this->translations->find($context,
$id);
202 $this->translations->add($entry->
toGettext());
213 if ($this->translations->has($translation)) {
214 $this->translations->add($translation);
218 foreach ($this->translations->getTranslations() as $existing) {
219 if ($following !== [] || strcmp($existing->getOriginal(), $translation->getOriginal()) > 0) {
220 $following[] = $existing;
221 $this->translations->remove($existing);
224 $this->translations->add($translation);
225 foreach ($following as $existing) {
226 $this->translations->add($existing);
235 return array_values(array_map(
237 $this->translations->getTranslations()
248 foreach ($headers->toArray() as $name => $value) {
249 $headers->set($name, substr(PoGenerator::encode($value), 1, -1));
265 return (
new MoGenerator())->includeHeaders(
true)->generateString($this->
prepareForGenerator(
true));
276 in_array($translation->getContext(), [
'',
'0'],
true)
277 || in_array($translation->getPlural(), [
'',
'0'],
true)
279 throw new RuntimeException(sprintf(
280 'The message "%s" has a context or plural id gettext/gettext cannot write.',
281 $translation->getOriginal()
284 if ($for_mo && $translation->getPlural() !==
null) {
287 if ($translation->getTranslation() !==
'0') {
290 if ($for_mo && $translation->getPlural() !==
null) {
292 throw new RuntimeException(sprintf(
293 'The plural message "%s" has the singular translation "0", which gettext/gettext cannot compile.',
294 $translation->getOriginal()
297 $translation->translate(
"0\0");
316 $library_plural_form = $headers->getPluralForm();
318 $headers->get(
'Plural-Forms') !==
null
319 && (!is_array($library_plural_form) || (
int) $library_plural_form[0] !== $plural_forms->getCount())
321 $headers->set(
'Plural-Forms', $plural_forms->getHeader());
323 $surplus = $plural_forms->getCount() - 1;
324 $translation->translatePlural(...array_pad(
325 array_slice(array_values($translation->getPluralTranslations()), 0, $surplus),
329 if (($translation->getTranslation() ??
'') ===
'' && implode(
'', $translation->getPluralTranslations()) !==
'') {
330 throw new RuntimeException(sprintf(
331 'The plural message "%s" has an empty msgstr[0] but other forms, which gettext/gettext cannot compile.',
332 $translation->getOriginal()
346 $previous_handler =
null;
347 $previous_handler = set_error_handler(
348 static function (
int $severity,
string $message,
string $file,
int $line) use (&$previous_handler):
bool {
349 if (($severity & (E_DEPRECATED | E_USER_DEPRECATED)) === 0) {
350 throw new ErrorException(
$message, 0, $severity, $file, $line);
353 return $previous_handler !==
null && $previous_handler($severity,
$message, $file, $line) !==
false;
358 }
catch (Throwable $t) {
359 throw new RuntimeException($t->getMessage(), 0, $t);
361 restore_error_handler();
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
An in-memory gettext catalog (header block plus messages) and the only place of the component that re...
Translations $translations
prepareForGenerator(bool $for_mo)
A copy of the catalog the generators write correctly, see the class docblock.
static fromMoString(string $content)
Reads a .mo of toMoString() for reading only: the library drops empty plural forms and forms "0",...
add(TranslationEntry $entry)
Adds $entry, replacing an entry with the same context and id.
addInIdOrder(TranslationEntry $entry)
add() at the position of $entry's id: before the first entry whose id sorts after it (strcmp(),...
static fromPoString(string $content)
setHeader(string $name, string $value)
The library trims header values.
static withLibraryErrorsAsExceptions(callable $load)
Runs $load and turns everything the library raises (exceptions, errors and, via a temporary error han...
static fromPoFile(string $file)
static prepareMoPluralForms(Translations $translations, Translation $translation)
A plural message as MoGenerator needs it: exactly as many forms as PluralForms gives for the language...
__clone()
Independent of the original: Translations::__clone() copies every message and the headers.
toMoString()
Deterministic: the same catalog always compiles to the same bytes, so an existing ....
find(?string $context, string $id)
One message of a TranslationCatalog (a msgctxt/msgid/msgstr block of a .po file).
static fromGettext(Translation $translation)