Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
Catalog
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
6 / 6
10
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 load
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 readMessages
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 readPlural
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 get
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 form
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace Celemas\Verba;
6
7use Closure;
8
9/**
10 * The messages of a single domain and locale, plus its plural rule.
11 *
12 * @api
13 */
14final class Catalog
15{
16    /**
17     * @param array<string, string|list<string>|null> $messages
18     * @param Closure(int): int $plural
19     */
20    public function __construct(
21        private array $messages,
22        private Closure $plural,
23    ) {}
24
25    /**
26     * Loads `<file>`, falling back to an empty catalog when it is missing or
27     * does not return an array.
28     */
29    public static function load(string $file, string $locale): self
30    {
31        if (!is_file($file)) {
32            return new self([], Plurals::rule($locale));
33        }
34
35        /** @var mixed $data */
36        $data = require $file;
37
38        if (!is_array($data)) {
39            return new self([], Plurals::rule($locale));
40        }
41
42        return new self(
43            self::readMessages($data['messages'] ?? null),
44            self::readPlural($data['plural'] ?? null, $locale),
45        );
46    }
47
48    /**
49     * @return array<string, string|list<string>|null>
50     */
51    private static function readMessages(mixed $messages): array
52    {
53        if (!is_array($messages)) {
54            return [];
55        }
56
57        /** @var array<string, string|list<string>|null> $messages */
58        return $messages;
59    }
60
61    /**
62     * A catalog may borrow another language's rule via a `plural` key holding a
63     * locale/rule id (e.g. `'plural' => 'ru'`); otherwise its own locale rules.
64     *
65     * @return Closure(int): int
66     */
67    private static function readPlural(mixed $plural, string $locale): Closure
68    {
69        return Plurals::rule(is_string($plural) ? $plural : $locale);
70    }
71
72    /**
73     * The raw entry for an id: a string, a list of plural forms, or null when
74     * absent or explicitly untranslated.
75     *
76     * @return string|list<string>|null
77     */
78    public function get(string $id): string|array|null
79    {
80        return $this->messages[$id] ?? null;
81    }
82
83    /**
84     * Zero-based plural form index for a count.
85     */
86    public function form(int $n): int
87    {
88        return ($this->plural)($n);
89    }
90}