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

Gettext::header()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 31
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 24
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 31
rs 9.536
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
                    if (!empty($ctx)) {
116
                        $lines[] = sprintf('msgctxt "%s"%smsgid "%s"%smsgstr ""', $ctx, "\n", $res, "\n");
117
                    } else {
118
                        $lines[] = sprintf('msgid "%s"%smsgstr ""', $res, "\n");
119
                    }
120
                }
121
            }
122
123
            $result = implode("\n\n", $lines);
124
            if ($contents !== $result) {
125
                $pot->fwrite(sprintf("%s\n%s\n", self::header('pot'), $result));
126
                $updated = true;
127
            }
128
        }
129
130
        return compact('info', 'updated');
131
    }
132
133
    /**
134
     * Write `.po` files
135
     *
136
     * @return array
137
     */
138
    public static function writePoFiles(array $locales, string $localePath, array &$translations): array
139
    {
140
        $info = [];
141
        if (empty($locales)) {
142
            $info[] = 'No locales set, .po files generation skipped';
143
144
            return compact('info');
145
        }
146
147
        $header = self::header('po');
148
        foreach ($locales as $loc) {
149
            $potDir = $localePath . DS . $loc;
150
            if (!file_exists($potDir)) {
151
                mkdir($potDir);
152
            }
153
            $info[] = sprintf('Language: %s', $loc);
154
            foreach (array_keys($translations) as $domain) {
155
                $potFilename = sprintf('%s/%s.pot', $localePath, $domain);
156
                $poFile = sprintf('%s/%s.po', $potDir, $domain);
157
                if (!file_exists($poFile)) {
158
                    $newPoFile = new \SplFileInfo($poFile);
159
                    $newPoFile->openFile('w')->fwrite($header);
160
                }
161
                $info[] = sprintf('Merging %s', $poFile);
162
                $mergeCmd = sprintf('msgmerge --backup=off -N -U %s %s', $poFile, $potFilename);
163
                exec($mergeCmd);
164
                $analysis = self::analyzePoFile($poFile);
165
                $info[] = sprintf(
166
                    'Translated %d of %d items - %s %%',
167
                    $analysis['translated'],
168
                    $analysis['numItems'],
169
                    $analysis['percent']
170
                );
171
                $info[] = '---------------------';
172
            }
173
        }
174
175
        return compact('info');
176
    }
177
}
178