Rev 1116 | Rev 1130 | Go to most recent revision | Only display areas with differences | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed
Rev 1116 | Rev 1127 | ||
---|---|---|---|
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 - 2021 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 | foreach(new RecursiveIteratorIterator($it) as $file) { |
54 | foreach(new RecursiveIteratorIterator($it) as $file) { |
54 | 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 |
55 | 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 | ||
- | 58 | if (preg_match('@[/\\\\]\\.[^\\.]@',$file,$m)) continue; // Alternative to SKIP_DOTS |
|
- | 59 | ||
56 | if ($file->getExtension() == 'php') { |
60 | if ($file->getExtension() == 'php') { |
57 | $cont = file_get_contents($file); |
61 | $cont = file_get_contents($file); |
58 | $cont = phpRemoveComments($cont); |
62 | $cont = phpRemoveComments($cont); |
59 | # $cont = str_replace('function _L($str, ...$sprintfArgs) {', '', $cont); |
63 | # $cont = str_replace('function _L($str, ...$sprintfArgs) {', '', $cont); |
60 | $cont = str_replace('_L($', '', $cont); |
64 | $cont = str_replace('_L($', '', $cont); |
61 | $strings = get_php_L_strings($cont); |
65 | $strings = get_php_L_strings($cont); |
62 | $strings_test = get_js_L_strings($cont); |
66 | $strings_test = get_js_L_strings($cont); |
63 | 67 | ||
64 | if (serialize($strings) != serialize($strings_test)) { |
68 | if (serialize($strings) != serialize($strings_test)) { |
65 | echo "Attention: File ".realpath($file)." ambiguous _L() functions\n"; |
69 | echo "Attention: File ".realpath($file)." ambiguous _L() functions\n"; |
66 | } |
70 | } |
67 | 71 | ||
68 | $all_strings = array_merge($all_strings, $strings); |
72 | $all_strings = array_merge($all_strings, $strings); |
69 | } |
73 | } |
70 | if ($file->getExtension() == 'js') { |
74 | if ($file->getExtension() == 'js') { |
71 | $cont = file_get_contents($file); |
75 | $cont = file_get_contents($file); |
72 | $cont = str_replace('function _L()', '', $cont); |
76 | $cont = str_replace('function _L()', '', $cont); |
73 | $strings = get_js_L_strings($cont); |
77 | $strings = get_js_L_strings($cont); |
74 | $all_strings = array_merge($all_strings, $strings); |
78 | $all_strings = array_merge($all_strings, $strings); |
75 | } |
79 | } |
76 | } |
80 | } |
77 | 81 | ||
78 | foreach ($all_strings as $str) { |
82 | foreach ($all_strings as $str) { |
79 | test_missing_placeholder($str); |
83 | test_missing_placeholder($str); |
80 | } |
84 | } |
81 | 85 | ||
82 | $all_strings = array_unique($all_strings); |
86 | $all_strings = array_unique($all_strings); |
83 | sort($all_strings); |
87 | sort($all_strings); |
84 | 88 | ||
85 | // --- |
89 | // --- |
86 | 90 | ||
87 | foreach ($langs as $lang) { |
91 | foreach ($langs as $lang) { |
88 | // 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 |
89 | $translation_files = glob($dir.'/plugins/'.'*'.'/language/'.$lang.'/messages.xml'); |
93 | $translation_files = glob($dir.'/plugins/'.'*'.'/language/'.$lang.'/messages.xml'); |
90 | $translation_file = count($translation_files) > 0 ? $translation_files[0] : null; |
94 | $translation_file = count($translation_files) > 0 ? $translation_files[0] : null; |
91 | if (file_exists($translation_file)) { |
95 | if (file_exists($translation_file)) { |
92 | $xml = simplexml_load_string(file_get_contents($translation_file)); |
96 | $xml = simplexml_load_string(file_get_contents($translation_file)); |
93 | if (!$xml) { |
97 | if (!$xml) { |
94 | echo "STOP: Cannot load $translation_file\n"; |
98 | echo "STOP: Cannot load $translation_file\n"; |
95 | continue; |
99 | continue; |
96 | } |
100 | } |
97 | foreach ($xml->message as $msg) { |
101 | foreach ($xml->message as $msg) { |
98 | $src = trim($msg->source->__toString()); |
102 | $src = trim($msg->source->__toString()); |
99 | $dst = trim($msg->target->__toString()); |
103 | $dst = trim($msg->target->__toString()); |
100 | $translation_array[$src] = $dst; |
104 | $translation_array[$src] = $dst; |
101 | } |
105 | } |
102 | } |
106 | } |
103 | 107 | ||
104 | // --- |
108 | // --- |
105 | 109 | ||
106 | echo "Processing ".realpath($translation_file)." ...\n"; |
110 | echo "Processing ".realpath($translation_file)." ...\n"; |
107 | 111 | ||
108 | $stats_total = 0; |
112 | $stats_total = 0; |
109 | $stats_translated = 0; |
113 | $stats_translated = 0; |
110 | $stats_not_translated = 0; |
114 | $stats_not_translated = 0; |
111 | 115 | ||
112 | $cont = '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>'."\n"; |
116 | $cont = '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>'."\n"; |
113 | $cont .= '<translation'."\n"; |
117 | $cont .= '<translation'."\n"; |
114 | $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"; |
115 | $cont .= ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'."\n"; |
119 | $cont .= ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'."\n"; |
116 | $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"; |
117 | $cont .= "\n"; |
121 | $cont .= "\n"; |
118 | foreach ($all_strings as $string) { |
122 | foreach ($all_strings as $string) { |
119 | $stats_total++; |
123 | $stats_total++; |
120 | $string = trim($string); |
124 | $string = trim($string); |
121 | $cont .= " <message>\n"; |
125 | $cont .= " <message>\n"; |
122 | $cont .= " <source><![CDATA[\n"; |
126 | $cont .= " <source><![CDATA[\n"; |
123 | $cont .= " $string\n"; |
127 | $cont .= " $string\n"; |
124 | $cont .= " ]]></source>\n"; |
128 | $cont .= " ]]></source>\n"; |
125 | if (isset($translation_array[$string]) && !empty($translation_array[$string])) { |
129 | if (isset($translation_array[$string]) && !empty($translation_array[$string])) { |
126 | $translation_array[$string] = trim($translation_array[$string]); |
130 | $translation_array[$string] = trim($translation_array[$string]); |
127 | $stats_translated++; |
131 | $stats_translated++; |
128 | if (substr_count($string,'%') != substr_count($translation_array[$string],'%')) { |
132 | if (substr_count($string,'%') != substr_count($translation_array[$string],'%')) { |
129 | 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"; |
130 | } |
134 | } |
131 | $cont .= " <target><![CDATA[\n"; |
135 | $cont .= " <target><![CDATA[\n"; |
132 | $cont .= " ".$translation_array[$string]."\n"; |
136 | $cont .= " ".$translation_array[$string]."\n"; |
133 | $cont .= " ]]></target>\n"; |
137 | $cont .= " ]]></target>\n"; |
134 | test_missing_placeholder($translation_array[$string]); |
138 | test_missing_placeholder($translation_array[$string]); |
135 | } else { |
139 | } else { |
136 | $stats_not_translated++; |
140 | $stats_not_translated++; |
137 | $cont .= " <target><![CDATA[\n"; |
141 | $cont .= " <target><![CDATA[\n"; |
138 | $cont .= " ]]></target><!-- TODO: TRANSLATE -->\n"; |
142 | $cont .= " ]]></target><!-- TODO: TRANSLATE -->\n"; |
139 | } |
143 | } |
140 | $cont .= " </message>\n"; |
144 | $cont .= " </message>\n"; |
141 | } |
145 | } |
142 | $cont .= "</translation>\n"; |
146 | $cont .= "</translation>\n"; |
143 | file_put_contents($translation_file, $cont); |
147 | file_put_contents($translation_file, $cont); |
144 | 148 | ||
145 | 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"; |
146 | echo "\tDone...\n"; |
150 | echo "\tDone...\n"; |
147 | } |
151 | } |
148 | 152 | ||
149 | if (count($langs) > 0) { |
153 | if (count($langs) > 0) { |
150 | echo "All done!\n"; |
154 | echo "All done!\n"; |
151 | } else { |
155 | } else { |
152 | echo "Attention: No language plugins found!\n"; |
156 | echo "Attention: No language plugins found!\n"; |
153 | } |
157 | } |
154 | 158 | ||
155 | # --- |
159 | # --- |
156 | 160 | ||
157 | function get_js_L_strings($cont) { |
161 | function get_js_L_strings($cont) { |
158 | // Works with JavaScript and PHP |
162 | // Works with JavaScript and PHP |
159 | $cont = preg_replace('@/\\*.+\\*/@ismU', '', $cont); |
163 | $cont = preg_replace('@/\\*.+\\*/@ismU', '', $cont); |
160 | $cont = str_replace('\\"', chr(1), $cont); |
164 | $cont = str_replace('\\"', chr(1), $cont); |
161 | $cont = str_replace("\\'", chr(2), $cont); |
165 | $cont = str_replace("\\'", chr(2), $cont); |
162 | $cont = str_replace("\\\\", "\\", $cont); |
166 | $cont = str_replace("\\\\", "\\", $cont); |
163 | $m = array(); |
167 | $m = array(); |
164 | preg_match_all('@[^_A-Za-z0-9]_L\\s*\\(([^\\)]*)(["\'])(.+)\\2@ismU', $cont, $m); |
168 | preg_match_all('@[^_A-Za-z0-9]_L\\s*\\(([^\\)]*)(["\'])(.+)\\2@ismU', $cont, $m); |
165 | foreach ($m[3] as &$x) { |
169 | foreach ($m[3] as &$x) { |
166 | $x = str_replace(chr(1), '"', $x); |
170 | $x = str_replace(chr(1), '"', $x); |
167 | $x = str_replace(chr(2), "'", $x); |
171 | $x = str_replace(chr(2), "'", $x); |
168 | } |
172 | } |
169 | return $m[3]; |
173 | return $m[3]; |
170 | } |
174 | } |
171 | 175 | ||
172 | function get_php_L_strings($cont) { |
176 | function get_php_L_strings($cont) { |
173 | // Works only with PHP |
177 | // Works only with PHP |
174 | $out = array(); |
178 | $out = array(); |
175 | $tokens = token_get_all($cont); |
179 | $tokens = token_get_all($cont); |
176 | $activated = 0; |
180 | $activated = 0; |
177 | foreach ($tokens as $token) { |
181 | foreach ($tokens as $token) { |
178 | if (is_array($token)) { |
182 | if (is_array($token)) { |
179 | if (($token[0] == T_STRING) && ($token[1] == '_L')) { |
183 | if (($token[0] == T_STRING) && ($token[1] == '_L')) { |
180 | $activated = 1; |
184 | $activated = 1; |
181 | } else if (($activated == 1) && ($token[0] == T_CONSTANT_ENCAPSED_STRING)) { |
185 | } else if (($activated == 1) && ($token[0] == T_CONSTANT_ENCAPSED_STRING)) { |
182 | $tmp = stripcslashes($token[1]); |
186 | $tmp = stripcslashes($token[1]); |
183 | $out[] = substr($tmp,1,strlen($tmp)-2); |
187 | $out[] = substr($tmp,1,strlen($tmp)-2); |
184 | $activated = 0; |
188 | $activated = 0; |
185 | } |
189 | } |
186 | } |
190 | } |
187 | } |
191 | } |
188 | return $out; |
192 | return $out; |
189 | } |
193 | } |
190 | 194 | ||
191 | function test_missing_placeholder($test) { |
195 | function test_missing_placeholder($test) { |
192 | $max = -1; |
196 | $max = -1; |
193 | for ($i=99; $i>=1; $i--) { |
197 | for ($i=99; $i>=1; $i--) { |
194 | if (strpos($test, '%'.$i) !== false) { |
198 | if (strpos($test, '%'.$i) !== false) { |
195 | $max = $i; |
199 | $max = $i; |
196 | break; |
200 | break; |
197 | } |
201 | } |
198 | } |
202 | } |
199 | 203 | ||
200 | for ($i=1; $i<=$max; $i++) { |
204 | for ($i=1; $i<=$max; $i++) { |
201 | if (strpos($test, '%'.$i) === false) { |
205 | if (strpos($test, '%'.$i) === false) { |
202 | echo "Attention: %$i is missing in string '$test'!\n"; |
206 | echo "Attention: %$i is missing in string '$test'!\n"; |
203 | $max = $i; |
207 | $max = $i; |
204 | break; |
208 | break; |
205 | } |
209 | } |
206 | } |
210 | } |
207 | 211 | ||
208 | $test = preg_replace('@%([1-9][0-9]|%)*@ism', '', $test); |
212 | $test = preg_replace('@%([1-9][0-9]|%)*@ism', '', $test); |
209 | if (strpos($test,'%') !== false) { |
213 | if (strpos($test,'%') !== false) { |
210 | echo "Attention: Wrong percentage sign in '$test'!\n"; |
214 | echo "Attention: Wrong percentage sign in '$test'!\n"; |
211 | } |
215 | } |
212 | } |
216 | } |
213 | 217 | ||
214 | # --- |
218 | # --- |
215 | 219 | ||
216 | function phpRemoveComments($fileStr) { |
220 | function phpRemoveComments($fileStr) { |
217 | 221 | ||
218 | // https://stackoverflow.com/questions/503871/best-way-to-automatically-remove-comments-from-php-code |
222 | // https://stackoverflow.com/questions/503871/best-way-to-automatically-remove-comments-from-php-code |
219 | 223 | ||
220 | $newStr = ''; |
224 | $newStr = ''; |
221 | 225 | ||
222 | $commentTokens = array(T_COMMENT); |
226 | $commentTokens = array(T_COMMENT); |
223 | 227 | ||
224 | if (defined('T_DOC_COMMENT')) $commentTokens[] = T_DOC_COMMENT; // PHP 5 |
228 | if (defined('T_DOC_COMMENT')) $commentTokens[] = T_DOC_COMMENT; // PHP 5 |
225 | if (defined('T_ML_COMMENT')) $commentTokens[] = T_ML_COMMENT; // PHP 4 |
229 | if (defined('T_ML_COMMENT')) $commentTokens[] = T_ML_COMMENT; // PHP 4 |
226 | 230 | ||
227 | $tokens = token_get_all($fileStr); |
231 | $tokens = token_get_all($fileStr); |
228 | 232 | ||
229 | foreach ($tokens as $token) { |
233 | foreach ($tokens as $token) { |
230 | if (is_array($token)) { |
234 | if (is_array($token)) { |
231 | if (in_array($token[0], $commentTokens)) continue; |
235 | if (in_array($token[0], $commentTokens)) continue; |
232 | $token = $token[1]; |
236 | $token = $token[1]; |
233 | } |
237 | } |
234 | $newStr .= $token; |
238 | $newStr .= $token; |
235 | } |
239 | } |
236 | 240 | ||
237 | return $newStr; |
241 | return $newStr; |
238 | 242 | ||
239 | } |
243 | } |
240 | 244 |