Translation   A
last analyzed

Complexity

Total Complexity 14

Size/Duplication

Total Lines 116
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 0
Metric Value
wmc 14
lcom 1
cbo 0
dl 0
loc 116
rs 10
c 0
b 0
f 0

4 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 31 8
A asArray() 0 4 1
A readMoWords() 0 6 1
A readMoFile() 0 31 4
1
<?php namespace Fisharebest\Localization;
2
3
/**
4
 * Class Translation - a set of translated messages, such as a .MO file.
5
 *
6
 * @author    Greg Roach <[email protected]>
7
 * @copyright (c) 2018 Greg Roach
8
 * @license   GPLv3+
9
 */
10
class Translation
11
{
12
    // Constants for processing .MO files
13
    const MO_MAGIC_LITTLE_ENDIAN = '950412de';
14
    const MO_MAGIC_BIG_ENDIAN    = 'de120495';
15
    const PACK_LITTLE_ENDIAN     = 'V';
16
    const PACK_BIG_ENDIAN        = 'N';
17
18
    /** @var array An association of English -> translated messages */
19
    private $translations;
20
21
    /**
22
     * The code for this variant.
23
     *
24
     * @param string $filename
25
     */
26
    public function __construct($filename)
27
    {
28
        $this->translations = array();
29
30
        switch (strtolower(pathinfo($filename, PATHINFO_EXTENSION))) {
31
            case 'csv':
32
                $fp = fopen($filename, 'r');
33
                if ($fp) {
34
                    while (($data = fgetcsv($fp, 0, ';')) !== false) {
35
                        $this->translations[$data[0]] = $data[1];
36
                    }
37
                    fclose($fp);
38
                }
39
                break;
40
41
            case 'mo':
42
                $fp = fopen($filename, 'rb');
43
                if ($fp) {
44
                    $this->readMoFile($fp);
45
                    fclose($fp);
46
                }
47
                break;
48
49
            case 'php':
50
                $translations = include $filename;
51
                if (is_array($translations)) {
52
                    $this->translations = $translations;
53
                }
54
                break;
55
        }
56
    }
57
58
    /**
59
     * The translation strings
60
     *
61
     * @return array
62
     */
63
    public function asArray()
64
    {
65
        return $this->translations;
66
    }
67
68
    /**
69
     * Read specific binary data (32 bit words) from a .MO file
70
     *
71
     * @param resource $fp
72
     * @param int      $offset
73
     * @param int      $count
74
     * @param string   $pack "N" for big-endian, "V" for little-endian
75
     *
76
     * @return int[]
77
     */
78
    private function readMoWords($fp, $offset, $count, $pack)
79
    {
80
        fseek($fp, $offset);
81
82
        return unpack($pack . $count, fread($fp, $count * 4));
83
    }
84
85
    /**
86
     * Read and parse a .MO (gettext) file
87
     *
88
     * @link https://www.gnu.org/software/gettext/manual/html_node/MO-Files.html
89
     *
90
     * @param resource $fp
91
     *
92
     * @return void
93
     */
94
    private function readMoFile($fp)
95
    {
96
        // How is the numeric data packed in the .MO file?
97
        $magic = $this->readMoWords($fp, 0, 1, self::PACK_LITTLE_ENDIAN);
98
99
        switch (dechex($magic[1])) {
100
            case self::MO_MAGIC_LITTLE_ENDIAN:
101
                $pack = self::PACK_LITTLE_ENDIAN;
102
                break;
103
            case self::MO_MAGIC_BIG_ENDIAN:
104
                $pack = self::PACK_BIG_ENDIAN;
105
                break;
106
            default:
107
                // Not a valid .MO file.
108
                throw new \InvalidArgumentException('Invalid .MO file');
109
        }
110
111
        // Read the lookup tables
112
        list(, $number_of_strings, $offset_original, $offset_translated) = $this->readMoWords($fp, 8, 3, $pack);
113
        $lookup_original   = $this->readMoWords($fp, $offset_original, $number_of_strings * 2, $pack);
114
        $lookup_translated = $this->readMoWords($fp, $offset_translated, $number_of_strings * 2, $pack);
115
116
        // Read the strings
117
        for ($n = 1; $n < $number_of_strings; ++$n) {
118
            fseek($fp, $lookup_original[$n * 2 + 2]);
119
            $original = fread($fp, $lookup_original[$n * 2 + 1]);
120
            fseek($fp, $lookup_translated[$n * 2 + 2]);
121
            $translated                    = fread($fp, $lookup_translated[$n * 2 + 1]);
122
            $this->translations[$original] = $translated;
123
        }
124
    }
125
}
126