Commit | Line | Data |
---|---|---|
a3b510ab NR |
1 | package be.nikiroo.jvcard.i18n; |
2 | ||
e7418457 NR |
3 | import java.io.BufferedWriter; |
4 | import java.io.File; | |
5 | import java.io.FileOutputStream; | |
6 | import java.io.IOException; | |
7 | import java.io.OutputStreamWriter; | |
8 | import java.lang.reflect.Field; | |
668268fc NR |
9 | import java.util.Locale; |
10 | import java.util.ResourceBundle; | |
296a0b75 | 11 | |
2a96e7b2 | 12 | import be.nikiroo.jvcard.resources.Bundles; |
296a0b75 NR |
13 | import be.nikiroo.jvcard.tui.UiColors; |
14 | ||
668268fc NR |
15 | import com.googlecode.lanterna.input.KeyStroke; |
16 | ||
a3b510ab | 17 | /** |
e7418457 | 18 | * This class manages the translation of {@link Trans.StringId}s into |
a3b510ab NR |
19 | * user-understandable text. |
20 | * | |
21 | * @author niki | |
22 | * | |
23 | */ | |
24 | public class Trans { | |
668268fc | 25 | ResourceBundle map; |
a3b510ab | 26 | |
a3b510ab | 27 | /** |
668268fc | 28 | * Create a translation service with the default language. |
a3b510ab | 29 | */ |
668268fc | 30 | public Trans() { |
e7418457 | 31 | setLanguage(null); |
668268fc | 32 | } |
a3b510ab | 33 | |
668268fc NR |
34 | /** |
35 | * Create a translation service for the given language. (Will fall back to | |
36 | * the default one i not found.) | |
37 | * | |
38 | * @param language | |
39 | * the language to use | |
40 | */ | |
41 | public Trans(String language) { | |
e7418457 | 42 | setLanguage(language); |
a3b510ab NR |
43 | } |
44 | ||
296a0b75 NR |
45 | /** |
46 | * Translate the given {@link StringId} into user text. | |
47 | * | |
48 | * @param stringId | |
49 | * the ID to translate | |
50 | * | |
51 | * @return the translated text | |
52 | */ | |
a3b510ab | 53 | public String trans(StringId stringId) { |
296a0b75 NR |
54 | StringId id = stringId; |
55 | if (!UiColors.getInstance().isUnicode()) { | |
56 | try { | |
2a96e7b2 | 57 | id = StringId.valueOf(stringId.name() + "_NOUTF"); |
296a0b75 NR |
58 | } catch (IllegalArgumentException iae) { |
59 | // no special _NOUTF version found | |
60 | } | |
61 | } | |
62 | ||
668268fc NR |
63 | if (id == StringId.NULL) { |
64 | return ""; | |
65 | } | |
66 | ||
67 | if (id == StringId.DUMMY) { | |
68 | return "[dummy]"; | |
69 | } | |
70 | ||
2a96e7b2 NR |
71 | if (map.containsKey(id.name())) { |
72 | return map.getString(id.name()); | |
296a0b75 NR |
73 | } |
74 | ||
75 | return id.toString(); | |
76 | } | |
77 | ||
78 | /** | |
79 | * Translate the given {@link KeyStroke} into a user text {@link String} of | |
80 | * size 3. | |
81 | * | |
82 | * @param key | |
83 | * the key to translate | |
84 | * | |
85 | * @return the translated text | |
86 | */ | |
87 | public String trans(KeyStroke key) { | |
88 | String keyTrans = ""; | |
89 | ||
90 | switch (key.getKeyType()) { | |
91 | case Enter: | |
92 | if (UiColors.getInstance().isUnicode()) | |
93 | keyTrans = " ⤶ "; | |
94 | else | |
668268fc | 95 | keyTrans = trans(StringId.KEY_ENTER); |
296a0b75 NR |
96 | break; |
97 | case Tab: | |
98 | if (UiColors.getInstance().isUnicode()) | |
99 | keyTrans = " ↹ "; | |
100 | else | |
668268fc | 101 | keyTrans = trans(StringId.KEY_TAB); |
296a0b75 NR |
102 | |
103 | break; | |
104 | case Character: | |
105 | keyTrans = " " + key.getCharacter() + " "; | |
106 | break; | |
107 | default: | |
108 | keyTrans = "" + key.getKeyType(); | |
109 | int width = 3; | |
110 | if (keyTrans.length() > width) { | |
111 | keyTrans = keyTrans.substring(0, width); | |
112 | } else if (keyTrans.length() < width) { | |
113 | keyTrans = keyTrans | |
114 | + new String(new char[width - keyTrans.length()]) | |
115 | .replace('\0', ' '); | |
116 | } | |
117 | break; | |
a3b510ab NR |
118 | } |
119 | ||
296a0b75 | 120 | return keyTrans; |
a3b510ab NR |
121 | } |
122 | ||
668268fc NR |
123 | /** |
124 | * Initialise the translation mappings for the given language. | |
125 | * | |
126 | * @param lang | |
127 | * the language to initialise | |
128 | */ | |
e7418457 | 129 | private void setLanguage(String lang) { |
668268fc NR |
130 | Locale locale = null; |
131 | ||
132 | if (lang == null) { | |
133 | locale = Locale.getDefault(); | |
134 | } else { | |
6b6a62ca | 135 | locale = new Locale(lang); |
668268fc NR |
136 | } |
137 | ||
2a96e7b2 | 138 | map = Bundles.getBundle("resources", locale); |
a3b510ab | 139 | } |
e7418457 NR |
140 | |
141 | /** | |
142 | * Create/update the translation .properties files. Will use the most likely | |
143 | * candidate as base if the file does not already exists (for instance, | |
144 | * "en_US" will use "en" as a base). | |
145 | * | |
146 | * @param args | |
147 | * the path where the .properties files are, then the languages | |
148 | * to create/update | |
149 | * | |
150 | * @throws IOException | |
151 | * in case of IO errors | |
152 | */ | |
153 | public static void main(String[] args) throws IOException { | |
154 | String path = args[0]; | |
155 | for (int i = 1; i < args.length; i++) { | |
6b6a62ca | 156 | Locale locale = new Locale(args[i].replaceAll("_", "-")); |
e7418457 NR |
157 | String code = locale.toString(); |
158 | Trans trans = new Trans(code); | |
159 | ||
160 | File file = null; | |
161 | if (code.length() > 0) { | |
162 | file = new File(path + "resources_" + code + ".properties"); | |
163 | } else { | |
164 | // Default properties file: | |
165 | file = new File(path + "resources.properties"); | |
166 | } | |
167 | ||
168 | BufferedWriter writer = new BufferedWriter(new OutputStreamWriter( | |
169 | new FileOutputStream(file), "UTF-8")); | |
170 | ||
171 | String name = locale.getDisplayCountry(locale); | |
172 | if (name.length() == 0) | |
173 | name = locale.getDisplayLanguage(locale); | |
174 | if (name.length() == 0) | |
175 | name = "default"; | |
176 | ||
177 | if (code.length() > 0) { | |
178 | name = name + " (" + code + ")"; | |
179 | } | |
180 | ||
181 | writer.append("# " + name + " translation file (UTF-8)\n"); | |
182 | writer.append("# \n"); | |
183 | writer.append("# Note that any key can be doubled with a _NOUTF suffix\n"); | |
184 | writer.append("# to use when the flag --noutf is passed\n"); | |
185 | writer.append("# \n"); | |
186 | writer.append("# Also, the comments always refer to the key below them.\n"); | |
187 | writer.append("# \n"); | |
188 | writer.append("\n"); | |
189 | ||
190 | for (Field field : StringId.class.getDeclaredFields()) { | |
191 | Meta meta = field.getAnnotation(Meta.class); | |
192 | if (meta != null) { | |
193 | StringId id = StringId.valueOf(field.getName()); | |
194 | String info = getMetaInfo(meta); | |
195 | if (info != null) { | |
196 | writer.append(info); | |
197 | writer.append("\n"); | |
198 | } | |
199 | ||
200 | writer.append(id.name()); | |
201 | writer.append(" = "); | |
202 | if (!trans.trans(id).equals(id.name())) | |
203 | writer.append(trans.trans(id)); | |
204 | writer.append("\n"); | |
205 | } | |
206 | } | |
207 | ||
208 | writer.close(); | |
209 | } | |
210 | } | |
211 | ||
212 | /** | |
213 | * Return formated, display-able information from the {@link Meta} field | |
214 | * given. Each line will always starts with a "#" character. | |
215 | * | |
216 | * @param meta | |
217 | * the {@link Meta} field | |
218 | * | |
219 | * @return the information to display or NULL if none | |
220 | */ | |
221 | private static String getMetaInfo(Meta meta) { | |
222 | String what = meta.what(); | |
223 | String where = meta.where(); | |
224 | String format = meta.format(); | |
225 | String info = meta.info(); | |
226 | ||
227 | int opt = what.length() + where.length() + format.length(); | |
228 | if (opt + info.length() == 0) | |
229 | return null; | |
230 | ||
231 | StringBuilder builder = new StringBuilder(); | |
232 | builder.append("# "); | |
233 | ||
234 | if (opt > 0) { | |
235 | builder.append("("); | |
236 | if (what.length() > 0) { | |
237 | builder.append("WHAT: " + what); | |
238 | if (where.length() + format.length() > 0) | |
239 | builder.append(", "); | |
240 | } | |
241 | ||
242 | if (where.length() > 0) { | |
243 | builder.append("WHERE: " + where); | |
244 | if (format.length() > 0) | |
245 | builder.append(", "); | |
246 | } | |
247 | ||
248 | if (format.length() > 0) { | |
249 | builder.append("FORMAT: " + format); | |
250 | } | |
251 | ||
252 | builder.append(")\n# "); | |
253 | } | |
254 | ||
255 | builder.append(info); | |
256 | ||
257 | return builder.toString(); | |
258 | } | |
259 | ||
260 | /** | |
261 | * The enum representing textual information to be translated to the user as | |
262 | * a key. | |
263 | * | |
264 | * Note that each key that should be translated MUST be annotated with a | |
265 | * {@link Meta} annotation. | |
266 | * | |
267 | * @author niki | |
268 | * | |
269 | */ | |
270 | public enum StringId { | |
271 | DUMMY, // <-- TODO : remove | |
272 | NULL, // Special usage, no annotations so it is not visible in | |
273 | // .properties files | |
274 | @Meta(what = "a key to press", where = "action keys", format = "MUST BE 3 chars long", info = "Tab key") | |
275 | KEY_TAB, // keys | |
276 | @Meta(what = "a key to press", where = "action keys", format = "MUST BE 3 chars long", info = "Enter key") | |
277 | KEY_ENTER, // | |
278 | @Meta(what = "", where = "", format = "", info = "") | |
279 | KEY_ACTION_BACK, // MainWindow | |
280 | @Meta(what = "", where = "", format = "", info = "") | |
281 | KEY_ACTION_HELP, // | |
282 | @Meta(what = "", where = "", format = "", info = "") | |
283 | KEY_ACTION_VIEW_CARD, // FileList | |
284 | @Meta(what = "", where = "", format = "", info = "") | |
285 | KEY_ACTION_VIEW_CONTACT, // ContactList | |
286 | @Meta(what = "", where = "", format = "", info = "") | |
287 | KEY_ACTION_EDIT_CONTACT, // | |
288 | @Meta(what = "", where = "", format = "", info = "") | |
289 | KEY_ACTION_SAVE_CARD, // | |
290 | @Meta(what = "", where = "", format = "", info = "") | |
291 | KEY_ACTION_DELETE_CONTACT, // | |
292 | @Meta(what = "", where = "", format = "", info = "") | |
293 | KEY_ACTION_SEARCH, // | |
294 | @Meta(what = "", where = "", format = "", info = "we could use: ' ', ┃, │...") | |
295 | DEAULT_FIELD_SEPARATOR, // MainContentList | |
296 | @Meta(what = "", where = "", format = "", info = "") | |
297 | DEAULT_FIELD_SEPARATOR_NOUTF, // | |
298 | @Meta(what = "", where = "", format = "", info = "") | |
299 | KEY_ACTION_INVERT, // ContactDetails | |
300 | @Meta(what = "", where = "", format = "", info = "") | |
301 | KEY_ACTION_FULLSCREEN, // | |
302 | @Meta(what = "", where = "", format = "", info = "") | |
303 | KEY_ACTION_SWITCH_FORMAT, // multi-usage | |
304 | }; | |
a3b510ab | 305 | } |