153 lines4.2 KBC
1/*
2i18n.c: which language the UI is in, and its words from data/lang/<code>.txt.
3
4The PS3's own language unless one was picked in the app (USRDIR/language.txt:
5a code, or "auto"). Arabic and Indonesian aren't PS3 system languages: they're
6only there by picking them. (Russian, Chinese, Japanese and Korean are: they follow the PS3.)
7
8A line of a language file is "English<TAB>translation"; "\n" in either is a
9new line and a line starting with # is a comment. A translation whose printf
10conversions (%s, %d...) aren't the English's, in the same order, is left out:
11it would read the wrong arguments.
12*/
13#include <stdio.h>
14#include <stdlib.h>
15#include <string.h>
16#include "plat.h"
17#include "lang.h"
18#include "i18n.h"
19
20static int choice = -1; // -1: the PS3's language
21static const lang_table_t *tables[LANG_COUNT]; // loaded once each, kept (tr() may be reading one)
22
23// the conversions of a format, in order ("%s %d" -> "sd")
24static void Specs(const char *s, char *out, int size)
25{
26 int n = 0;
27 for (; *s && n < size - 1; s++) {
28 if (*s != '%') continue;
29 s++;
30 if (*s == '%') continue;
31 while (*s && strchr("-+ #0123456789.lhz", *s)) s++;
32 if (!*s) break;
33 out[n++] = *s;
34 }
35 out[n] = 0;
36}
37
38// "\n" -> a new line, "\\" -> "\", in place
39static void Unescape(char *s)
40{
41 char *d = s;
42 for (; *s; s++) {
43 if (*s == '\\' && s[1] == 'n') { *d++ = '\n'; s++; }
44 else if (*s == '\\' && s[1] == '\\') { *d++ = '\\'; s++; }
45 else *d++ = *s;
46 }
47 *d = 0;
48}
49
50static const lang_table_t *Load(int lang)
51{
52 char path[512];
53 snprintf(path, sizeof(path), "%s/lang/%s.txt", plat_data_dir(), lang_code(lang));
54 char *text = (char *)plat_read_file(path, NULL); // (kept: the table's strings are in it)
55 if (!text) {
56 plat_log("lang: no %s: English", path);
57 return NULL;
58 }
59 int lines = 1;
60 for (const char *p = text; *p; p++) lines += *p == '\n';
61 int size = 16;
62 while (size < lines * 2) size *= 2;
63 lang_table_t *t = calloc(1, sizeof(*t));
64 const char **en = calloc(size, sizeof(char *)), **tx = calloc(size, sizeof(char *));
65 if (!t || !en || !tx) {
66 free(t); free(en); free(tx); free(text);
67 return NULL;
68 }
69 t->size = size;
70 t->en = en;
71 t->tx = tx;
72 int n = 0, bad = 0;
73 for (char *line = text, *next; line && *line; line = next) {
74 next = strchr(line, '\n');
75 if (next) *next++ = 0;
76 char *cr = strchr(line, '\r');
77 if (cr) *cr = 0;
78 char *tab = strchr(line, '\t');
79 if (line[0] == '#' || !tab || !tab[1]) continue;
80 *tab = 0;
81 char *e = line, *x = tab + 1;
82 Unescape(e);
83 Unescape(x);
84 char se[32], sx[32];
85 Specs(e, se, sizeof(se));
86 Specs(x, sx, sizeof(sx));
87 if (strcmp(se, sx)) {
88 plat_log("lang: %s: \"%s\" left out (its %% conversions differ)", lang_code(lang), e);
89 bad++;
90 continue;
91 }
92 unsigned i = lang_hash(e) & (size - 1);
93 while (en[i] && strcmp(en[i], e)) i = (i + 1) & (size - 1);
94 en[i] = e;
95 tx[i] = x;
96 n++;
97 }
98 plat_log("lang: %s, %d phrases%s", lang_code(lang), n, bad ? " (some left out)" : "");
99 return t;
100}
101
102// the PS3's language (sysutil's numbering), as one of ours
103int i18n_system(void)
104{
105 switch (plat_system_lang()) {
106 case 0: return LANG_JA;
107 case 2: return LANG_FR;
108 case 3: return LANG_ES;
109 case 4: return LANG_DE;
110 case 5: return LANG_IT;
111 case 6: return LANG_NL;
112 case 7: case 17: return LANG_PT;
113 case 8: return LANG_RU;
114 case 9: return LANG_KO;
115 case 10: case 11: return LANG_ZH; // (traditional too: simplified is closer than English)
116 case 14: return LANG_DA;
117 case 16: return LANG_PL;
118 case 19: return LANG_TR;
119 }
120 return LANG_EN;
121}
122
123static void Apply(void)
124{
125 int l = choice >= 0 ? choice : i18n_system();
126 if (l != LANG_EN && !tables[l]) tables[l] = Load(l);
127 lang_install(l, l == LANG_EN ? NULL : tables[l]);
128}
129
130void i18n_init(void)
131{
132 char path[512];
133 snprintf(path, sizeof(path), "%s/language.txt", plat_save_dir());
134 char *s = (char *)plat_read_file(path, NULL);
135 if (s) {
136 for (int i = 0; i < LANG_COUNT; i++)
137 if (!strncmp(s, lang_code(i), 2) && (s[2] == 0 || s[2] == '\n' || s[2] == '\r')) choice = i;
138 free(s);
139 }
140 Apply();
141}
142
143int i18n_choice(void) { return choice; }
144
145void i18n_set(int lang)
146{
147 char path[512];
148 choice = lang >= 0 && lang < LANG_COUNT ? lang : -1;
149 snprintf(path, sizeof(path), "%s/language.txt", plat_save_dir());
150 const char *v = choice >= 0 ? lang_code(choice) : "auto";
151 plat_write_file(path, v, strlen(v));
152 Apply();
153}