Passed
Pull Request — master (#81)
by Dante
01:06
created

Gettext::analyzePoFile()   B

Complexity

Conditions 7
Paths 10

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 12
c 1
b 0
f 0
nc 10
nop 1
dl 0
loc 19
rs 8.8333
1
<?php
2
declare(strict_types=1);
3
4
/**
5
 * BEdita, API-first content management framework
6
 * Copyright 2023 Atlas Srl, Chialab Srl
7
 *
8
 * This file is part of BEdita: you can redistribute it and/or modify
9
 * it under the terms of the GNU Lesser General Public License as published
10
 * by the Free Software Foundation, either version 3 of the License, or
11
 * (at your option) any later version.
12
 *
13
 * See LICENSE.LGPL or <http://gnu.org/licenses/lgpl-3.0.html> for more details.
14
 */
15
16
namespace BEdita\I18n\Filesystem;
17
18
use Cake\I18n\FrozenTime;
19
20
class Gettext
21
{
22
    /**
23
     * Write `master.pot` file
24
     *
25
     * @return array
26
     */
27
    public static function writeMasterPot(string $localePath, array $translations): array
28
    {
29
        $info = [];
30
        $updated = false;
31
32
        foreach ($translations as $domain => $poResult) {
33
            $potFilename = sprintf('%s/%s.pot', $localePath, $domain);
34
            $info[] = sprintf('Writing new .pot file: %s', $potFilename);
35
36
            $file = new \SplFileInfo($potFilename);
37
            $pot = $file->openFile('w');
38
            $contents = file_get_contents($potFilename);
39
40
            // remove headers from pot file
41
            $contents = preg_replace('/^msgid ""\nmsgstr ""/', '', $contents);
42
            $contents = trim(preg_replace('/^"([^"]*?)"$/m', '', $contents));
43
44
            $lines = [];
45
            ksort($poResult);
46
            foreach ($poResult as $res => $contexts) {
47
                sort($contexts);
48
                foreach ($contexts as $ctx) {
49
                    if (!empty($ctx)) {
50
                        $lines[] = sprintf('msgctxt "%s"%smsgid "%s"%smsgstr ""', $ctx, "\n", $res, "\n");
51
                    } else {
52
                        $lines[] = sprintf('msgid "%s"%smsgstr ""', $res, "\n");
53
                    }
54
                }
55
            }
56
57
            $result = implode("\n\n", $lines);
58
            if ($contents !== $result) {
59
                $pot->fwrite(sprintf("%s\n%s\n", self::header('pot'), $result));
60
                $updated = true;
61
            }
62
        }
63
64
        return compact('info', 'updated');
65
    }
66
67
    /**
68
     * Write `.po` files
69
     *
70
     * @return array
71
     */
72
    public static function writePoFiles(array $locales, string $localePath, array &$translations): array
73
    {
74
        $info = [];
75
        if (empty($locales)) {
76
            $info[] = 'No locales set, .po files generation skipped';
77
78
            return compact('info');
79
        }
80
81
        $header = self::header('po');
82
        foreach ($locales as $loc) {
83
            $potDir = $localePath . DS . $loc;
84
            if (!file_exists($potDir)) {
85
                mkdir($potDir);
86
            }
87
            $info[] = sprintf('Language: %s', $loc);
88
            foreach (array_keys($translations) as $domain) {
89
                $potFilename = sprintf('%s/%s.pot', $localePath, $domain);
90
                $poFile = sprintf('%s/%s.po', $potDir, $domain);
91
                if (!file_exists($poFile)) {
92
                    $newPoFile = new \SplFileInfo($poFile);
93
                    $newPoFile->openFile('w')->fwrite($header);
94
                }
95
                $info[] = sprintf('Merging %s', $poFile);
96
                $mergeCmd = sprintf('msgmerge --backup=off -N -U %s %s', $poFile, $potFilename);
97
                exec($mergeCmd);
98
                $analysis = self::analyzePoFile($poFile);
99
                $info[] = sprintf(
100
                    'Translated %d of %d items - %s %%',
101
                    $analysis['translated'],
102
                    $analysis['numItems'],
103
                    $analysis['percent']
104
                );
105
                $info[] = '---------------------';
106
            }
107
        }
108
109
        return compact('info');
110
    }
111
112
    /**
113
     * Analyze po file and translate it
114
     *
115
     * @param string $filename The po file name
116
     * @return array
117
     */
118
    public static function analyzePoFile($filename): array
119
    {
120
        $lines = file($filename);
121
        $numItems = $numNotTranslated = 0;
122
        foreach ($lines as $k => $l) {
123
            if (strpos($l, 'msgid "') === 0) {
124
                $numItems++;
125
            }
126
            if (strpos($l, 'msgstr ""') === 0 && (!isset($lines[$k + 1]) || strpos($lines[$k + 1], '"') !== 0)) {
127
                $numNotTranslated++;
128
            }
129
        }
130
        $translated = $numItems - $numNotTranslated;
131
        $percent = 0;
132
        if ($numItems > 0) {
133
            $percent = number_format($translated * 100. / $numItems, 1);
134
        }
135
136
        return compact('numItems', 'numNotTranslated', 'translated', 'percent');
137
    }
138
139
    /**
140
     * Header lines for po/pot file
141
     *
142
     * @param string $type The file type (can be 'po', 'pot')
143
     * @return string
144
     */
145
    public static function header(string $type = 'po'): string
146
    {
147
        $result = sprintf('msgid ""%smsgstr ""%s', "\n", "\n");
148
        $contents = [
149
            'po' => [
150
                'Project-Id-Version' => 'BEdita 4',
151
                'POT-Creation-Date' => FrozenTime::now()->format('Y-m-d H:i:s'),
152
                'PO-Revision-Date' => '',
153
                'Last-Translator' => '',
154
                'Language-Team' => 'BEdita I18N & I10N Team',
155
                'Language' => '',
156
                'MIME-Version' => '1.0',
157
                'Content-Transfer-Encoding' => '8bit',
158
                'Plural-Forms' => 'nplurals=2; plural=(n != 1);',
159
                'Content-Type' => 'text/plain; charset=utf-8',
160
            ],
161
            'pot' => [
162
                'Project-Id-Version' => 'BEdita 4',
163
                'POT-Creation-Date' => FrozenTime::now()->format('Y-m-d H:i:s'),
164
                'MIME-Version' => '1.0',
165
                'Content-Transfer-Encoding' => '8bit',
166
                'Language-Team' => 'BEdita I18N & I10N Team',
167
                'Plural-Forms' => 'nplurals=2; plural=(n != 1);',
168
                'Content-Type' => 'text/plain; charset=utf-8',
169
            ],
170
        ];
171
        foreach ($contents[$type] as $k => $v) {
172
            $result .= sprintf('"%s: %s \n"', $k, $v) . "\n";
173
        }
174
175
        return $result;
176
    }
177
}
178