1 /**************************************************************************/
2 /* Klavaro - a flexible touch typing tutor */
3 /* Copyright (C) from 2005 until 2008 Felipe Castro */
4 /* Copyright (C) from 2009 until 2019 The Free Software Foundation */
5 /* */
6 /* This program is free software, licensed under the terms of the GNU */
7 /* General Public License as published by the Free Software Foundation, */
8 /* either version 3 of the License, or (at your option) any later */
9 /* version. You should have received a copy of the GNU General Public */
10 /* License along with this program. If not, */
11 /* see <https://www.gnu.org/licenses/>. */
12 /**************************************************************************/
13
14 /*
15 * Set of functions to deal with internationalization (translation).
16 */
17 #include <stdio.h>
18 #include <string.h>
19 #include <locale.h>
20 #include <glib.h>
21 #include <glib/gstdio.h>
22 #include <gtk/gtk.h>
23
24 #include "auxiliar.h"
25 #include "main.h"
26 #include "callbacks.h"
27 #include "keyboard.h"
28 #include "velocity.h"
29 #include "fluidness.h"
30 #include "translation.h"
31
32 /**********************************************************************
33 * Variables
34 */
35 static Lang_Name_Code *lang;
36 static gint lang_num = 0;
37
38 /**********************************************************************
39 * Get country name from its "ISO code" (eo and xx are just languages...)
40 */
41 const gchar *
trans_code_to_country(gchar * code)42 trans_code_to_country (gchar *code)
43 {
44 #define COUNTRY_N 50
45 gsize i;
46 gchar *dummy = NULL;
47 static gchar map[COUNTRY_N][3][64] = {
48 {"xx","Esperantio"},
49 {"ad","Andorra"},
50 {"ar","العالم العربي"},
51 {"be","België"},
52 {"bg","България"},
53 {"bo","ཧི་མ་ལ་ཡ།"},
54 {"br","Brasil"},
55 {"ca","Canada"},
56 {"ch","Schweiz / Suisse"},
57 /* 10 */
58 {"cn","中华人民共和国"},
59 {"cz","Česká republika"},
60 {"dk","Danmark"},
61 {"de","Deutschland"},
62 {"eo","Esperantujo"},
63 {"es","España"},
64 {"eu","Euskal Herria"},
65 {"fi","Suomi"},
66 {"fr","France"},
67 {"gr","Ελλάδα"},
68 /* 20 */
69 {"il","ישראל"},
70 {"hr","Hrvatska"},
71 {"hu","Magyarország"},
72 {"in","India"},
73 {"it","Italia"},
74 {"jp","日本 (Nippon)"},
75 {"kk","Қазақстан"},
76 {"kr","대한민국"}, /* Korea */
77 {"no","Norge"},
78 {"pl","Polska"},
79 /* 30 */
80 {"pk","پاکستان"},
81 {"pt","Portugal"},
82 {"rs","Србија"}, /* Serbia */
83 {"ru","Россия"},
84 {"se","Sverige"},
85 {"si","Slovenija"},
86 {"sk","Slovensko"},
87 {"tr","Türkiye"},
88 {"ua","Україна"},
89 {"uk","United Kingdom"},
90 /* 40 */
91 {"us","USA"},
92 {"",""},
93 {"",""},
94 {"",""},
95 {"",""},
96 {"",""},
97 {"",""},
98 {"",""},
99 {"",""},
100 {"",""},
101 /* 50 */
102 {"",""},
103 };
104
105 for (i = 0; i < COUNTRY_N; i++)
106 if (g_str_equal (code, map[i][0]))
107 return (map[i][1]);
108 dummy = g_strdup_printf ("(%s)", code);
109 return (dummy);
110 }
111
112 /**********************************************************************
113 * Get a 'reazonable' value for keyboard, that is, QWERTY... :-(
114 */
115 gchar *
trans_get_default_keyboard()116 trans_get_default_keyboard ()
117 {
118 gint i;
119 gchar *tmp;
120
121 if (lang_num == 0)
122 {
123 g_warning ("Internal error: trying to use language data not initialized!");
124 return (NULL);
125 }
126
127 tmp = main_preferences_get_string ("interface", "language");
128 for (i = 0; i < lang_num; i++)
129 if (g_str_equal (lang[i].code, tmp))
130 {
131 g_free (tmp);
132 return (lang[i].kbd);
133 }
134
135 g_free (tmp);
136 return (NULL);
137 }
138
139 /**********************************************************************
140 * Initialize 'lang', 'code' and 'kbd' accordingly to 'language_set',
141 * which was defined in "languages.h".
142 */
143 void
trans_init_lang_name_code()144 trans_init_lang_name_code ()
145 {
146 static gboolean init = FALSE;
147 gint i;
148 gchar *tmp;
149 gchar **temp_lang = NULL;
150 const gchar languages_set[] = LANG_SET;
151
152 if (init)
153 {
154 g_warning ("Not initializing again the language data");
155 return;
156 }
157 init = TRUE;
158
159 /* Number of configured languages
160 */
161 temp_lang = g_strsplit (languages_set, "\n", -1);
162 i = 0;
163 while (temp_lang[i] != NULL)
164 i++;
165 g_assert (i > 0);
166 lang = g_new (Lang_Name_Code, i);
167 lang_num = i;
168
169 for (i = 0; i < lang_num; i++)
170 {
171 gchar *end;
172 gchar *begin;
173
174 tmp = g_strdup (temp_lang[i]);
175 /* Initialize 'lang'
176 */
177 end = strchr (tmp, '(');
178 if (end)
179 end -= ( ((end - tmp) > 1) ? 1 : 0 );
180 else
181 g_error ("Internal lang error: found nothing like '(LL...'");
182 lang[i].name = g_strndup (tmp, end - tmp);
183
184 /* Initialize 'code'
185 */
186 begin = strchr (end, '(') + 1;
187 end = strchr (begin, ')');
188 if (end == NULL)
189 g_error ("Internal lang error: found nothing like '(LL)'");
190 lang[i].code = g_strndup (begin, end - begin);
191
192 /* Initialize 'cd'
193 */
194 if (lang[i].code[0] == 'C')
195 strcpy (lang[i].cd, "en");
196 else
197 strncpy (lang[i].cd, lang[i].code, 2);
198 lang[i].cd[2] = '\0';
199
200 /* Initialize 'kbd'
201 */
202 begin = strchr (end, '[') + 1;
203 end = strchr (begin, ']');
204 if (end == NULL)
205 g_error ("Internal lang error: found nothing like '[yy_zz]'");
206 lang[i].kbd = g_strndup (begin, end - begin);
207
208 //g_printf ("%s : %s : %s : %s\n", lang[i].name, lang[i].code, lang[i].cd, lang[i].kbd);
209 }
210 g_strfreev (temp_lang);
211 }
212
213 gchar *
trans_get_code(gint i)214 trans_get_code (gint i)
215 {
216 if (i >= lang_num || i < 0)
217 return (NULL);
218 return (lang[i].cd);
219 }
220
221 gboolean
trans_lang_is_available(gchar * langcode)222 trans_lang_is_available (gchar * langcode)
223 {
224 gint i;
225
226 for (i = 0; i < lang_num; i++)
227 if (g_str_equal (lang[i].code, langcode))
228 break;
229 return (i == lang_num ? FALSE : TRUE);
230 }
231
232 /**********************************************************************
233 * Define if we may put a stop mark at the end of "phrases".
234 */
235 gboolean
trans_lang_has_stopmark()236 trans_lang_has_stopmark ()
237 {
238 gboolean stopmark;
239 gchar *hlp;
240
241 hlp = main_preferences_get_string ("interface", "language");
242 stopmark = g_str_has_prefix (hlp, "ur") ||
243 g_str_has_prefix (hlp, "ar") ||
244 g_str_has_prefix (hlp, "bn") ||
245 g_str_has_prefix (hlp, "bo") ||
246 g_str_has_prefix (hlp, "pa");
247 g_free (hlp);
248
249 return (!stopmark);
250 }
251
252 /**********************************************************************
253 * Private auxiliar function
254 */
255 static gboolean
trans_lang_get_similar(gchar * test)256 trans_lang_get_similar (gchar * test)
257 {
258 gint i;
259 gchar aux_code_2[3];
260
261 /* Prefer C over en_GB for English variants other than en_GB. (Debian patch 02) */
262 if (g_str_has_prefix (test, "en"))
263 {
264 g_free (test);
265 test = g_strdup ("C");
266 return (TRUE);
267 }
268
269 if (g_str_equal (test, "C"))
270 return TRUE;
271
272 strncpy (aux_code_2, test, 2);
273 aux_code_2[2] = '\0';
274
275 for (i = 0; i < lang_num; i++)
276 {
277 if (strstr (lang[i].code, aux_code_2))
278 {
279 g_free (test);
280 test = g_strdup (lang[i].code);
281 break;
282 }
283 }
284 if (i == lang_num && g_str_has_prefix (test, "en"))
285 {
286 g_free (test);
287 test = g_strdup ("C");
288 return (TRUE);
289 }
290 return (i == lang_num ? FALSE : TRUE);
291 }
292
293 /**********************************************************************
294 * Get the current locale and change it if necessary
295 */
296 void
trans_init_language_env()297 trans_init_language_env ()
298 {
299 gchar *tmp_code;
300 gboolean lang_ok;
301 gint i;
302
303 /*
304 * If the language is already set in preferences, just use it
305 */
306 lang_ok = FALSE;
307 if (main_preferences_exist ("interface", "language"))
308 {
309 lang_ok = TRUE;
310 tmp_code = main_preferences_get_string ("interface", "language");
311 if (trans_lang_is_available (tmp_code) == FALSE)
312 {
313 tmp_code[2] = '\0';
314 if (trans_lang_is_available (tmp_code) == FALSE)
315 {
316 lang_ok = FALSE;
317 main_preferences_remove ("interface", "language");
318 }
319 else
320 main_preferences_set_string ("interface", "language", tmp_code);
321 }
322 }
323
324 if (lang_ok == FALSE)
325 {
326 /*
327 * Read the current locale
328 */
329 #ifdef G_OS_UNIX
330 i = 0;
331 while ((tmp_code = g_strdup (g_get_language_names ()[i])))
332 {
333 if (tmp_code[0] == 'C')
334 {
335 lang_ok = (i == 0 ? TRUE : FALSE);
336 break;
337 }
338 lang_ok = trans_lang_is_available (tmp_code);
339 if (lang_ok == TRUE)
340 break;
341 g_free (tmp_code);
342 lang_ok = FALSE;
343 i++;
344 }
345 if (lang_ok == FALSE)
346 {
347 i = 0;
348 while ((tmp_code = g_strdup (g_get_language_names ()[i])))
349 {
350 if (tmp_code[0] == 'C')
351 {
352 lang_ok = (i == 0 ? TRUE : FALSE);
353 break;
354 }
355 lang_ok = trans_lang_get_similar (tmp_code);
356 if (lang_ok == TRUE)
357 break;
358 g_free (tmp_code);
359 lang_ok = FALSE;
360 i++;
361 }
362 }
363 #else
364 tmp_code = g_win32_getlocale ();
365 lang_ok = trans_lang_is_available (tmp_code);
366 if (lang_ok == FALSE)
367 lang_ok = trans_lang_get_similar (tmp_code);
368 #endif
369 }
370 if (tmp_code == NULL)
371 tmp_code = g_strdup ("xx");
372
373 /* If even a similar is not available...
374 */
375 if (lang_ok == FALSE)
376 {
377 g_message ("as your locale (%s) isn't available, "
378 "we are using \"C\"", tmp_code);
379 g_free (tmp_code);
380 tmp_code = g_strdup ("C");
381 }
382
383 #ifdef G_OS_WIN32
384 g_setenv ("LANGUAGE", tmp_code, TRUE);
385 #endif
386 main_preferences_set_string ("interface", "language", tmp_code);
387 g_free (tmp_code);
388 }
389
390 /**********************************************************************
391 * Inserts the list of available languages in the 'combo_language'.
392 */
393 void
trans_set_combo_language()394 trans_set_combo_language ()
395 {
396 static gboolean recur = FALSE;
397 gint i;
398 gint i_env;
399 gchar *tmp_code;
400 gchar *langcode;
401 GtkComboBox *cmb;
402
403 callbacks_shield_set (TRUE);
404
405 if (recur)
406 cmb = GTK_COMBO_BOX (get_wg ("combobox_top10_language"));
407 else
408 cmb = GTK_COMBO_BOX (get_wg ("combobox_language"));
409
410 tmp_code = main_preferences_get_string ("interface", "language");
411 if (tmp_code == NULL || g_str_equal ("en_US", tmp_code))
412 {
413 g_message ("Using \"C\" as language code.");
414 main_preferences_set_string ("interface", "language", "C");
415 if (tmp_code)
416 g_free (tmp_code);
417 tmp_code = g_strdup ("C");
418 }
419 gtk_combo_box_text_remove (GTK_COMBO_BOX_TEXT (cmb), 0);
420 for (i = 0, i_env = -1; i < lang_num; i++)
421 {
422 langcode = g_strdup_printf ("%s (%s)", lang[i].name, lang[i].code);
423 gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (cmb), langcode);
424 if (g_str_equal (lang[i].code, tmp_code))
425 i_env = i;
426 else if (g_str_has_prefix (tmp_code, lang[i].code))
427 {
428 i_env = i;
429 main_preferences_set_string ("interface", "language", lang[i].code);
430 }
431 g_free (langcode);
432 }
433 if (i_env == -1)
434 {
435 g_warning ("set_combo_language() ==> the locale \"%s\" is not available!", tmp_code);
436 g_message ("Using \"C\" as language code.");
437 g_free (tmp_code);
438 tmp_code = g_strdup ("C");
439 main_preferences_set_string ("interface", "language", "C");
440 gtk_combo_box_set_active (cmb, 8);
441 }
442 else
443 gtk_combo_box_set_active (cmb, i_env);
444
445 callbacks_shield_set (FALSE);
446
447 if (! recur)
448 {
449 recur = TRUE;
450 trans_set_combo_language ();
451 return;
452 }
453 recur = FALSE;
454
455 if (g_str_has_prefix (tmp_code, "en") || g_str_equal (tmp_code, "C"))
456 gtk_widget_show (get_wg ("checkbutton_speech"));
457 else
458 gtk_widget_hide (get_wg ("checkbutton_speech"));
459
460 g_free (tmp_code);
461 }
462
463 /**********************************************************************
464 * Get the current language name, mapped from the preference's key code
465 */
466 gchar *
trans_get_current_language()467 trans_get_current_language ()
468 {
469 gchar *tmp_code;
470 gint i;
471
472 if (lang_num == 0)
473 {
474 g_warning ("Internal error: trying to use language data not initialized!");
475 return (NULL);
476 }
477
478 tmp_code = main_preferences_get_string ("interface", "language");
479 for (i = 0; i < lang_num; i++)
480 if (g_str_equal (lang[i].code, tmp_code))
481 {
482 g_free (tmp_code);
483 return (lang[i].name);
484 }
485 g_free (tmp_code);
486 return ("??");
487 }
488
489 /**********************************************************************
490 * Update the current language used accordingly to that selected in the
491 * 'combo_language'
492 */
493 void
trans_change_language(gchar * language)494 trans_change_language (gchar *language)
495 {
496 gint i;
497 gchar *tmp_code;
498
499 /* Keep decreasing order of scanning, for not missing "English UK", for instance. */
500 for (i = lang_num-1; i >= 0; i--)
501 if (g_str_has_prefix (language, lang[i].name))
502 break;
503
504 if (i == lang_num)
505 {
506 g_warning ("change_language() --> couldn't find the language: %s", language);
507 return;
508 }
509
510 main_preferences_set_string ("interface", "language", lang[i].code);
511
512 velo_reset_dict ();
513 fluid_reset_paragraph ();
514
515 /* Check if the interface language is the same of the selected in the combo,
516 * so that it may be spoken
517 */
518 if (lang[i].code[0] == 'C')
519 tmp_code = g_strdup ("en");
520 else
521 tmp_code = g_strdup (lang[i].code);
522 if (tmp_code[0] == _("en")[0] && tmp_code[1] == _("en")[1])
523 gtk_widget_show (get_wg ("checkbutton_speech"));
524 else
525 gtk_widget_hide (get_wg ("checkbutton_speech"));
526 g_free (tmp_code);
527 }
528
529 /**********************************************************************
530 * Find a file whose language prefix is similar to the current one
531 */
532 FILE *
trans_lang_get_similar_file(const gchar * file_end)533 trans_lang_get_similar_file (const gchar * file_end)
534 {
535 gint i;
536 gchar *tmp_code;
537 gchar *tmp_path = NULL;
538 FILE *fh = NULL;
539
540 tmp_code = main_preferences_get_string ("interface", "language");
541 for (i = 0; i < lang_num && fh == NULL; i++)
542 {
543 if (g_str_equal (lang[i].code, tmp_code))
544 continue;
545 if (lang[i].code[0] == tmp_code[0] && lang[i].code[1] == tmp_code[1])
546 {
547 g_free (tmp_path);
548 tmp_path =
549 g_strconcat (main_path_data (), G_DIR_SEPARATOR_S, lang[i].code, file_end, NULL);
550 fh = (FILE *) g_fopen (tmp_path, "r");
551 }
552 }
553 g_free (tmp_code);
554 g_free (tmp_path);
555 i--;
556 if (fh)
557 g_message ("applying similar file: %s%s", lang[i].code, file_end);
558 return (fh);
559 }
560
561 /**********************************************************************
562 * Find a file whose language prefix is similar to the current one, returnig
563 * its name
564 */
565 gchar *
trans_lang_get_similar_file_name(const gchar * file_end)566 trans_lang_get_similar_file_name (const gchar * file_end)
567 {
568 gint i;
569 gchar *tmp_code;
570 gchar *tmp_path = NULL;
571
572 tmp_code = main_preferences_get_string ("interface", "language");
573 for (i = 0; i < lang_num; i++)
574 {
575 if (g_str_equal (lang[i].code, tmp_code))
576 continue;
577 if (lang[i].code[0] == tmp_code[0] && lang[i].code[1] == tmp_code[1])
578 {
579 g_free (tmp_path);
580 tmp_path =
581 g_strconcat (main_path_data (), G_DIR_SEPARATOR_S, lang[i].code, file_end, NULL);
582 if (g_file_test (tmp_path, G_FILE_TEST_IS_REGULAR))
583 break;
584 }
585 }
586 if (tmp_path == NULL)
587 tmp_path = g_strconcat (main_path_data (), G_DIR_SEPARATOR_S, "C", file_end, NULL);
588 g_free (tmp_code);
589 return (tmp_path);
590 }
591
592 /**********************************************************************
593 * Reads the text of a file to be presented at some dialog,
594 * accordingly to the environment language.
595 * 'text': new buffer where the text is copied into.
596 * 'file_end': how the file name ends
597 */
598
599 gchar *
trans_read_text(const gchar * file_end)600 trans_read_text (const gchar * file_end)
601 {
602 gchar *buf;
603 gchar *tmp_name = NULL;
604 gchar *tmp_path = NULL;
605 gchar *tmp_code;
606 FILE *fh;
607
608 gchar *basic1 = _(
609 "The basic course focuses on having you read the characters presented to you on screen "
610 "and typing the corresponding keys. Remember to keep your hands correctly oriented "
611 "on the home row of the keyboard at all times (see introduction on main menu).");
612 gchar *basic2 = _(
613 "The key set used in each series will be shown in the above message line. "
614 "The [Space], [Shift] and [Enter] keys may not show up there but are used very often.");
615 gchar *basic3 = _(
616 "The message line below follows and echoes your key presses. "
617 "If required, it changes and displays instructions for actions required from you.");
618
619 gchar *adapt1 = _(
620 "Here you may practice and improve your memorization of all keys. "
621 "There will be sentences presented with nonsense words which mix some numbers and symbols.");
622 gchar *adapt2 = _(
623 "In order to keep the lesson contents language and keyboard independent, "
624 "accented letter combinations will probably not appear. For real word sentences, "
625 "please use the fourth option of the main menu (about fluidness).");
626 gchar *adapt3 = _(
627 "After each exercise there will be a brief statistics panel "
628 "reviewing your performance along with some relevant comments.");
629
630 gchar *velo1 = _(
631 "This exercise is very similar to the second one, for adaptability. "
632 "The difference is that here you'll type real words.");
633 gchar *velo2 = _(
634 "The default language is the actual one of the interface. "
635 "But you may select any other texts with words you would like to use. "
636 "Press the 'Other' option above and add files containing those texts.");
637 gchar *velo3 = _(
638 "With this exercise the focus is on speed. "
639 "So, you are supposed to type really fast and I will only flatter you when you deserve it!");
640
641 gchar *fluid1 = _(
642 "We will now use complete sentences and paragraphs which make logical sense. "
643 "This may distract you while you type if you try to understand what you are entering. "
644 "The previous exercises were aimed at getting you to type without interpreting and analyzing the content.");
645 gchar *fluid2 = _(
646 "We do not mean to imply that the typists must behave like a robot, without understanding what they type. "
647 "We do aim to develop the skill of typing, making it an automatic reflex akin to the acts of walking, talking, etc. "
648 "After reaching this goal, the act of typing will become automatic and require little concentration. "
649 "Then you will be able to pay attention to the real meaning of the text.");
650 gchar *fluid3 = _("These exercises are longer. Each exercise consists of three paragraphs and "
651 "the emphasis is placed on correctness and rhythm, with a minimum speed requirement. "
652 "Here you will be required to use the backspace key to correct any mistakes. "
653 "In other words, only input without error will be accepted.");
654
655 if (g_str_equal (file_end, "_basic_intro.txt"))
656 return (g_strdup_printf ("%s\n%s\n%s", basic1, basic2, basic3));
657
658 if (g_str_equal (file_end, "_adapt_intro.txt"))
659 return (g_strdup_printf ("%s\n%s\n%s", adapt1, adapt2, adapt3));
660
661 if (g_str_equal (file_end, "_velo_intro.txt"))
662 return (g_strdup_printf ("%s\n%s\n%s", velo1, velo2, velo3));
663
664 if (g_str_equal (file_end, "_fluid_intro.txt"))
665 return (g_strdup_printf ("%s\n%s\n%s", fluid1, fluid2, fluid3));
666
667 /* Use text files
668 */
669 tmp_code = main_preferences_get_string ("interface", "language");
670 tmp_name = g_strconcat (tmp_code, file_end, NULL);
671
672 /* Try at HOME
673 */
674 tmp_path = g_build_filename (main_path_user (), tmp_name, NULL);
675 fh = (FILE *) g_fopen (tmp_path, "r");
676
677 /* Try at PACKAGE_DATA
678 */
679 if (fh == NULL)
680 {
681 g_free (tmp_path);
682 tmp_path = g_build_filename (main_path_data (), tmp_name, NULL);
683 fh = (FILE *) g_fopen (tmp_path, "r");
684 }
685
686 /*
687 * Try other "flavors" of the same language
688 */
689 if (fh == NULL && strlen (tmp_code) > 1)
690 fh = trans_lang_get_similar_file (file_end);
691
692 /*
693 * Default to C
694 */
695 if (fh == NULL && ! g_str_equal (tmp_code, "C"))
696 {
697 g_message ("trans_read_text() --> couldn't open the data file: %s\n"
698 " So, we have to apply the default one: C%s", tmp_name, file_end);
699 main_preferences_set_string ("interface", "language", "C");
700 buf = trans_read_text (file_end);
701 main_preferences_set_string ("interface", "language", tmp_code);
702 g_free (tmp_code);
703 g_free (tmp_path);
704 g_free (tmp_name);
705 return buf;
706 }
707
708 if (fh == NULL)
709 g_error ("trans_read_text() --> couldn't open the data file:\n %s", tmp_name);
710
711 g_free (tmp_code);
712 g_free (tmp_path);
713 g_free (tmp_name);
714
715 /*
716 * Process the file
717 */
718 gsize bufsize = 16;
719 gsize pos = 0;
720 buf = g_malloc (bufsize);
721 while (1)
722 {
723 gsize max = bufsize - pos - 1; // -1 for terminating zero
724 gsize ct = fread (buf + pos, 1, max, fh);
725 if (ct == 0)
726 break;
727 pos += ct;
728 if (ct == max)
729 {
730 bufsize *= 2;
731 buf = g_realloc (buf, bufsize);
732 }
733 }
734 buf[pos] = 0;
735 fclose (fh);
736 return buf;
737 }
738