1 /**
2  * \file Language.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  * \author Jean-Marc Lasgouttes
8  * \author Jürgen Spitzmüller
9  * \author Dekel Tsur
10  *
11  * Full author contact details are available in file CREDITS.
12  */
13 
14 #include <config.h>
15 
16 #include "Language.h"
17 
18 #include "Encoding.h"
19 #include "Lexer.h"
20 #include "LyXRC.h"
21 
22 #include "support/debug.h"
23 #include "support/FileName.h"
24 #include "support/filetools.h"
25 #include "support/lassert.h"
26 #include "support/lstrings.h"
27 #include "support/Messages.h"
28 
29 using namespace std;
30 using namespace lyx::support;
31 
32 namespace lyx {
33 
34 Languages languages;
35 Language const * ignore_language = 0;
36 Language const * default_language = 0;
37 Language const * latex_language = 0;
38 Language const * reset_language = 0;
39 
40 
isPolyglossiaExclusive() const41 bool Language::isPolyglossiaExclusive() const
42 {
43 	return babel().empty() && !polyglossia().empty() && requires().empty();
44 }
45 
46 
isBabelExclusive() const47 bool Language::isBabelExclusive() const
48 {
49 	return !babel().empty() && polyglossia().empty() && requires().empty();
50 }
51 
52 
translateLayout(string const & m) const53 docstring const Language::translateLayout(string const & m) const
54 {
55 	if (m.empty())
56 		return docstring();
57 
58 	if (!isAscii(m)) {
59 		lyxerr << "Warning: not translating `" << m
60 		       << "' because it is not pure ASCII.\n";
61 		return from_utf8(m);
62 	}
63 
64 	TranslationMap::const_iterator it = layoutTranslations_.find(m);
65 	if (it != layoutTranslations_.end())
66 		return it->second;
67 
68 	docstring t = from_ascii(m);
69 	cleanTranslation(t);
70 	return t;
71 }
72 
73 
readLanguage(Lexer & lex)74 bool Language::readLanguage(Lexer & lex)
75 {
76 	enum LanguageTags {
77 		LA_AS_BABELOPTS = 1,
78 		LA_BABELNAME,
79 		LA_ENCODING,
80 		LA_END,
81 		LA_FONTENC,
82 		LA_GUINAME,
83 		LA_HAS_GUI_SUPPORT,
84 		LA_INTERNAL_ENC,
85 		LA_LANG_CODE,
86 		LA_LANG_VARIETY,
87 		LA_POLYGLOSSIANAME,
88 		LA_POLYGLOSSIAOPTS,
89 		LA_POSTBABELPREAMBLE,
90 		LA_QUOTESTYLE,
91 		LA_PREBABELPREAMBLE,
92 		LA_REQUIRES,
93 		LA_RTL
94 	};
95 
96 	// Keep these sorted alphabetically!
97 	LexerKeyword languageTags[] = {
98 		{ "asbabeloptions",       LA_AS_BABELOPTS },
99 		{ "babelname",            LA_BABELNAME },
100 		{ "encoding",             LA_ENCODING },
101 		{ "end",                  LA_END },
102 		{ "fontencoding",         LA_FONTENC },
103 		{ "guiname",              LA_GUINAME },
104 		{ "hasguisupport",        LA_HAS_GUI_SUPPORT },
105 		{ "internalencoding",     LA_INTERNAL_ENC },
106 		{ "langcode",             LA_LANG_CODE },
107 		{ "langvariety",          LA_LANG_VARIETY },
108 		{ "polyglossianame",      LA_POLYGLOSSIANAME },
109 		{ "polyglossiaopts",      LA_POLYGLOSSIAOPTS },
110 		{ "postbabelpreamble",    LA_POSTBABELPREAMBLE },
111 		{ "prebabelpreamble",     LA_PREBABELPREAMBLE },
112 		{ "quotestyle",           LA_QUOTESTYLE },
113 		{ "requires",             LA_REQUIRES },
114 		{ "rtl",                  LA_RTL }
115 	};
116 
117 	bool error = false;
118 	bool finished = false;
119 	lex.pushTable(languageTags);
120 	// parse style section
121 	while (!finished && lex.isOK() && !error) {
122 		int le = lex.lex();
123 		// See comment in LyXRC.cpp.
124 		switch (le) {
125 		case Lexer::LEX_FEOF:
126 			continue;
127 
128 		case Lexer::LEX_UNDEF: // parse error
129 			lex.printError("Unknown language tag `$$Token'");
130 			error = true;
131 			continue;
132 
133 		default:
134 			break;
135 		}
136 		switch (static_cast<LanguageTags>(le)) {
137 		case LA_END: // end of structure
138 			finished = true;
139 			break;
140 		case LA_AS_BABELOPTS:
141 			lex >> as_babel_options_;
142 			break;
143 		case LA_BABELNAME:
144 			lex >> babel_;
145 			break;
146 		case LA_POLYGLOSSIANAME:
147 			lex >> polyglossia_name_;
148 			break;
149 		case LA_POLYGLOSSIAOPTS:
150 			lex >> polyglossia_opts_;
151 			break;
152 		case LA_QUOTESTYLE:
153 			lex >> quote_style_;
154 			break;
155 		case LA_ENCODING:
156 			lex >> encodingStr_;
157 			break;
158 		case LA_FONTENC:
159 			lex >> fontenc_;
160 			break;
161 		case LA_GUINAME:
162 			lex >> display_;
163 			break;
164 		case LA_HAS_GUI_SUPPORT:
165 			lex >> has_gui_support_;
166 			break;
167 		case LA_INTERNAL_ENC:
168 			lex >> internal_enc_;
169 			break;
170 		case LA_LANG_CODE:
171 			lex >> code_;
172 			break;
173 		case LA_LANG_VARIETY:
174 			lex >> variety_;
175 			break;
176 		case LA_POSTBABELPREAMBLE:
177 			babel_postsettings_ =
178 				lex.getLongString(from_ascii("EndPostBabelPreamble"));
179 			break;
180 		case LA_PREBABELPREAMBLE:
181 			babel_presettings_ =
182 				lex.getLongString(from_ascii("EndPreBabelPreamble"));
183 			break;
184 		case LA_REQUIRES:
185 			lex >> requires_;
186 			break;
187 		case LA_RTL:
188 			lex >> rightToLeft_;
189 			break;
190 		}
191 	}
192 	lex.popTable();
193 	return finished && !error;
194 }
195 
196 
read(Lexer & lex)197 bool Language::read(Lexer & lex)
198 {
199 	as_babel_options_ = 0;
200 	encoding_ = 0;
201 	internal_enc_ = 0;
202 	rightToLeft_ = 0;
203 
204 	if (!lex.next()) {
205 		lex.printError("No name given for language: `$$Token'.");
206 		return false;
207 	}
208 
209 	lang_ = lex.getString();
210 	LYXERR(Debug::INFO, "Reading language " << lang_);
211 	if (!readLanguage(lex)) {
212 		LYXERR0("Error parsing language `" << lang_ << '\'');
213 		return false;
214 	}
215 
216 	encoding_ = encodings.fromLyXName(encodingStr_);
217 	if (!encoding_ && !encodingStr_.empty()) {
218 		encoding_ = encodings.fromLyXName("iso8859-1");
219 		LYXERR0("Unknown encoding " << encodingStr_);
220 	}
221 	return true;
222 }
223 
224 
readLayoutTranslations(Language::TranslationMap const & trans,bool replace)225 void Language::readLayoutTranslations(Language::TranslationMap const & trans, bool replace)
226 {
227 	TranslationMap::const_iterator const end = trans.end();
228 	for (TranslationMap::const_iterator it = trans.begin(); it != end; ++it) {
229 		if (replace
230 			|| layoutTranslations_.find(it->first) == layoutTranslations_.end())
231 			layoutTranslations_[it->first] = it->second;
232 	}
233 }
234 
235 
read(FileName const & filename)236 void Languages::read(FileName const & filename)
237 {
238 	Lexer lex;
239 	lex.setFile(filename);
240 	lex.setContext("Languages::read");
241 	while (lex.isOK()) {
242 		int le = lex.lex();
243 		switch (le) {
244 		case Lexer::LEX_FEOF:
245 			continue;
246 
247 		default:
248 			break;
249 		}
250 		if (lex.getString() != "Language") {
251 			lex.printError("Unknown Language tag `$$Token'");
252 			continue;
253 		}
254 		Language l;
255 		l.read(lex);
256 		if (!lex)
257 			break;
258 		if (l.lang() == "latex") {
259 			// Check if latex language was not already defined.
260 			LASSERT(latex_language == 0, continue);
261 			static const Language latex_lang = l;
262 			latex_language = &latex_lang;
263 		} else if (l.lang() == "ignore") {
264 			// Check if ignore language was not already defined.
265 			LASSERT(ignore_language == 0, continue);
266 			static const Language ignore_lang = l;
267 			ignore_language = &ignore_lang;
268 		} else
269 			languagelist[l.lang()] = l;
270 	}
271 
272 	default_language = getLanguage("english");
273 	if (!default_language) {
274 		LYXERR0("Default language \"english\" not found!");
275 		default_language = &(*languagelist.begin()).second;
276 		LYXERR0("Using \"" << default_language->lang() << "\" instead!");
277 	}
278 
279 	// Read layout translations
280 	FileName const path = libFileSearch(string(), "layouttranslations");
281 	readLayoutTranslations(path);
282 }
283 
284 
285 namespace {
286 
readTranslations(Lexer & lex,Language::TranslationMap & trans)287 bool readTranslations(Lexer & lex, Language::TranslationMap & trans)
288 {
289 	while (lex.isOK()) {
290 		if (lex.checkFor("End"))
291 			break;
292 		if (!lex.next(true))
293 			return false;
294 		string const key = lex.getString();
295 		if (!lex.next(true))
296 			return false;
297 		docstring const val = lex.getDocString();
298 		trans[key] = val;
299 	}
300 	return true;
301 }
302 
303 
304 enum Match {
305 	NoMatch,
306 	ApproximateMatch,
307 	ExactMatch
308 };
309 
310 
match(string const & code,Language const & lang)311 Match match(string const & code, Language const & lang)
312 {
313 	// we need to mimic gettext: code can be a two-letter code, which
314 	// should match all variants, e.g. "de" should match "de_DE",
315 	// "de_AT" etc.
316 	// special case for chinese:
317 	// simplified  => code == "zh_CN", langcode == "zh_CN"
318 	// traditional => code == "zh_TW", langcode == "zh_CN"
319 	string const variety = lang.variety();
320 	string const langcode = variety.empty() ?
321 	                        lang.code() : lang.code() + '_' + variety;
322 	string const name = lang.lang();
323 	if ((code == langcode && name != "chinese-traditional")
324 		|| (code == "zh_TW"  && name == "chinese-traditional"))
325 		return ExactMatch;
326 	if ((code.size() == 2) && (langcode.size() > 2)
327 		&& (code + '_' == langcode.substr(0, 3)))
328 		return ApproximateMatch;
329 	return NoMatch;
330 }
331 
332 } // namespace
333 
334 
readLayoutTranslations(support::FileName const & filename)335 void Languages::readLayoutTranslations(support::FileName const & filename)
336 {
337 	Lexer lex;
338 	lex.setFile(filename);
339 	lex.setContext("Languages::read");
340 
341 	// 1) read all translations (exact and approximate matches) into trans
342 	typedef std::map<string, Language::TranslationMap> TransMap;
343 	TransMap trans;
344 	LanguageList::iterator const lbeg = languagelist.begin();
345 	LanguageList::iterator const lend = languagelist.end();
346 	while (lex.isOK()) {
347 		if (!lex.checkFor("Translation")) {
348 			if (lex.isOK())
349 				lex.printError("Unknown layout translation tag `$$Token'");
350 			break;
351 		}
352 		if (!lex.next(true))
353 			break;
354 		string const code = lex.getString();
355 		bool found = false;
356 		for (LanguageList::iterator lit = lbeg; lit != lend; ++lit) {
357 			if (match(code, lit->second) != NoMatch) {
358 				found = true;
359 				break;
360 			}
361 		}
362 		if (!found) {
363 			lex.printError("Unknown language `" + code + "'");
364 			break;
365 		}
366 		if (!readTranslations(lex, trans[code])) {
367 			lex.printError("Could not read layout translations for language `"
368 				+ code + "'");
369 			break;
370 		}
371 	}
372 
373 	// 2) merge all translations into the languages
374 	// exact translations overwrite approximate ones
375 	TransMap::const_iterator const tbeg = trans.begin();
376 	TransMap::const_iterator const tend = trans.end();
377 	for (TransMap::const_iterator tit = tbeg; tit != tend; ++tit) {
378 		for (LanguageList::iterator lit = lbeg; lit != lend; ++lit) {
379 			Match const m = match(tit->first, lit->second);
380 			if (m == NoMatch)
381 				continue;
382 			lit->second.readLayoutTranslations(tit->second,
383 			                                   m == ExactMatch);
384 		}
385 	}
386 
387 }
388 
389 
getLanguage(string const & language) const390 Language const * Languages::getLanguage(string const & language) const
391 {
392 	if (language == "reset")
393 		return reset_language;
394 	if (language == "ignore")
395 		return ignore_language;
396 	const_iterator it = languagelist.find(language);
397 	return it == languagelist.end() ? reset_language : &it->second;
398 }
399 
400 
401 } // namespace lyx
402