Rev 1127 | Rev 1133 | Go to most recent revision | Only display areas with differences | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed
Rev 1127 | Rev 1130 | ||
---|---|---|---|
1 | #!/usr/bin/env php |
1 | #!/usr/bin/env php |
2 | <?php |
2 | <?php |
3 | 3 | ||
4 | /* |
4 | /* |
5 | * OIDplus 2.0 |
5 | * OIDplus 2.0 |
6 | * Copyright 2019 - 2023 Daniel Marschall, ViaThinkSoft |
6 | * Copyright 2019 - 2023 Daniel Marschall, ViaThinkSoft |
7 | * |
7 | * |
8 | * Licensed under the Apache License, Version 2.0 (the "License"); |
8 | * Licensed under the Apache License, Version 2.0 (the "License"); |
9 | * you may not use this file except in compliance with the License. |
9 | * you may not use this file except in compliance with the License. |
10 | * You may obtain a copy of the License at |
10 | * You may obtain a copy of the License at |
11 | * |
11 | * |
12 | * http://www.apache.org/licenses/LICENSE-2.0 |
12 | * http://www.apache.org/licenses/LICENSE-2.0 |
13 | * |
13 | * |
14 | * Unless required by applicable law or agreed to in writing, software |
14 | * Unless required by applicable law or agreed to in writing, software |
15 | * distributed under the License is distributed on an "AS IS" BASIS, |
15 | * distributed under the License is distributed on an "AS IS" BASIS, |
16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
17 | * See the License for the specific language governing permissions and |
17 | * See the License for the specific language governing permissions and |
18 | * limitations under the License. |
18 | * limitations under the License. |
19 | */ |
19 | */ |
20 | 20 | ||
21 | // This script updates the language message files (adding new entries and |
21 | // This script updates the language message files (adding new entries and |
22 | // removing entries that are not existing anymore). |
22 | // removing entries that are not existing anymore). |
23 | // It requires that PHP scripts are using following syntax for translations: |
23 | // It requires that PHP scripts are using following syntax for translations: |
24 | // _L('hello world',optionalParams) <recommended> |
24 | // _L('hello world',optionalParams) <recommended> |
25 | // _L("hello world",optionalParams) |
25 | // _L("hello world",optionalParams) |
26 | // and JS files: |
26 | // and JS files: |
27 | // _L('hello world',optionalParams) |
27 | // _L('hello world',optionalParams) |
28 | // _L("hello world",optionalParams) <recommended> |
28 | // _L("hello world",optionalParams) <recommended> |
29 | 29 | ||
30 | $dir = __DIR__ . '/../../'; |
30 | $dir = __DIR__ . '/../../'; |
31 | 31 | ||
32 | // TODO: Support the case messages*.xml (for than 1 message file per language) |
32 | // TODO: Support the case messages*.xml (for than 1 message file per language) |
33 | // Not sure how to solve it, because if there is a string we do not have, |
33 | // Not sure how to solve it, because if there is a string we do not have, |
34 | // to which file do we add it? |
34 | // to which file do we add it? |
35 | 35 | ||
36 | // --- |
36 | // --- |
37 | 37 | ||
38 | $langs = array(); |
38 | $langs = array(); |
39 | // TODO: Instead of hard-coding messages.xml, read it from manifest.xml |
39 | // TODO: Instead of hard-coding messages.xml, read it from manifest.xml |
40 | $tmp = glob($dir.'/plugins/'.'*'.'/language/'.'*'.'/messages.xml'); |
40 | $tmp = glob($dir.'/plugins/'.'*'.'/language/'.'*'.'/messages.xml'); |
41 | foreach ($tmp as $tmp2) { |
41 | foreach ($tmp as $tmp2) { |
42 | $tmp3 = explode('/', $tmp2); |
42 | $tmp3 = explode('/', $tmp2); |
43 | $lang = $tmp3[count($tmp3)-2]; |
43 | $lang = $tmp3[count($tmp3)-2]; |
44 | if ($lang == 'enus') continue; // ignore base lang |
44 | if ($lang == 'enus') continue; // ignore base lang |
45 | $langs[] = $lang; |
45 | $langs[] = $lang; |
46 | } |
46 | } |
47 | 47 | ||
48 | // --- |
48 | // --- |
49 | 49 | ||
50 | $all_strings = array(); |
50 | $all_strings = array(); |
51 | 51 | ||
52 | $it = new RecursiveDirectoryIterator($dir); |
52 | $it = new RecursiveDirectoryIterator($dir); |
53 | $it->setFlags(RecursiveDirectoryIterator::SKIP_DOTS); // DOES NOT WORK! Folders with . prefix still get evaluated! |
53 | $it->setFlags(RecursiveDirectoryIterator::SKIP_DOTS); // DOES NOT WORK! Folders with . prefix still get evaluated! |
54 | foreach(new RecursiveIteratorIterator($it) as $file) { |
54 | foreach(new RecursiveIteratorIterator($it) as $file) { |
55 | if ((strpos(str_replace('\\','/',realpath($file)),'/vendor/') !== false) && (strpos(str_replace('\\','/',realpath($file)),'/vendor/danielmarschall/') === false)) continue; // ignore third-party-code |
55 | if ((strpos(str_replace('\\','/',realpath($file)),'/vendor/') !== false) && (strpos(str_replace('\\','/',realpath($file)),'/vendor/danielmarschall/') === false)) continue; // ignore third-party-code |
56 | if (strpos(str_replace('\\','/',realpath($file)),'/dev/') !== false) continue; // ignore development utilities |
56 | if (strpos(str_replace('\\','/',realpath($file)),'/dev/') !== false) continue; // ignore development utilities |
57 | 57 | ||
58 | if (preg_match('@[/\\\\]\\.[^\\.]@',$file,$m)) continue; // Alternative to SKIP_DOTS |
58 | if (preg_match('@[/\\\\]\\.[^\\.]@',$file,$m)) continue; // Alternative to SKIP_DOTS |
59 | 59 | ||
60 | if ($file->getExtension() == 'php') { |
60 | if ($file->getExtension() == 'php') { |
61 | $cont = file_get_contents($file); |
61 | $cont = file_get_contents($file); |
62 | $cont = phpRemoveComments($cont); |
62 | $cont = phpRemoveComments($cont); |
63 | # $cont = str_replace('function _L($str, ...$sprintfArgs) {', '', $cont); |
63 | # $cont = str_replace('function _L($str, ...$sprintfArgs) {', '', $cont); |
64 | $cont = str_replace('_L($', '', $cont); |
64 | $cont = str_replace('_L($', '', $cont); |
65 | $strings = get_php_L_strings($cont); |
65 | $strings = get_php_L_strings($cont); |
66 | $strings_test = get_js_L_strings($cont); |
66 | $strings_test = get_js_L_strings($cont); |
67 | 67 | ||
68 | if (serialize($strings) != serialize($strings_test)) { |
68 | if (serialize($strings) != serialize($strings_test)) { |
69 | echo "Attention: File ".realpath($file)." ambiguous _L() functions\n"; |
69 | echo "Attention: File ".realpath($file)." ambiguous _L() functions\n"; |
70 | } |
70 | } |
71 | 71 | ||
72 | $all_strings = array_merge($all_strings, $strings); |
72 | $all_strings = array_merge($all_strings, $strings); |
73 | } |
73 | } |
74 | if ($file->getExtension() == 'js') { |
74 | if ($file->getExtension() == 'js') { |
75 | $cont = file_get_contents($file); |
75 | $cont = file_get_contents($file); |
76 | $cont = str_replace('function _L()', '', $cont); |
76 | $cont = str_replace('function _L()', '', $cont); |
77 | $strings = get_js_L_strings($cont); |
77 | $strings = get_js_L_strings($cont); |
78 | $all_strings = array_merge($all_strings, $strings); |
78 | $all_strings = array_merge($all_strings, $strings); |
79 | } |
79 | } |
80 | } |
80 | } |
81 | 81 | ||
82 | foreach ($all_strings as $str) { |
82 | foreach ($all_strings as $str) { |
83 | test_missing_placeholder($str); |
83 | test_missing_placeholder($str); |
84 | } |
84 | } |
85 | 85 | ||
86 | $all_strings = array_unique($all_strings); |
86 | $all_strings = array_unique($all_strings); |
87 | sort($all_strings); |
87 | sort($all_strings); |
88 | 88 | ||
89 | // --- |
89 | // --- |
90 | 90 | ||
91 | foreach ($langs as $lang) { |
91 | foreach ($langs as $lang) { |
92 | // TODO: Instead of hard-coding messages.xml, read it from manifest.xml |
92 | // TODO: Instead of hard-coding messages.xml, read it from manifest.xml |
93 | $translation_files = glob($dir.'/plugins/'.'*'.'/language/'.$lang.'/messages.xml'); |
93 | $translation_files = glob($dir.'/plugins/'.'*'.'/language/'.$lang.'/messages.xml'); |
94 | $translation_file = count($translation_files) > 0 ? $translation_files[0] : null; |
94 | $translation_file = count($translation_files) > 0 ? $translation_files[0] : null; |
95 | if (file_exists($translation_file)) { |
95 | if (file_exists($translation_file)) { |
96 | $xml = simplexml_load_string(file_get_contents($translation_file)); |
96 | $xml = simplexml_load_string(file_get_contents($translation_file)); |
97 | if (!$xml) { |
97 | if (!$xml) { |
98 | echo "STOP: Cannot load $translation_file\n"; |
98 | echo "STOP: Cannot load $translation_file\n"; |
99 | continue; |
99 | continue; |
100 | } |
100 | } |
101 | foreach ($xml->message as $msg) { |
101 | foreach ($xml->message as $msg) { |
102 | $src = trim($msg->source->__toString()); |
102 | $src = trim($msg->source->__toString()); |
103 | $dst = trim($msg->target->__toString()); |
103 | $dst = trim($msg->target->__toString()); |
104 | $translation_array[$src] = $dst; |
104 | $translation_array[$src] = $dst; |
105 | } |
105 | } |
106 | } |
106 | } |
107 | 107 | ||
108 | // --- |
108 | // --- |
109 | 109 | ||
110 | echo "Processing ".realpath($translation_file)." ...\n"; |
110 | echo "Processing ".realpath($translation_file)." ...\n"; |
111 | 111 | ||
112 | $stats_total = 0; |
112 | $stats_total = 0; |
113 | $stats_translated = 0; |
113 | $stats_translated = 0; |
114 | $stats_not_translated = 0; |
114 | $stats_not_translated = 0; |
115 | 115 | ||
116 | $cont = '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>'."\n"; |
116 | $cont = '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>'."\n"; |
117 | $cont .= '<translation'."\n"; |
117 | $cont .= '<translation'."\n"; |
118 | $cont .= ' xmlns="urn:oid:1.3.6.1.4.1.37476.2.5.2.5.4.1"'."\n"; |
118 | $cont .= ' xmlns="urn:oid:1.3.6.1.4.1.37476.2.5.2.5.4.1"'."\n"; |
119 | $cont .= ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'."\n"; |
119 | $cont .= ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'."\n"; |
120 | $cont .= ' xsi:schemaLocation="urn:oid:1.3.6.1.4.1.37476.2.5.2.5.4.1 https://oidplus.viathinksoft.com/oidplus/plugins/messages.xsd">'."\n"; |
120 | $cont .= ' xsi:schemaLocation="urn:oid:1.3.6.1.4.1.37476.2.5.2.5.4.1 https://oidplus.viathinksoft.com/oidplus/plugins/messages.xsd">'."\n"; |
121 | $cont .= "\n"; |
121 | $cont .= "\n"; |
122 | foreach ($all_strings as $string) { |
122 | foreach ($all_strings as $string) { |
123 | $stats_total++; |
123 | $stats_total++; |
124 | $string = trim($string); |
124 | $string = trim($string); |
125 | $cont .= " <message>\n"; |
125 | $cont .= " <message>\n"; |
126 | $cont .= " <source><![CDATA[\n"; |
126 | $cont .= " <source><![CDATA[\n"; |
127 | $cont .= " $string\n"; |
127 | $cont .= " $string\n"; |
128 | $cont .= " ]]></source>\n"; |
128 | $cont .= " ]]></source>\n"; |
129 | if (isset($translation_array[$string]) && !empty($translation_array[$string])) { |
129 | if (isset($translation_array[$string]) && !empty($translation_array[$string])) { |
130 | $translation_array[$string] = trim($translation_array[$string]); |
130 | $translation_array[$string] = trim($translation_array[$string]); |
131 | $stats_translated++; |
131 | $stats_translated++; |
132 | if (substr_count($string,'%') != substr_count($translation_array[$string],'%')) { |
132 | if (substr_count($string,'%') != substr_count($translation_array[$string],'%')) { |
133 | echo "\tAttention: Number of %-Replacements differs at translation of message '$string'\n"; |
133 | echo "\tAttention: Number of %-Replacements differs at translation of message '$string'\n"; |
134 | } |
134 | } |
135 | $cont .= " <target><![CDATA[\n"; |
135 | $cont .= " <target><![CDATA[\n"; |
136 | $cont .= " ".$translation_array[$string]."\n"; |
136 | $cont .= " ".$translation_array[$string]."\n"; |
137 | $cont .= " ]]></target>\n"; |
137 | $cont .= " ]]></target>\n"; |
138 | test_missing_placeholder($translation_array[$string]); |
138 | test_missing_placeholder($translation_array[$string]); |
139 | } else { |
139 | } else { |
140 | $stats_not_translated++; |
140 | $stats_not_translated++; |
141 | $cont .= " <target><![CDATA[\n"; |
141 | $cont .= " <target><![CDATA[\n"; |
142 | $cont .= " ]]></target><!-- TODO: TRANSLATE -->\n"; |
142 | $cont .= " ]]></target><!-- TODO: TRANSLATE -->\n"; |
143 | } |
143 | } |
144 | $cont .= " </message>\n"; |
144 | $cont .= " </message>\n"; |
145 | } |
145 | } |
146 | $cont .= "</translation>\n"; |
146 | $cont .= "</translation>\n"; |
147 | file_put_contents($translation_file, $cont); |
147 | file_put_contents($translation_file, $cont); |
148 | 148 | ||
149 | echo "\t$stats_total total messages, $stats_translated already translated (".round($stats_translated/$stats_total*100,2)."%), $stats_not_translated not translated (".round($stats_not_translated/$stats_total*100,2)."%)\n"; |
149 | echo "\t$stats_total total messages, $stats_translated already translated (".round($stats_translated/$stats_total*100,2)."%), $stats_not_translated not translated (".round($stats_not_translated/$stats_total*100,2)."%)\n"; |
150 | echo "\tDone...\n"; |
150 | echo "\tDone...\n"; |
151 | } |
151 | } |
152 | 152 | ||
153 | if (count($langs) > 0) { |
153 | if (count($langs) > 0) { |
154 | echo "All done!\n"; |
154 | echo "All done!\n"; |
155 | } else { |
155 | } else { |
156 | echo "Attention: No language plugins found!\n"; |
156 | echo "Attention: No language plugins found!\n"; |
157 | } |
157 | } |
158 | 158 | ||
159 | # --- |
159 | # --- |
160 | 160 | ||
- | 161 | /** |
|
- | 162 | * @param string $cont |
|
- | 163 | * @return string[] |
|
- | 164 | */ |
|
161 | function get_js_L_strings($cont) { |
165 | function get_js_L_strings(string $cont): array { |
162 | // Works with JavaScript and PHP |
166 | // Works with JavaScript and PHP |
163 | $cont = preg_replace('@/\\*.+\\*/@ismU', '', $cont); |
167 | $cont = preg_replace('@/\\*.+\\*/@ismU', '', $cont); |
164 | $cont = str_replace('\\"', chr(1), $cont); |
168 | $cont = str_replace('\\"', chr(1), $cont); |
165 | $cont = str_replace("\\'", chr(2), $cont); |
169 | $cont = str_replace("\\'", chr(2), $cont); |
166 | $cont = str_replace("\\\\", "\\", $cont); |
170 | $cont = str_replace("\\\\", "\\", $cont); |
167 | $m = array(); |
171 | $m = array(); |
168 | preg_match_all('@[^_A-Za-z0-9]_L\\s*\\(([^\\)]*)(["\'])(.+)\\2@ismU', $cont, $m); |
172 | preg_match_all('@[^_A-Za-z0-9]_L\\s*\\(([^\\)]*)(["\'])(.+)\\2@ismU', $cont, $m); |
169 | foreach ($m[3] as &$x) { |
173 | foreach ($m[3] as &$x) { |
170 | $x = str_replace(chr(1), '"', $x); |
174 | $x = str_replace(chr(1), '"', $x); |
171 | $x = str_replace(chr(2), "'", $x); |
175 | $x = str_replace(chr(2), "'", $x); |
172 | } |
176 | } |
173 | return $m[3]; |
177 | return $m[3]; |
174 | } |
178 | } |
175 | 179 | ||
- | 180 | /** |
|
- | 181 | * @param string $cont |
|
- | 182 | * @return string[] |
|
- | 183 | */ |
|
176 | function get_php_L_strings($cont) { |
184 | function get_php_L_strings(string $cont): array { |
177 | // Works only with PHP |
185 | // Works only with PHP |
178 | $out = array(); |
186 | $out = array(); |
179 | $tokens = token_get_all($cont); |
187 | $tokens = token_get_all($cont); |
180 | $activated = 0; |
188 | $activated = 0; |
181 | foreach ($tokens as $token) { |
189 | foreach ($tokens as $token) { |
182 | if (is_array($token)) { |
190 | if (is_array($token)) { |
183 | if (($token[0] == T_STRING) && ($token[1] == '_L')) { |
191 | if (($token[0] == T_STRING) && ($token[1] == '_L')) { |
184 | $activated = 1; |
192 | $activated = 1; |
185 | } else if (($activated == 1) && ($token[0] == T_CONSTANT_ENCAPSED_STRING)) { |
193 | } else if (($activated == 1) && ($token[0] == T_CONSTANT_ENCAPSED_STRING)) { |
186 | $tmp = stripcslashes($token[1]); |
194 | $tmp = stripcslashes($token[1]); |
187 | $out[] = substr($tmp,1,strlen($tmp)-2); |
195 | $out[] = substr($tmp,1,strlen($tmp)-2); |
188 | $activated = 0; |
196 | $activated = 0; |
189 | } |
197 | } |
190 | } |
198 | } |
191 | } |
199 | } |
192 | return $out; |
200 | return $out; |
193 | } |
201 | } |
194 | 202 | ||
- | 203 | /** |
|
- | 204 | * @param string $test |
|
- | 205 | * @return void |
|
- | 206 | */ |
|
195 | function test_missing_placeholder($test) { |
207 | function test_missing_placeholder(string $test) { |
196 | $max = -1; |
208 | $max = -1; |
197 | for ($i=99; $i>=1; $i--) { |
209 | for ($i=99; $i>=1; $i--) { |
198 | if (strpos($test, '%'.$i) !== false) { |
210 | if (strpos($test, '%'.$i) !== false) { |
199 | $max = $i; |
211 | $max = $i; |
200 | break; |
212 | break; |
201 | } |
213 | } |
202 | } |
214 | } |
203 | 215 | ||
204 | for ($i=1; $i<=$max; $i++) { |
216 | for ($i=1; $i<=$max; $i++) { |
205 | if (strpos($test, '%'.$i) === false) { |
217 | if (strpos($test, '%'.$i) === false) { |
206 | echo "Attention: %$i is missing in string '$test'!\n"; |
218 | echo "Attention: %$i is missing in string '$test'!\n"; |
207 | $max = $i; |
219 | $max = $i; |
208 | break; |
220 | break; |
209 | } |
221 | } |
210 | } |
222 | } |
211 | 223 | ||
212 | $test = preg_replace('@%([1-9][0-9]|%)*@ism', '', $test); |
224 | $test = preg_replace('@%([1-9][0-9]|%)*@ism', '', $test); |
213 | if (strpos($test,'%') !== false) { |
225 | if (strpos($test,'%') !== false) { |
214 | echo "Attention: Wrong percentage sign in '$test'!\n"; |
226 | echo "Attention: Wrong percentage sign in '$test'!\n"; |
215 | } |
227 | } |
216 | } |
228 | } |
217 | 229 | ||
218 | # --- |
230 | # --- |
219 | 231 | ||
- | 232 | /** |
|
- | 233 | * @param string $fileStr |
|
- | 234 | * @return string |
|
- | 235 | */ |
|
220 | function phpRemoveComments($fileStr) { |
236 | function phpRemoveComments(string $fileStr): string { |
221 | 237 | ||
222 | // https://stackoverflow.com/questions/503871/best-way-to-automatically-remove-comments-from-php-code |
238 | // https://stackoverflow.com/questions/503871/best-way-to-automatically-remove-comments-from-php-code |
223 | 239 | ||
224 | $newStr = ''; |
240 | $newStr = ''; |
225 | 241 | ||
226 | $commentTokens = array(T_COMMENT); |
242 | $commentTokens = array(T_COMMENT); |
227 | 243 | ||
228 | if (defined('T_DOC_COMMENT')) $commentTokens[] = T_DOC_COMMENT; // PHP 5 |
244 | if (defined('T_DOC_COMMENT')) $commentTokens[] = T_DOC_COMMENT; // PHP 5 |
229 | if (defined('T_ML_COMMENT')) $commentTokens[] = T_ML_COMMENT; // PHP 4 |
245 | if (defined('T_ML_COMMENT')) $commentTokens[] = T_ML_COMMENT; // PHP 4 |
230 | 246 | ||
231 | $tokens = token_get_all($fileStr); |
247 | $tokens = token_get_all($fileStr); |
232 | 248 | ||
233 | foreach ($tokens as $token) { |
249 | foreach ($tokens as $token) { |
234 | if (is_array($token)) { |
250 | if (is_array($token)) { |
235 | if (in_array($token[0], $commentTokens)) continue; |
251 | if (in_array($token[0], $commentTokens)) continue; |
236 | $token = $token[1]; |
252 | $token = $token[1]; |
237 | } |
253 | } |
238 | $newStr .= $token; |
254 | $newStr .= $token; |
239 | } |
255 | } |
240 | 256 | ||
241 | return $newStr; |
257 | return $newStr; |
242 | 258 | ||
243 | } |
259 | } |
244 | 260 |