Passed
Pull Request — master (#81)
by Dante
01:14
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
     * Analyze po file and translate it
24
     *
25
     * @param string $filename The po file name
26
     * @return array
27
     */
28
    public static function analyzePoFile($filename): array
29
    {
30
        $lines = file($filename);
31
        $numItems = $numNotTranslated = 0;
32
        foreach ($lines as $k => $l) {
33
            if (strpos($l, 'msgid "') === 0) {
34
                $numItems++;
35
            }
36
            if (strpos($l, 'msgstr ""') === 0 && (!isset($lines[$k + 1]) || strpos($lines[$k + 1], '"') !== 0)) {
37
                $numNotTranslated++;
38
            }
39
        }
40
        $translated = $numItems - $numNotTranslated;
41
        $percent = 0;
42
        if ($numItems > 0) {
43
            $percent = number_format($translated * 100. / $numItems, 1);
44
        }
45
46
        return compact('numItems', 'numNotTranslated', 'translated', 'percent');
47
    }
48
49
    /**
50
     * Header lines for po/pot file
51
     *
52
     * @param string $type The file type (can be 'po', 'pot')
53
     * @return string
54
     */
55
    public static function header(string $type = 'po'): string
56
    {
57
        $result = sprintf('msgid ""%smsgstr ""%s', "\n", "\n");
58
        $contents = [
59
            'po' => [
60
                'Project-Id-Version' => 'BEdita 4',
61
                'POT-Creation-Date' => FrozenTime::now()->format('Y-m-d H:i:s'),
62
                'PO-Revision-Date' => '',
63
                'Last-Translator' => '',
64
                'Language-Team' => 'BEdita I18N & I10N Team',
65
                'Language' => '',
66
                'MIME-Version' => '1.0',
67
                'Content-Transfer-Encoding' => '8bit',
68
                'Plural-Forms' => 'nplurals=2; plural=(n != 1);',
69
                'Content-Type' => 'text/plain; charset=utf-8',
70
            ],
71
            'pot' => [
72
                'Project-Id-Version' => 'BEdita 4',
73
                'POT-Creation-Date' => FrozenTime::now()->format('Y-m-d H:i:s'),
74
                'MIME-Version' => '1.0',
75
                'Content-Transfer-Encoding' => '8bit',
76
                'Language-Team' => 'BEdita I18N & I10N Team',
77
                'Plural-Forms' => 'nplurals=2; plural=(n != 1);',
78
                'Content-Type' => 'text/plain; charset=utf-8',
79
            ],
80
        ];
81
        foreach ($contents[$type] as $k => $v) {
82
            $result .= sprintf('"%s: %s \n"', $k, $v) . "\n";
83
        }
84
85
        return $result;
86
    }
87
88
    /**
89
     * Write `master.pot` file
90
     *
91
     * @return array
92
     */
93
    public static function writeMasterPot(string $localePath, array $translations): array
94
    {
95
        $info = [];
96
        $updated = false;
97
98
        foreach ($translations as $domain => $poResult) {
99
            $potFilename = sprintf('%s/%s.pot', $localePath, $domain);
100
            $info[] = sprintf('Writing new .pot file: %s', $potFilename);
101
102
            $file = new \SplFileInfo($potFilename);
103
            $pot = $file->openFile('w');
104
            $contents = file_get_contents($potFilename);
105
106
            // remove headers from pot file
107
            $contents = preg_replace('/^msgid ""\nmsgstr ""/', '', $contents);
108
            $contents = trim(preg_replace('/^"([^"]*?)"$/m', '', $contents));
109
110
            $lines = [];
111
            ksort($poResult);
112
            foreach ($poResult as $res => $contexts) {
113
                sort($contexts);
114
                foreach ($contexts as $ctx) {
115
                    $msgctxt = sprintf('msgctxt "%s"%smsgid "%s"%smsgstr ""', $ctx, "\n", $res, "\n");
116
                    $msgidstr = sprintf('msgid "%s"%smsgstr ""', $res, "\n");
117
                    $lines[] = !empty($ctx) ? $msgctxt : $msgidstr;
118
                }
119
            }
120
121
            $result = implode("\n\n", $lines);
122
            if ($contents !== $result) {
123
                $pot->fwrite(sprintf("%s\n%s\n", self::header('pot'), $result));
124
                $updated = true;
125
            }
126
        }
127
128
        return compact('info', 'updated');
129
    }
130
131
    /**
132
     * Write `.po` files
133
     *
134
     * @return array
135
     */
136
    public static function writePoFiles(array $locales, string $localePath, array &$translations): array
137
    {
138
        $info = [];
139
        if (empty($locales)) {
140
            $info[] = 'No locales set, .po files generation skipped';
141
142
            return compact('info');
143
        }
144
145
        $header = self::header('po');
146
        foreach ($locales as $loc) {
147
            $potDir = $localePath . DS . $loc;
148
            if (!file_exists($potDir)) {
149
                mkdir($potDir);
150
            }
151
            $info[] = sprintf('Language: %s', $loc);
152
            foreach (array_keys($translations) as $domain) {
153
                $potFilename = sprintf('%s/%s.pot', $localePath, $domain);
154
                $poFile = sprintf('%s/%s.po', $potDir, $domain);
155
                if (!file_exists($poFile)) {
156
                    $newPoFile = new \SplFileInfo($poFile);
157
                    $newPoFile->openFile('w')->fwrite($header);
158
                }
159
                $info[] = sprintf('Merging %s', $poFile);
160
                $mergeCmd = sprintf('msgmerge --backup=off -N -U %s %s', $poFile, $potFilename);
161
                exec($mergeCmd);
162
                $analysis = self::analyzePoFile($poFile);
163
                $info[] = sprintf(
164
                    'Translated %d of %d items - %s %%',
165
                    $analysis['translated'],
166
                    $analysis['numItems'],
167
                    $analysis['percent']
168
                );
169
                $info[] = '---------------------';
170
            }
171
        }
172
173
        return compact('info');
174
    }
175
}
176