Merge lp:~widelands-dev/widelands/translation_stats into lp:widelands
- translation_stats
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 8475 |
Proposed branch: | lp:~widelands-dev/widelands/translation_stats |
Merge into: | lp:widelands |
Diff against target: |
749 lines (+474/-69) 6 files modified
data/i18n/translation_stats.conf (+182/-0) src/ui_fsmenu/CMakeLists.txt (+1/-0) src/ui_fsmenu/options.cc (+123/-67) src/ui_fsmenu/options.h (+16/-2) utils/merge_and_push_translations.sh (+5/-0) utils/update_translation_stats.py (+147/-0) |
To merge this branch: | bzr merge lp:~widelands-dev/widelands/translation_stats |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
kaputtnik (community) | Needs Fixing | ||
GunChleoc | Needs Resubmitting | ||
SirVer | Approve | ||
Review via email: mp+332029@code.launchpad.net |
Commit message
Show translation stats next to the language selection menu and invite translators if a translation is incomplete, with the help of the Translate Toolkit.
- Added a new utils script "update_
This script is now called on every translation pull.
- Added translation stats + invitation to translators to Options.
Description of the change
Some translations have very little work accomplished, so I thought we'd better tell the user so. This also gives us an opportunity to invite translators.
bunnybot (widelandsofficial) wrote : | # |
kaputtnik (franku) wrote : | # |
This is good approach to get more translators, i think.
Translation stats: I guess the count of 'total' is ever the same, because English is the base language. Is it needed to store this value for each language?
The text for RTL languages are messed up somehow. I guess this will be fixed if the string is translated:
"is 0% complete. If you whish to help us translate, please visit [language name] The translation intowidelands.
But if some one is willing to help translate, he should know English. So i would suggest to keep the English sentence here. Anyway adding "https://" to the underlined words would be helpful to show that this is an internet address.
SirVer (sirver) wrote : | # |
code lgtm, a few nits.
not tested.
GunChleoc (gunchleoc) wrote : | # |
Answers to SirVer's comments in-line.
> Translation stats: I guess the count of 'total' is ever the same, because English is the base language. Is it needed to store this value for each language?
You are right, I should refactor that.
> The text for RTL languages are messed up somehow. I guess this will be fixed if the string is translated: <snip>
This is an issue with the BiDi support in the font renderer, so I'd rather not change this just to hide the bug. And there are actually translators who don't speak English that well - I was recently at a localizer's meeting and having a language other than English in the translation tool's UI was a desired feature for some of them.
SirVer (sirver) wrote : | # |
more comments inline.
GunChleoc (gunchleoc) wrote : | # |
I just pushed another update, where I write the total only once as suggested by Kaputtnik. I still need to clean up the Python script incl. the regex.
SirVer (sirver) : | # |
GunChleoc (gunchleoc) wrote : | # |
Thanks for the comment, I mixed something up there. Will replace the vector with a map and get rid of the std::sort call.
GunChleoc (gunchleoc) wrote : | # |
This should be ready now.
I left the data container in Python as it is with the total for each locale, because the entires come in per directory rather than per locale. So, I sacrificed some memory in favour of code readability + performance.
kaputtnik (franku) wrote : | # |
I have run the update_
Looks like this could be run only with python2. Maybe add a comment?
I got a list out of range error:
$ > python2 utils/update_
Fetching translation stats .......
Locale Total Translated
------ ----- ----------
Something went wrong:
Traceback (most recent call last):
File "utils/
result = generate_
File "utils/
result = result + 'total=' + str(locale_
IndexError: list index out of range
Do i miss something?
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 2722. State: failed. Details: https:/
Appveyor build 2534. State: failed. Details: https:/
GunChleoc (gunchleoc) wrote : | # |
Thanks for pointing that out - there are multiple things in that script that won't work with Python 3 and a fix isn't trivial, so I've changed the Python header.
kaputtnik (franku) wrote : | # |
The regex doesn't match here and the match is always 'None', resulting in the List out of range error.
Don't know how to fix the regex... but maybe adding an else clause? See diff comment.
GunChleoc (gunchleoc) wrote : | # |
It fails for me with Python3 too, but not with Python2. Good idea about the error handling though. I've added some :)
kaputtnik (franku) wrote : | # |
Just want to mention that i did run the file with python2 :-) This is what i get now:
$:> python2 utils/update_
Fetching translation stats .
ERROR: Invalid line in pocount output:
/home/kaputtnik
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 2729. State: passed. Details: https:/
Appveyor build 2541. State: failed. Details: https:/
GunChleoc (gunchleoc) wrote : | # |
Looks like your version of pocount produces slightly different output. Can you please try again and double-check if the resulting conf looks OK?
kaputtnik (franku) wrote : | # |
My version of translate-toolkit is 2.2.5-1.
Pulled your changes but no change:
$:> python2 utils/update_
Fetching translation stats .
ERROR: Invalid line in pocount output:
/home/kaputtnik
I have tried to print the line showing escape sequences by adjusting line 106 like "...line.
$:> python2 utils/update_
Fetching translation stats .
ERROR: Invalid line in pocount output:
\x1b[95m/
So tabs are there plus escape sequences showing colored output.
GunChleoc (gunchleoc) wrote : | # |
I am now testing for non-Word characters rather than tabs or spaces. Does that work for you?
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 2743. State: errored. Details: https:/
Appveyor build 2555. State: failed. Details: https:/
kaputtnik (franku) wrote : | # |
No, it doesn't work :(
Whereas the version of pocount used by me has an option '--no-color', it is maybe better to use another pocount output format like --csv in combination with python csv module?
https:/
I guess the csv format provided by pocount will not change when switching the version of translate-toolkit. So the script might get more stable without using a fragile regexp.
For comparison her is the output with option --csv on my machine:
$:> pocount --csv po/map_
Filename, Translated Messages, Translated Source Words, Translated Target Words, Fuzzy Messages, Fuzzy Source Words, Untranslated Messages, Untranslated Source Words, Total Message, Total Source Words, Review Messages, Review Source Words
po/map_
GunChleoc (gunchleoc) wrote : | # |
Much better! No idea why I didn't use the csv format in the first place.
kaputtnik (franku) wrote : | # |
Now its working :-)
Could you please add 'https:/
GunChleoc (gunchleoc) wrote : | # |
I actually removed that https on purpose, it adds visual clutter and makes the string too long to fir.
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 2746. State: errored. Details: https:/
Appveyor build 2558. State: success. Details: https:/
GunChleoc (gunchleoc) wrote : | # |
I gave it another try, adding https:// results in a string truncation, so I'm leaving it out.
I found another bug where I was parsing the wrong column, so I've added some error handling and am parsing the header info.
Thanks for all your help in debugging this thing!
@bunnybot merge
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 2754. State: passed. Details: https:/
Appveyor build 2566. State: success. Details: https:/
Preview Diff
1 | === added file 'data/i18n/translation_stats.conf' |
2 | --- data/i18n/translation_stats.conf 1970-01-01 00:00:00 +0000 |
3 | +++ data/i18n/translation_stats.conf 2017-11-04 09:41:54 +0000 |
4 | @@ -0,0 +1,182 @@ |
5 | +[global] |
6 | +total=44699 |
7 | + |
8 | +[ar] |
9 | +translated=8657 |
10 | + |
11 | +[ast] |
12 | +translated=469 |
13 | + |
14 | +[bg] |
15 | +translated=44699 |
16 | + |
17 | +[br] |
18 | +translated=422 |
19 | + |
20 | +[ca] |
21 | +translated=44689 |
22 | + |
23 | +[cs] |
24 | +translated=43115 |
25 | + |
26 | +[da] |
27 | +translated=42285 |
28 | + |
29 | +[de] |
30 | +translated=44689 |
31 | + |
32 | +[el] |
33 | +translated=491 |
34 | + |
35 | +[en_CA] |
36 | +translated=546 |
37 | + |
38 | +[en_GB] |
39 | +translated=41937 |
40 | + |
41 | +[en_US] |
42 | +translated=455 |
43 | + |
44 | +[eo] |
45 | +translated=4713 |
46 | + |
47 | +[es] |
48 | +translated=44288 |
49 | + |
50 | +[et] |
51 | +translated=575 |
52 | + |
53 | +[eu] |
54 | +translated=1338 |
55 | + |
56 | +[fa] |
57 | +translated=115 |
58 | + |
59 | +[fi] |
60 | +translated=44689 |
61 | + |
62 | +[fr] |
63 | +translated=44699 |
64 | + |
65 | +[ga] |
66 | +translated=0 |
67 | + |
68 | +[gd] |
69 | +translated=42984 |
70 | + |
71 | +[gl] |
72 | +translated=10382 |
73 | + |
74 | +[he] |
75 | +translated=420 |
76 | + |
77 | +[hi] |
78 | +translated=26 |
79 | + |
80 | +[hr] |
81 | +translated=3172 |
82 | + |
83 | +[hu] |
84 | +translated=33179 |
85 | + |
86 | +[ia] |
87 | +translated=126 |
88 | + |
89 | +[id] |
90 | +translated=31 |
91 | + |
92 | +[it] |
93 | +translated=36868 |
94 | + |
95 | +[ja] |
96 | +translated=6944 |
97 | + |
98 | +[jv] |
99 | +translated=22 |
100 | + |
101 | +[ka] |
102 | +translated=4 |
103 | + |
104 | +[ko] |
105 | +translated=24920 |
106 | + |
107 | +[krl] |
108 | +translated=103 |
109 | + |
110 | +[la] |
111 | +translated=3994 |
112 | + |
113 | +[lt] |
114 | +translated=202 |
115 | + |
116 | +[mr] |
117 | +translated=7 |
118 | + |
119 | +[ms] |
120 | +translated=10192 |
121 | + |
122 | +[my] |
123 | +translated=30 |
124 | + |
125 | +[nb] |
126 | +translated=9055 |
127 | + |
128 | +[nds] |
129 | +translated=874 |
130 | + |
131 | +[nl] |
132 | +translated=33794 |
133 | + |
134 | +[nn] |
135 | +translated=2845 |
136 | + |
137 | +[oc] |
138 | +translated=193 |
139 | + |
140 | +[pl] |
141 | +translated=41731 |
142 | + |
143 | +[pt] |
144 | +translated=32884 |
145 | + |
146 | +[pt_BR] |
147 | +translated=14496 |
148 | + |
149 | +[ro] |
150 | +translated=1105 |
151 | + |
152 | +[ru] |
153 | +translated=43920 |
154 | + |
155 | +[rw] |
156 | +translated=48 |
157 | + |
158 | +[si] |
159 | +translated=283 |
160 | + |
161 | +[sk] |
162 | +translated=39387 |
163 | + |
164 | +[sl] |
165 | +translated=1010 |
166 | + |
167 | +[sr] |
168 | +translated=177 |
169 | + |
170 | +[sv] |
171 | +translated=41420 |
172 | + |
173 | +[tr] |
174 | +translated=2030 |
175 | + |
176 | +[uk] |
177 | +translated=4161 |
178 | + |
179 | +[vi] |
180 | +translated=1197 |
181 | + |
182 | +[zh_CN] |
183 | +translated=2202 |
184 | + |
185 | +[zh_TW] |
186 | +translated=641 |
187 | |
188 | === modified file 'src/ui_fsmenu/CMakeLists.txt' |
189 | --- src/ui_fsmenu/CMakeLists.txt 2017-05-14 20:06:48 +0000 |
190 | +++ src/ui_fsmenu/CMakeLists.txt 2017-11-04 09:41:54 +0000 |
191 | @@ -10,6 +10,7 @@ |
192 | graphic_fonthandler |
193 | graphic_text |
194 | graphic_text_constants |
195 | + graphic_text_layout |
196 | helper |
197 | io_filesystem |
198 | logic_constants |
199 | |
200 | === modified file 'src/ui_fsmenu/options.cc' |
201 | --- src/ui_fsmenu/options.cc 2017-09-11 16:59:41 +0000 |
202 | +++ src/ui_fsmenu/options.cc 2017-11-04 09:41:54 +0000 |
203 | @@ -36,6 +36,7 @@ |
204 | #include "graphic/text/bidi.h" |
205 | #include "graphic/text/font_set.h" |
206 | #include "graphic/text_constants.h" |
207 | +#include "graphic/text_layout.h" |
208 | #include "helper.h" |
209 | #include "io/filesystem/layered_filesystem.h" |
210 | #include "logic/constants.h" |
211 | @@ -47,23 +48,6 @@ |
212 | |
213 | namespace { |
214 | |
215 | -// Data model for the entries in the language selection list. |
216 | -struct LanguageEntry { |
217 | - LanguageEntry(const std::string& init_localename, |
218 | - const std::string& init_descname, |
219 | - const std::string& init_sortname) |
220 | - : localename(init_localename), descname(init_descname), sortname(init_sortname) { |
221 | - } |
222 | - |
223 | - bool operator<(const LanguageEntry& other) const { |
224 | - return sortname < other.sortname; |
225 | - } |
226 | - |
227 | - std::string localename; // ISO code for the locale |
228 | - std::string descname; // Native language name |
229 | - std::string sortname; // ASCII Language name used for sorting |
230 | -}; |
231 | - |
232 | // Locale identifiers can look like this: ca_ES@valencia.UTF-8 |
233 | // The contents of 'selected_locale' will be changed to match the 'current_locale' |
234 | void find_selected_locale(std::string* selected_locale, const std::string& current_locale) { |
235 | @@ -120,21 +104,22 @@ |
236 | // Tabs |
237 | tabs_(this, g_gr->images().get("images/ui_basic/but1.png"), UI::TabPanel::Type::kBorder), |
238 | |
239 | - box_interface_(&tabs_, 0, 0, UI::Box::Vertical, 0, 0, padding_), |
240 | + box_interface_(&tabs_, 0, 0, UI::Box::Horizontal, 0, 0, padding_), |
241 | + box_interface_left_(&box_interface_, 0, 0, UI::Box::Vertical, 0, 0, padding_), |
242 | box_windows_(&tabs_, 0, 0, UI::Box::Vertical, 0, 0, padding_), |
243 | box_sound_(&tabs_, 0, 0, UI::Box::Vertical, 0, 0, padding_), |
244 | box_saving_(&tabs_, 0, 0, UI::Box::Vertical, 0, 0, padding_), |
245 | box_game_(&tabs_, 0, 0, UI::Box::Vertical, 0, 0, padding_), |
246 | |
247 | // Interface options |
248 | - language_dropdown_(&box_interface_, |
249 | + language_dropdown_(&box_interface_left_, |
250 | 0, |
251 | 0, |
252 | 100, // 100 is arbitrary, will be resized in layout(). |
253 | 100, // 100 is arbitrary, will be resized in layout(). |
254 | 24, |
255 | _("Language")), |
256 | - resolution_dropdown_(&box_interface_, |
257 | + resolution_dropdown_(&box_interface_left_, |
258 | 0, |
259 | 0, |
260 | 100, // 100 is arbitrary, will be resized in layout(). |
261 | @@ -142,10 +127,10 @@ |
262 | 24, |
263 | _("In-game resolution")), |
264 | |
265 | - fullscreen_(&box_interface_, Vector2i::zero(), _("Fullscreen"), "", 0), |
266 | - inputgrab_(&box_interface_, Vector2i::zero(), _("Grab Input"), "", 0), |
267 | - |
268 | - sb_maxfps_(&box_interface_, 0, 0, 0, 0, opt.maxfps, 0, 99, _("Maximum FPS:")), |
269 | + fullscreen_(&box_interface_left_, Vector2i::zero(), _("Fullscreen"), "", 0), |
270 | + inputgrab_(&box_interface_left_, Vector2i::zero(), _("Grab Input"), "", 0), |
271 | + sb_maxfps_(&box_interface_left_, 0, 0, 0, 0, opt.maxfps, 0, 99, _("Maximum FPS:")), |
272 | + translation_info_(&box_interface_, 0, 0, 100, 100), |
273 | |
274 | // Windows options |
275 | snap_win_overlap_only_( |
276 | @@ -232,6 +217,7 @@ |
277 | os_(opt) { |
278 | // Set up UI Elements |
279 | title_.set_fontsize(UI_FONT_SIZE_BIG); |
280 | + translation_info_.force_new_renderer(); |
281 | |
282 | // Buttons |
283 | button_box_.add(UI::g_fh1->fontset()->is_rtl() ? &ok_ : &cancel_); |
284 | @@ -252,18 +238,14 @@ |
285 | tabs_.activate(os_.active_tab); |
286 | } |
287 | |
288 | - box_interface_.set_size(tabs_.get_inner_w(), tabs_.get_inner_h()); |
289 | - box_windows_.set_size(tabs_.get_inner_w(), tabs_.get_inner_h()); |
290 | - box_sound_.set_size(tabs_.get_inner_w(), tabs_.get_inner_h()); |
291 | - box_saving_.set_size(tabs_.get_inner_w(), tabs_.get_inner_h()); |
292 | - box_game_.set_size(tabs_.get_inner_w(), tabs_.get_inner_h()); |
293 | - |
294 | // Interface |
295 | - box_interface_.add(&language_dropdown_); |
296 | - box_interface_.add(&resolution_dropdown_); |
297 | - box_interface_.add(&fullscreen_); |
298 | - box_interface_.add(&inputgrab_); |
299 | - box_interface_.add(&sb_maxfps_); |
300 | + box_interface_.add(&box_interface_left_); |
301 | + box_interface_.add(&translation_info_, UI::Box::Resizing::kExpandBoth); |
302 | + box_interface_left_.add(&language_dropdown_); |
303 | + box_interface_left_.add(&resolution_dropdown_); |
304 | + box_interface_left_.add(&fullscreen_); |
305 | + box_interface_left_.add(&inputgrab_); |
306 | + box_interface_left_.add(&sb_maxfps_); |
307 | |
308 | // Windows |
309 | box_windows_.add(&snap_win_overlap_only_); |
310 | @@ -290,6 +272,8 @@ |
311 | box_game_.add(&single_watchwin_); |
312 | |
313 | // Bind actions |
314 | + language_dropdown_.selected.connect( |
315 | + boost::bind(&FullscreenMenuOptions::update_language_stats, this, false)); |
316 | cancel_.sigclicked.connect(boost::bind(&FullscreenMenuOptions::clicked_back, this)); |
317 | apply_.sigclicked.connect(boost::bind(&FullscreenMenuOptions::clicked_apply, this)); |
318 | ok_.sigclicked.connect(boost::bind(&FullscreenMenuOptions::clicked_ok, this)); |
319 | @@ -360,6 +344,7 @@ |
320 | |
321 | // Language options |
322 | add_languages_to_list(opt.language); |
323 | + update_language_stats(true); |
324 | layout(); |
325 | } |
326 | |
327 | @@ -369,8 +354,7 @@ |
328 | butw_ = get_w() / 5; |
329 | buth_ = get_h() * 9 / 200; |
330 | hmargin_ = get_w() * 19 / 200; |
331 | - tab_panel_width_ = get_inner_w() - 2 * hmargin_; |
332 | - column_width_ = tab_panel_width_ - padding_; |
333 | + int tab_panel_width = get_inner_w() - 2 * hmargin_; |
334 | tab_panel_y_ = get_h() * 14 / 100; |
335 | |
336 | // Title |
337 | @@ -382,48 +366,53 @@ |
338 | apply_.set_desired_size(butw_, buth_); |
339 | ok_.set_desired_size(butw_, buth_); |
340 | button_box_.set_pos(Vector2i(hmargin_ + butw_ / 3, get_inner_h() - hmargin_)); |
341 | - button_box_.set_size(tab_panel_width_ - 2 * butw_ / 3, buth_); |
342 | + button_box_.set_size(tab_panel_width - 2 * butw_ / 3, buth_); |
343 | |
344 | // Tabs |
345 | tabs_.set_pos(Vector2i(hmargin_, tab_panel_y_)); |
346 | - tabs_.set_size(tab_panel_width_, get_inner_h() - tab_panel_y_ - buth_ - hmargin_); |
347 | + tabs_.set_size(tab_panel_width, get_inner_h() - tab_panel_y_ - buth_ - hmargin_); |
348 | + |
349 | + tab_panel_width -= padding_; |
350 | + const int column_width = tab_panel_width / 2; |
351 | |
352 | // Interface |
353 | - language_dropdown_.set_desired_size(column_width_ / 2, language_dropdown_.get_h()); |
354 | + box_interface_left_.set_desired_size(column_width + padding_, tabs_.get_inner_h()); |
355 | + box_interface_.set_size(tabs_.get_inner_w(), tabs_.get_inner_h()); |
356 | + language_dropdown_.set_desired_size(column_width, language_dropdown_.get_h()); |
357 | language_dropdown_.set_height(tabs_.get_h() - language_dropdown_.get_y() - buth_ - 3 * padding_); |
358 | - resolution_dropdown_.set_desired_size(column_width_ / 2, resolution_dropdown_.get_h()); |
359 | + resolution_dropdown_.set_desired_size(column_width, resolution_dropdown_.get_h()); |
360 | resolution_dropdown_.set_height(tabs_.get_h() - resolution_dropdown_.get_y() - buth_ - |
361 | 3 * padding_); |
362 | |
363 | - fullscreen_.set_desired_size(column_width_, fullscreen_.get_h()); |
364 | - inputgrab_.set_desired_size(column_width_, inputgrab_.get_h()); |
365 | - sb_maxfps_.set_unit_width(column_width_ / 4); |
366 | - sb_maxfps_.set_desired_size(column_width_ / 2, sb_maxfps_.get_h()); |
367 | + fullscreen_.set_desired_size(column_width, fullscreen_.get_h()); |
368 | + inputgrab_.set_desired_size(column_width, inputgrab_.get_h()); |
369 | + sb_maxfps_.set_unit_width(column_width / 2); |
370 | + sb_maxfps_.set_desired_size(column_width, sb_maxfps_.get_h()); |
371 | |
372 | // Windows options |
373 | - snap_win_overlap_only_.set_desired_size(column_width_, snap_win_overlap_only_.get_h()); |
374 | - dock_windows_to_edges_.set_desired_size(column_width_, dock_windows_to_edges_.get_h()); |
375 | - animate_map_panning_.set_desired_size(column_width_, animate_map_panning_.get_h()); |
376 | + snap_win_overlap_only_.set_desired_size(tab_panel_width, snap_win_overlap_only_.get_h()); |
377 | + dock_windows_to_edges_.set_desired_size(tab_panel_width, dock_windows_to_edges_.get_h()); |
378 | + animate_map_panning_.set_desired_size(tab_panel_width, animate_map_panning_.get_h()); |
379 | sb_dis_panel_.set_unit_width(200); |
380 | - sb_dis_panel_.set_desired_size(column_width_, sb_dis_panel_.get_h()); |
381 | + sb_dis_panel_.set_desired_size(tab_panel_width, sb_dis_panel_.get_h()); |
382 | sb_dis_border_.set_unit_width(200); |
383 | - sb_dis_border_.set_desired_size(column_width_, sb_dis_border_.get_h()); |
384 | + sb_dis_border_.set_desired_size(tab_panel_width, sb_dis_border_.get_h()); |
385 | |
386 | // Sound options |
387 | - music_.set_desired_size(column_width_, music_.get_h()); |
388 | - fx_.set_desired_size(column_width_, fx_.get_h()); |
389 | - message_sound_.set_desired_size(column_width_, message_sound_.get_h()); |
390 | + music_.set_desired_size(tab_panel_width, music_.get_h()); |
391 | + fx_.set_desired_size(tab_panel_width, fx_.get_h()); |
392 | + message_sound_.set_desired_size(tab_panel_width, message_sound_.get_h()); |
393 | |
394 | // Saving options |
395 | sb_autosave_.set_unit_width(250); |
396 | - sb_autosave_.set_desired_size(column_width_, sb_autosave_.get_h()); |
397 | + sb_autosave_.set_desired_size(tab_panel_width, sb_autosave_.get_h()); |
398 | sb_rolling_autosave_.set_unit_width(250); |
399 | - sb_rolling_autosave_.set_desired_size(column_width_, sb_rolling_autosave_.get_h()); |
400 | - zip_.set_desired_size(column_width_, zip_.get_h()); |
401 | - write_syncstreams_.set_desired_size(column_width_, write_syncstreams_.get_h()); |
402 | + sb_rolling_autosave_.set_desired_size(tab_panel_width, sb_rolling_autosave_.get_h()); |
403 | + zip_.set_desired_size(tab_panel_width, zip_.get_h()); |
404 | + write_syncstreams_.set_desired_size(tab_panel_width, write_syncstreams_.get_h()); |
405 | |
406 | // Game options |
407 | - transparent_chat_.set_desired_size(column_width_, transparent_chat_.get_h()); |
408 | + transparent_chat_.set_desired_size(tab_panel_width, transparent_chat_.get_h()); |
409 | } |
410 | |
411 | void FullscreenMenuOptions::add_languages_to_list(const std::string& current_locale) { |
412 | @@ -432,8 +421,8 @@ |
413 | language_dropdown_.add(_("Try system language"), "", nullptr, current_locale == ""); |
414 | language_dropdown_.add("English", "en", nullptr, current_locale == "en"); |
415 | |
416 | - // Add translation directories to the list |
417 | - std::vector<LanguageEntry> entries; |
418 | + // Add translation directories to the list. Using the LanguageEntries' sortnames as a key for getting a sorted result. |
419 | + std::map<std::string, LanguageEntry> entries; |
420 | std::string selected_locale; |
421 | |
422 | try { // Begin read locales table |
423 | @@ -460,7 +449,9 @@ |
424 | |
425 | std::string name = i18n::make_ligatures(table->get_string("name").c_str()); |
426 | const std::string sortname = table->get_string("sort_name"); |
427 | - entries.push_back(LanguageEntry(localename, name, sortname)); |
428 | + LanguageEntry* entry = new LanguageEntry(localename, name); |
429 | + entries.insert(std::make_pair(sortname, *entry)); |
430 | + language_entries_.insert(std::make_pair(localename, *entry)); |
431 | |
432 | if (localename == current_locale) { |
433 | selected_locale = current_locale; |
434 | @@ -468,7 +459,7 @@ |
435 | |
436 | } catch (const WException&) { |
437 | log("Could not read locale for: %s\n", localename.c_str()); |
438 | - entries.push_back(LanguageEntry(localename, localename, localename)); |
439 | + entries.insert(std::make_pair(localename, LanguageEntry(localename, localename))); |
440 | } // End read locale from table |
441 | } // End scan locales directory |
442 | } catch (const LuaError& err) { |
443 | @@ -477,11 +468,76 @@ |
444 | } // End read locales table |
445 | |
446 | find_selected_locale(&selected_locale, current_locale); |
447 | - std::sort(entries.begin(), entries.end()); |
448 | - for (const LanguageEntry& entry : entries) { |
449 | - language_dropdown_.add(entry.descname.c_str(), entry.localename, nullptr, |
450 | - entry.localename == selected_locale, ""); |
451 | - } |
452 | + for (const auto& entry : entries) { |
453 | + const LanguageEntry& language_entry = entry.second; |
454 | + language_dropdown_.add(language_entry.descname.c_str(), language_entry.localename, nullptr, |
455 | + language_entry.localename == selected_locale, ""); |
456 | + } |
457 | +} |
458 | + |
459 | +/** |
460 | + * Updates the language statistics message according to the currently selected locale. |
461 | + * @param include_system_lang We only want to include the system lang if it matches the Widelands |
462 | + * locale. |
463 | + */ |
464 | +void FullscreenMenuOptions::update_language_stats(bool include_system_lang) { |
465 | + int percent = 100; |
466 | + std::string message = ""; |
467 | + if (language_dropdown_.has_selection()) { |
468 | + std::string locale = language_dropdown_.get_selected(); |
469 | + // Empty locale means try system locale |
470 | + if (locale.empty() && include_system_lang) { |
471 | + std::vector<std::string> parts; |
472 | + boost::split(parts, i18n::get_locale(), boost::is_any_of(".")); |
473 | + if (language_entries_.count(parts[0]) == 1) { |
474 | + locale = parts[0]; |
475 | + } else { |
476 | + boost::split(parts, parts[0], boost::is_any_of("@")); |
477 | + if (language_entries_.count(parts[0]) == 1) { |
478 | + locale = parts[0]; |
479 | + } else { |
480 | + boost::split(parts, parts[0], boost::is_any_of("_")); |
481 | + if (language_entries_.count(parts[0]) == 1) { |
482 | + locale = parts[0]; |
483 | + } |
484 | + } |
485 | + } |
486 | + } |
487 | + |
488 | + // If we have the locale, grab the stats and set the message |
489 | + if (language_entries_.count(locale) == 1) { |
490 | + try { |
491 | + const LanguageEntry& entry = language_entries_[locale]; |
492 | + Profile prof("i18n/translation_stats.conf"); |
493 | + Section& s = prof.get_safe_section("global"); |
494 | + const int total = s.get_int("total"); |
495 | + s = prof.get_safe_section(locale); |
496 | + percent = floor(100.f * s.get_int("translated") / total); |
497 | + if (percent == 100) { |
498 | + message = (boost::format(_("The translation into %s is complete.")) % |
499 | + entry.descname) |
500 | + .str(); |
501 | + } else { |
502 | + message = (boost::format(_("The translation into %s is %d%% complete.")) % |
503 | + entry.descname % percent) |
504 | + .str(); |
505 | + } |
506 | + } catch (...) { |
507 | + } |
508 | + } |
509 | + } |
510 | + |
511 | + // We will want some help with incomplete translations. We set this lower than 100%, |
512 | + // because some translators let things drop a bit sometimes because they're busy and |
513 | + // will catch up with the work later. |
514 | + if (percent <= 90) { |
515 | + message = message + " " + |
516 | + (boost::format(_("If you wish to help us translate, please visit %s")) % |
517 | + "<font underline=1>widelands.org/wiki/TranslatingWidelands</font>") |
518 | + .str(); |
519 | + } |
520 | + // Make font a bit smaller so the link will fit at 800x600 resolution. |
521 | + translation_info_.set_text(as_uifont(message, 12)); |
522 | } |
523 | |
524 | void FullscreenMenuOptions::clicked_apply() { |
525 | |
526 | === modified file 'src/ui_fsmenu/options.h' |
527 | --- src/ui_fsmenu/options.h 2017-09-11 08:09:07 +0000 |
528 | +++ src/ui_fsmenu/options.h 2017-11-04 09:41:54 +0000 |
529 | @@ -104,6 +104,7 @@ |
530 | |
531 | // Fills the language selection list |
532 | void add_languages_to_list(const std::string& current_locale); |
533 | + void update_language_stats(bool include_system_lang); |
534 | |
535 | // Saves the options and reloads the active tab |
536 | void clicked_apply(); |
537 | @@ -112,8 +113,6 @@ |
538 | uint32_t butw_; |
539 | uint32_t buth_; |
540 | uint32_t hmargin_; |
541 | - uint32_t tab_panel_width_; |
542 | - uint32_t column_width_; |
543 | uint32_t tab_panel_y_; |
544 | |
545 | UI::Textarea title_; |
546 | @@ -123,6 +122,7 @@ |
547 | // UI elements |
548 | UI::TabPanel tabs_; |
549 | UI::Box box_interface_; |
550 | + UI::Box box_interface_left_; |
551 | UI::Box box_windows_; |
552 | UI::Box box_sound_; |
553 | UI::Box box_saving_; |
554 | @@ -134,6 +134,7 @@ |
555 | UI::Checkbox fullscreen_; |
556 | UI::Checkbox inputgrab_; |
557 | UI::SpinBox sb_maxfps_; |
558 | + UI::MultilineTextarea translation_info_; |
559 | |
560 | // Windows options |
561 | UI::Checkbox snap_win_overlap_only_; |
562 | @@ -170,6 +171,19 @@ |
563 | |
564 | /// All supported screen resolutions. |
565 | std::vector<ScreenResolution> resolutions_; |
566 | + |
567 | + // Data model for the entries in the language selection list. |
568 | + struct LanguageEntry { |
569 | + LanguageEntry(const std::string& init_localename, |
570 | + const std::string& init_descname) |
571 | + : localename(init_localename), descname(init_descname) { |
572 | + } |
573 | + LanguageEntry() : LanguageEntry("", "") { |
574 | + } |
575 | + std::string localename; // ISO code for the locale |
576 | + std::string descname; // Native language name |
577 | + }; |
578 | + std::map<std::string, LanguageEntry> language_entries_; |
579 | }; |
580 | |
581 | #endif // end of include guard: WL_UI_FSMENU_OPTIONS_H |
582 | |
583 | === modified file 'utils/merge_and_push_translations.sh' |
584 | --- utils/merge_and_push_translations.sh 2017-05-19 07:41:26 +0000 |
585 | +++ utils/merge_and_push_translations.sh 2017-11-04 09:41:54 +0000 |
586 | @@ -67,6 +67,11 @@ |
587 | |
588 | # Update catalogues. |
589 | utils/buildcat.py |
590 | + |
591 | +# Update statistics. |
592 | +utils/update_translation_stats.py |
593 | + |
594 | +# Commit and push. |
595 | bzr commit -m "Fetched translations and updated catalogues." |
596 | bzr push lp:widelands |
597 | |
598 | |
599 | === added file 'utils/update_translation_stats.py' |
600 | --- utils/update_translation_stats.py 1970-01-01 00:00:00 +0000 |
601 | +++ utils/update_translation_stats.py 2017-11-04 09:41:54 +0000 |
602 | @@ -0,0 +1,147 @@ |
603 | +#!/usr/bin/python2 |
604 | +# encoding: utf-8 |
605 | + |
606 | + |
607 | +"""Uses pocount from the Translate Toolkit to write translation statistics to |
608 | +data/i18n/translation_stats.conf. |
609 | + |
610 | +You will need to have the Translate Toolkit installed: |
611 | +http://toolkit.translatehouse.org/ |
612 | + |
613 | +For Debian-based Linux: sudo apt-get install translate-toolkit |
614 | + |
615 | +""" |
616 | + |
617 | +from collections import defaultdict |
618 | +from subprocess import call, check_output, CalledProcessError |
619 | +import os.path |
620 | +import re |
621 | +import subprocess |
622 | +import sys |
623 | +import traceback |
624 | + |
625 | +############################################################################# |
626 | +# Data Containers # |
627 | +############################################################################# |
628 | + |
629 | + |
630 | +class TranslationStats: |
631 | + """Total source words and translated source words.""" |
632 | + |
633 | + def __init__(self): |
634 | + # We need the total only once, but since the entries come in per |
635 | + # directory rather than per locale, we just store it here to keep the |
636 | + # algorithm simpler |
637 | + self.total = 0 |
638 | + self.translated = 0 |
639 | + |
640 | + |
641 | +############################################################################# |
642 | +# Main Loop # |
643 | +############################################################################# |
644 | + |
645 | +def generate_translation_stats(po_dir, output_file): |
646 | + locale_stats = defaultdict(TranslationStats) |
647 | + |
648 | + sys.stdout.write('Fetching translation stats ') |
649 | + |
650 | + # Regex to extract the locale from the po filenames. |
651 | + regex_po = re.compile(r"/\S+/(\w+)\.po") |
652 | + |
653 | + # We get errors for non-po files in the base po dir, so we have to walk |
654 | + # the subdirs. |
655 | + for subdir in sorted(os.listdir(po_dir), key=str.lower): |
656 | + subdir = os.path.join(po_dir, subdir) |
657 | + if not os.path.isdir(subdir): |
658 | + continue |
659 | + |
660 | + sys.stdout.write('.') |
661 | + sys.stdout.flush() |
662 | + |
663 | + try: |
664 | + # We need shell=True, otherwise we get "No such file or directory". |
665 | + stats_output = check_output( |
666 | + ['pocount ' + subdir + ' --csv'], stderr=subprocess.STDOUT, shell=True) |
667 | + if 'ERROR' in stats_output: |
668 | + print('\nError running pocount:\n' + stats_output.split('\n', 0) |
669 | + [0]) + '\nAborted creating translation statistics.' |
670 | + return False |
671 | + |
672 | + except CalledProcessError: |
673 | + print('Failed to run pocount:\n FILE: ' + po_dir + |
674 | + '\n ' + stats_output.split('\n', 1)[1]) |
675 | + return False |
676 | + |
677 | + result = stats_output.split('\n') |
678 | + |
679 | + # pocount distributes its header over multiple lines, so we have to do some |
680 | + # collecting here before we parse it |
681 | + header = "" |
682 | + for line in result: |
683 | + # Non-header rows will have a file path in them |
684 | + if "/" in line or "\\" in line: |
685 | + break |
686 | + header = header + " " + line |
687 | + header_entries = header.split(",") |
688 | + column_counter = 0 |
689 | + total_column = 0 |
690 | + translated_column = 0 |
691 | + while column_counter < len(header_entries): |
692 | + if header_entries[column_counter].strip() == "Total Source Words": |
693 | + total_column = column_counter |
694 | + elif header_entries[column_counter].strip() == "Translated Source Words": |
695 | + translated_column = column_counter |
696 | + column_counter = column_counter + 1 |
697 | + |
698 | + # Now do the actual counting for the current textdomain |
699 | + for line in result: |
700 | + cells = line.split(",") |
701 | + po_filename = cells[0] |
702 | + if po_filename.endswith(".po"): |
703 | + entry = TranslationStats() |
704 | + locale = regex_po.match(po_filename).group(1) |
705 | + if locale in locale_stats: |
706 | + entry = locale_stats[locale] |
707 | + entry.total = entry.total + int(cells[total_column]) |
708 | + entry.translated = entry.translated + int(cells[translated_column]) |
709 | + if entry.translated > entry.total: |
710 | + print("Error! Translated " + str(entry.translated) + " (+"+(cells[translated_column])+") is bigger than the total of " + str(entry.total) + "("+(cells[total_column])+")\n" + line) |
711 | + sys.exit(1) |
712 | + locale_stats[locale] = entry |
713 | + |
714 | + print('\n\nLocale\tTotal\tTranslated') |
715 | + print('------\t-----\t----------') |
716 | + |
717 | + # The total goes in a [global] section and is identical for all locales |
718 | + result = '[global]\n' |
719 | + result = result + 'total=' + str(locale_stats[locale_stats.keys()[0]].total) + '\n\n' |
720 | + |
721 | + # Write translation stats for all locales |
722 | + for locale in sorted(locale_stats.keys(), key=str.lower): |
723 | + entry = locale_stats[locale] |
724 | + print(locale + '\t' + str(entry.total) + '\t' + str(entry.translated)) |
725 | + result = result + '[' + locale + ']\n' |
726 | + result = result + 'translated=' + str(entry.translated) + '\n\n' |
727 | + |
728 | + with open(output_file, 'w+') as destination: |
729 | + destination.write(result[:-1]) # Strip the final \n |
730 | + print('\nResult written to ' + output_file) |
731 | + return True |
732 | + |
733 | + |
734 | +def main(): |
735 | + try: |
736 | + po_dir = os.path.abspath(os.path.join( |
737 | + os.path.dirname(__file__), '../po')) |
738 | + output_file = os.path.abspath(os.path.join( |
739 | + os.path.dirname(__file__), '../data/i18n/translation_stats.conf')) |
740 | + result = generate_translation_stats(po_dir, output_file) |
741 | + return result |
742 | + |
743 | + except Exception: |
744 | + print('Something went wrong:') |
745 | + traceback.print_exc() |
746 | + return 1 |
747 | + |
748 | +if __name__ == '__main__': |
749 | + sys.exit(main()) |
Continuous integration builds have changed state:
Travis build 2702. State: failed. Details: https:/ /travis- ci.org/ widelands/ widelands/ builds/ 285758712. /ci.appveyor. com/project/ widelands- dev/widelands/ build/_ widelands_ dev_widelands_ translation_ stats-2517.
Appveyor build 2517. State: success. Details: https:/