aboutsummaryrefslogtreecommitdiffhomepage
path: root/application
diff options
context:
space:
mode:
authorArthurHoaro <arthur@hoa.ro>2017-05-09 18:12:15 +0200
committerArthurHoaro <arthur@hoa.ro>2017-10-22 12:55:03 +0200
commit12266213d098a53c5f005b9afcbbe62771fd580c (patch)
treec7adfb280272fee16a5e2011f55315f838a07395 /application
parent72cfe44436f4316112fc4aabfe8940aa7b4adcab (diff)
downloadShaarli-12266213d098a53c5f005b9afcbbe62771fd580c.tar.gz
Shaarli-12266213d098a53c5f005b9afcbbe62771fd580c.tar.zst
Shaarli-12266213d098a53c5f005b9afcbbe62771fd580c.zip
Shaarli's translation
* translation system and unit tests * Translations everywhere Dont use translation merge It is not available with PHP builtin gettext, so it would have lead to inconsistency.
Diffstat (limited to 'application')
-rw-r--r--application/ApplicationUtils.php19
-rw-r--r--application/Cache.php2
-rw-r--r--application/FeedBuilder.php4
-rw-r--r--application/History.php4
-rw-r--r--application/Languages.php153
-rw-r--r--application/LinkDB.php20
-rw-r--r--application/LinkFilter.php8
-rw-r--r--application/NetscapeBookmarkUtils.php16
-rw-r--r--application/PageBuilder.php7
-rw-r--r--application/PluginManager.php7
-rw-r--r--application/Updater.php8
-rw-r--r--application/Utils.php17
-rw-r--r--application/config/ConfigJson.php15
-rw-r--r--application/config/ConfigManager.php6
-rw-r--r--application/config/ConfigPhp.php4
-rw-r--r--application/config/exception/MissingFieldConfigException.php2
-rw-r--r--application/config/exception/PluginConfigOrderException.php2
-rw-r--r--application/config/exception/UnauthorizedConfigException.php2
-rw-r--r--application/exceptions/IOException.php2
19 files changed, 232 insertions, 66 deletions
diff --git a/application/ApplicationUtils.php b/application/ApplicationUtils.php
index 5643f4a0..911873a0 100644
--- a/application/ApplicationUtils.php
+++ b/application/ApplicationUtils.php
@@ -149,12 +149,13 @@ class ApplicationUtils
149 public static function checkPHPVersion($minVersion, $curVersion) 149 public static function checkPHPVersion($minVersion, $curVersion)
150 { 150 {
151 if (version_compare($curVersion, $minVersion) < 0) { 151 if (version_compare($curVersion, $minVersion) < 0) {
152 throw new Exception( 152 $msg = t(
153 'Your PHP version is obsolete!' 153 'Your PHP version is obsolete!'
154 .' Shaarli requires at least PHP '.$minVersion.', and thus cannot run.' 154 . ' Shaarli requires at least PHP %s, and thus cannot run.'
155 .' Your PHP version has known security vulnerabilities and should be' 155 . ' Your PHP version has known security vulnerabilities and should be'
156 .' updated as soon as possible.' 156 . ' updated as soon as possible.'
157 ); 157 );
158 throw new Exception(sprintf($msg, $minVersion));
158 } 159 }
159 } 160 }
160 161
@@ -179,7 +180,7 @@ class ApplicationUtils
179 $rainTplDir.'/'.$conf->get('resource.theme'), 180 $rainTplDir.'/'.$conf->get('resource.theme'),
180 ) as $path) { 181 ) as $path) {
181 if (! is_readable(realpath($path))) { 182 if (! is_readable(realpath($path))) {
182 $errors[] = '"'.$path.'" directory is not readable'; 183 $errors[] = '"'.$path.'" '. t('directory is not readable');
183 } 184 }
184 } 185 }
185 186
@@ -191,10 +192,10 @@ class ApplicationUtils
191 $conf->get('resource.raintpl_tmp'), 192 $conf->get('resource.raintpl_tmp'),
192 ) as $path) { 193 ) as $path) {
193 if (! is_readable(realpath($path))) { 194 if (! is_readable(realpath($path))) {
194 $errors[] = '"'.$path.'" directory is not readable'; 195 $errors[] = '"'.$path.'" '. t('directory is not readable');
195 } 196 }
196 if (! is_writable(realpath($path))) { 197 if (! is_writable(realpath($path))) {
197 $errors[] = '"'.$path.'" directory is not writable'; 198 $errors[] = '"'.$path.'" '. t('directory is not writable');
198 } 199 }
199 } 200 }
200 201
@@ -212,10 +213,10 @@ class ApplicationUtils
212 } 213 }
213 214
214 if (! is_readable(realpath($path))) { 215 if (! is_readable(realpath($path))) {
215 $errors[] = '"'.$path.'" file is not readable'; 216 $errors[] = '"'.$path.'" '. t('file is not readable');
216 } 217 }
217 if (! is_writable(realpath($path))) { 218 if (! is_writable(realpath($path))) {
218 $errors[] = '"'.$path.'" file is not writable'; 219 $errors[] = '"'.$path.'" '. t('file is not writable');
219 } 220 }
220 } 221 }
221 222
diff --git a/application/Cache.php b/application/Cache.php
index 5d050165..e5d43e61 100644
--- a/application/Cache.php
+++ b/application/Cache.php
@@ -13,7 +13,7 @@
13function purgeCachedPages($pageCacheDir) 13function purgeCachedPages($pageCacheDir)
14{ 14{
15 if (! is_dir($pageCacheDir)) { 15 if (! is_dir($pageCacheDir)) {
16 $error = 'Cannot purge '.$pageCacheDir.': no directory'; 16 $error = sprintf(t('Cannot purge %s: no directory'), $pageCacheDir);
17 error_log($error); 17 error_log($error);
18 return $error; 18 return $error;
19 } 19 }
diff --git a/application/FeedBuilder.php b/application/FeedBuilder.php
index 7377bcec..3cfaafb4 100644
--- a/application/FeedBuilder.php
+++ b/application/FeedBuilder.php
@@ -148,9 +148,9 @@ class FeedBuilder
148 $link['url'] = $pageaddr . $link['url']; 148 $link['url'] = $pageaddr . $link['url'];
149 } 149 }
150 if ($this->usePermalinks === true) { 150 if ($this->usePermalinks === true) {
151 $permalink = '<a href="'. $link['url'] .'" title="Direct link">Direct link</a>'; 151 $permalink = '<a href="'. $link['url'] .'" title="'. t('Direct link') .'">'. t('Direct link') .'</a>';
152 } else { 152 } else {
153 $permalink = '<a href="'. $link['guid'] .'" title="Permalink">Permalink</a>'; 153 $permalink = '<a href="'. $link['guid'] .'" title="'. t('Permalink') .'">'. t('Permalink') .'</a>';
154 } 154 }
155 $link['description'] = format_description($link['description'], '', $pageaddr); 155 $link['description'] = format_description($link['description'], '', $pageaddr);
156 $link['description'] .= PHP_EOL .'<br>&#8212; '. $permalink; 156 $link['description'] .= PHP_EOL .'<br>&#8212; '. $permalink;
diff --git a/application/History.php b/application/History.php
index 5e3b1b72..35ec016a 100644
--- a/application/History.php
+++ b/application/History.php
@@ -171,7 +171,7 @@ class History
171 } 171 }
172 172
173 if (! is_writable($this->historyFilePath)) { 173 if (! is_writable($this->historyFilePath)) {
174 throw new Exception('History file isn\'t readable or writable'); 174 throw new Exception(t('History file isn\'t readable or writable'));
175 } 175 }
176 } 176 }
177 177
@@ -182,7 +182,7 @@ class History
182 { 182 {
183 $this->history = FileUtils::readFlatDB($this->historyFilePath, []); 183 $this->history = FileUtils::readFlatDB($this->historyFilePath, []);
184 if ($this->history === false) { 184 if ($this->history === false) {
185 throw new Exception('Could not parse history file'); 185 throw new Exception(t('Could not parse history file'));
186 } 186 }
187 } 187 }
188 188
diff --git a/application/Languages.php b/application/Languages.php
index c8b0a25a..4ba32f29 100644
--- a/application/Languages.php
+++ b/application/Languages.php
@@ -1,21 +1,150 @@
1<?php 1<?php
2 2
3namespace Shaarli;
4
5use Gettext\GettextTranslator;
6use Gettext\Merge;
7use Gettext\Translations;
8use Gettext\Translator;
9use Gettext\TranslatorInterface;
10use Shaarli\Config\ConfigManager;
11
3/** 12/**
4 * Wrapper function for translation which match the API 13 * Class Languages
5 * of gettext()/_() and ngettext(). 14 *
15 * Load Shaarli translations using 'gettext/gettext'.
16 * This class allows to either use PHP gettext extension, or a PHP implementation of gettext,
17 * with a fixed language, or dynamically using autoLocale().
6 * 18 *
7 * Not doing translation for now. 19 * Translation files PO/MO files follow gettext standard and must be placed under:
20 * <translation path>/<language>/LC_MESSAGES/<domain>.[po|mo]
8 * 21 *
9 * @param string $text Text to translate. 22 * Pros/cons:
10 * @param string $nText The plural message ID. 23 * - gettext extension is faster
11 * @param int $nb The number of items for plural forms. 24 * - gettext is very system dependent (PHP extension, the locale must be installed, and web server reloaded)
12 * 25 *
13 * @return String Text translated. 26 * Settings:
27 * - translation.mode:
28 * - auto: use default setting (PHP implementation)
29 * - php: use PHP implementation
30 * - gettext: use gettext wrapper
31 * - translation.language:
32 * - auto: use autoLocale() and the language change according to user HTTP headers
33 * - fixed language: e.g. 'fr'
34 * - translation.extensions:
35 * - domain => translation_path: allow plugins and themes to extend the defaut extension
36 * The domain must be unique, and translation path must be relative, and contains the tree mentioned above.
37 *
38 * @package Shaarli
14 */ 39 */
15function t($text, $nText = '', $nb = 0) { 40class Languages
16 if (empty($nText)) { 41{
17 return $text; 42 /**
43 * Core translations domain
44 */
45 const DEFAULT_DOMAIN = 'shaarli';
46
47 /**
48 * @var TranslatorInterface
49 */
50 protected $translator;
51
52 /**
53 * @var string
54 */
55 protected $language;
56
57 /**
58 * @var ConfigManager
59 */
60 protected $conf;
61
62 /**
63 * Languages constructor.
64 *
65 * @param string $language lang determined by autoLocale(), can be override.
66 * @param ConfigManager $conf instance.
67 */
68 public function __construct($language, $conf)
69 {
70 $this->conf = $conf;
71 $confLanguage = $this->conf->get('translation.language', 'auto');
72 if ($confLanguage === 'auto' || ! $this->isValidLanguage($confLanguage)) {
73 $this->language = substr($language, 0, 5);
74 } else {
75 $this->language = $confLanguage;
76 }
77
78 if (! extension_loaded('gettext')
79 || in_array($this->conf->get('translation.mode', 'auto'), ['auto', 'php'])
80 ) {
81 $this->initPhpTranslator();
82 } else {
83 $this->initGettextTranslator();
84 }
85
86 // Register default functions (e.g. '__()') to use our Translator
87 $this->translator->register();
88 }
89
90 /**
91 * Initialize the translator using php gettext extension (gettext dependency act as a wrapper).
92 */
93 protected function initGettextTranslator ()
94 {
95 $this->translator = new GettextTranslator();
96 $this->translator->setLanguage($this->language);
97 $this->translator->loadDomain(self::DEFAULT_DOMAIN, 'inc/languages');
98
99 foreach ($this->conf->get('translation.extensions', []) as $domain => $translationPath) {
100 if ($domain !== self::DEFAULT_DOMAIN) {
101 $this->translator->loadDomain($domain, $translationPath, false);
102 }
103 }
104 }
105
106 /**
107 * Initialize the translator using a PHP implementation of gettext.
108 *
109 * Note that if language po file doesn't exist, errors are ignored (e.g. not installed language).
110 */
111 protected function initPhpTranslator()
112 {
113 $this->translator = new Translator();
114 $translations = new Translations();
115 // Core translations
116 try {
117 /** @var Translations $translations */
118 $translations = $translations->addFromPoFile('inc/languages/'. $this->language .'/LC_MESSAGES/shaarli.po');
119 $translations->setDomain('shaarli');
120 $this->translator->loadTranslations($translations);
121 } catch (\InvalidArgumentException $e) {}
122
123
124 // Extension translations (plugins, themes, etc.).
125 foreach ($this->conf->get('translation.extensions', []) as $domain => $translationPath) {
126 if ($domain === self::DEFAULT_DOMAIN) {
127 continue;
128 }
129
130 try {
131 /** @var Translations $extension */
132 $extension = Translations::fromPoFile($translationPath . $this->language .'/LC_MESSAGES/'. $domain .'.po');
133 $extension->setDomain($domain);
134 $this->translator->loadTranslations($extension);
135 } catch (\InvalidArgumentException $e) {}
136 }
137 }
138
139 /**
140 * Checks if a language string is valid.
141 *
142 * @param string $language e.g. 'fr' or 'en_US'
143 *
144 * @return bool true if valid, false otherwise
145 */
146 protected function isValidLanguage($language)
147 {
148 return preg_match('/^[a-z]{2}(_[A-Z]{2})?/', $language) === 1;
18 } 149 }
19 $actualForm = $nb > 1 ? $nText : $text;
20 return sprintf($actualForm, $nb);
21} 150}
diff --git a/application/LinkDB.php b/application/LinkDB.php
index 22c1f0ab..f026a041 100644
--- a/application/LinkDB.php
+++ b/application/LinkDB.php
@@ -133,16 +133,16 @@ class LinkDB implements Iterator, Countable, ArrayAccess
133 { 133 {
134 // TODO: use exceptions instead of "die" 134 // TODO: use exceptions instead of "die"
135 if (!$this->loggedIn) { 135 if (!$this->loggedIn) {
136 die('You are not authorized to add a link.'); 136 die(t('You are not authorized to add a link.'));
137 } 137 }
138 if (!isset($value['id']) || empty($value['url'])) { 138 if (!isset($value['id']) || empty($value['url'])) {
139 die('Internal Error: A link should always have an id and URL.'); 139 die(t('Internal Error: A link should always have an id and URL.'));
140 } 140 }
141 if (($offset !== null && ! is_int($offset)) || ! is_int($value['id'])) { 141 if (($offset !== null && ! is_int($offset)) || ! is_int($value['id'])) {
142 die('You must specify an integer as a key.'); 142 die(t('You must specify an integer as a key.'));
143 } 143 }
144 if ($offset !== null && $offset !== $value['id']) { 144 if ($offset !== null && $offset !== $value['id']) {
145 die('Array offset and link ID must be equal.'); 145 die(t('Array offset and link ID must be equal.'));
146 } 146 }
147 147
148 // If the link exists, we reuse the real offset, otherwise new entry 148 // If the link exists, we reuse the real offset, otherwise new entry
@@ -248,13 +248,13 @@ class LinkDB implements Iterator, Countable, ArrayAccess
248 $this->links = array(); 248 $this->links = array();
249 $link = array( 249 $link = array(
250 'id' => 1, 250 'id' => 1,
251 'title'=>' Shaarli: the personal, minimalist, super-fast, no-database delicious clone', 251 'title'=> t('The personal, minimalist, super-fast, database free, bookmarking service'),
252 'url'=>'https://shaarli.readthedocs.io', 252 'url'=>'https://shaarli.readthedocs.io',
253 'description'=>'Welcome to Shaarli! This is your first public bookmark. To edit or delete me, you must first login. 253 'description'=>t('Welcome to Shaarli! This is your first public bookmark. To edit or delete me, you must first login.
254 254
255To learn how to use Shaarli, consult the link "Help/documentation" at the bottom of this page. 255To learn how to use Shaarli, consult the link "Documentation" at the bottom of this page.
256 256
257You use the community supported version of the original Shaarli project, by Sebastien Sauvage.', 257You use the community supported version of the original Shaarli project, by Sebastien Sauvage.'),
258 'private'=>0, 258 'private'=>0,
259 'created'=> new DateTime(), 259 'created'=> new DateTime(),
260 'tags'=>'opensource software' 260 'tags'=>'opensource software'
@@ -264,9 +264,9 @@ You use the community supported version of the original Shaarli project, by Seba
264 264
265 $link = array( 265 $link = array(
266 'id' => 0, 266 'id' => 0,
267 'title'=>'My secret stuff... - Pastebin.com', 267 'title'=> t('My secret stuff... - Pastebin.com'),
268 'url'=>'http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=', 268 'url'=>'http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=',
269 'description'=>'Shhhh! I\'m a private link only YOU can see. You can delete me too.', 269 'description'=> t('Shhhh! I\'m a private link only YOU can see. You can delete me too.'),
270 'private'=>1, 270 'private'=>1,
271 'created'=> new DateTime('1 minute ago'), 271 'created'=> new DateTime('1 minute ago'),
272 'tags'=>'secretstuff', 272 'tags'=>'secretstuff',
diff --git a/application/LinkFilter.php b/application/LinkFilter.php
index 99ecd1e2..12376e27 100644
--- a/application/LinkFilter.php
+++ b/application/LinkFilter.php
@@ -444,5 +444,11 @@ class LinkFilter
444 444
445class LinkNotFoundException extends Exception 445class LinkNotFoundException extends Exception
446{ 446{
447 protected $message = 'The link you are trying to reach does not exist or has been deleted.'; 447 /**
448 * LinkNotFoundException constructor.
449 */
450 public function __construct()
451 {
452 $this->message = t('The link you are trying to reach does not exist or has been deleted.');
453 }
448} 454}
diff --git a/application/NetscapeBookmarkUtils.php b/application/NetscapeBookmarkUtils.php
index 31796367..31a14537 100644
--- a/application/NetscapeBookmarkUtils.php
+++ b/application/NetscapeBookmarkUtils.php
@@ -32,11 +32,11 @@ class NetscapeBookmarkUtils
32 { 32 {
33 // see tpl/export.html for possible values 33 // see tpl/export.html for possible values
34 if (! in_array($selection, array('all', 'public', 'private'))) { 34 if (! in_array($selection, array('all', 'public', 'private'))) {
35 throw new Exception('Invalid export selection: "'.$selection.'"'); 35 throw new Exception(t('Invalid export selection:') .' "'.$selection.'"');
36 } 36 }
37 37
38 $bookmarkLinks = array(); 38 $bookmarkLinks = array();
39 397
40 foreach ($linkDb as $link) { 40 foreach ($linkDb as $link) {
41 if ($link['private'] != 0 && $selection == 'public') { 41 if ($link['private'] != 0 && $selection == 'public') {
42 continue; 42 continue;
@@ -79,14 +79,14 @@ class NetscapeBookmarkUtils
79 $duration=0 79 $duration=0
80 ) 80 )
81 { 81 {
82 $status = 'File '.$filename.' ('.$filesize.' bytes) '; 82 $status = sprintf(t('File %s (%d bytes) '), $filename, $filesize);
83 if ($importCount == 0 && $overwriteCount == 0 && $skipCount == 0) { 83 if ($importCount == 0 && $overwriteCount == 0 && $skipCount == 0) {
84 $status .= 'has an unknown file format. Nothing was imported.'; 84 $status .= t('has an unknown file format. Nothing was imported.');
85 } else { 85 } else {
86 $status .= 'was successfully processed in '. $duration .' seconds: '; 86 $status .= vsprintf(
87 $status .= $importCount.' links imported, '; 87 t('was successfully processed in %d seconds: %d links imported, %d links overwritten, %d links skipped.'),
88 $status .= $overwriteCount.' links overwritten, '; 88 [$duration, $importCount, $overwriteCount, $skipCount]
89 $status .= $skipCount.' links skipped.'; 89 );
90 } 90 }
91 return $status; 91 return $status;
92 } 92 }
diff --git a/application/PageBuilder.php b/application/PageBuilder.php
index 291860ad..af290671 100644
--- a/application/PageBuilder.php
+++ b/application/PageBuilder.php
@@ -159,9 +159,12 @@ class PageBuilder
159 * 159 *
160 * @param string $message A messate to display what is not found 160 * @param string $message A messate to display what is not found
161 */ 161 */
162 public function render404($message = 'The page you are trying to reach does not exist or has been deleted.') 162 public function render404($message = '')
163 { 163 {
164 header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found'); 164 if (empty($message)) {
165 $message = t('The page you are trying to reach does not exist or has been deleted.');
166 }
167 header($_SERVER['SERVER_PROTOCOL'] .' '. t('404 Not Found'));
165 $this->tpl->assign('error_message', $message); 168 $this->tpl->assign('error_message', $message);
166 $this->renderPage('404'); 169 $this->renderPage('404');
167 } 170 }
diff --git a/application/PluginManager.php b/application/PluginManager.php
index 59ece4fa..cf603845 100644
--- a/application/PluginManager.php
+++ b/application/PluginManager.php
@@ -188,6 +188,9 @@ class PluginManager
188 $metaData[$plugin] = parse_ini_file($metaFile); 188 $metaData[$plugin] = parse_ini_file($metaFile);
189 $metaData[$plugin]['order'] = array_search($plugin, $this->authorizedPlugins); 189 $metaData[$plugin]['order'] = array_search($plugin, $this->authorizedPlugins);
190 190
191 if (isset($metaData[$plugin]['description'])) {
192 $metaData[$plugin]['description'] = t($metaData[$plugin]['description']);
193 }
191 // Read parameters and format them into an array. 194 // Read parameters and format them into an array.
192 if (isset($metaData[$plugin]['parameters'])) { 195 if (isset($metaData[$plugin]['parameters'])) {
193 $params = explode(';', $metaData[$plugin]['parameters']); 196 $params = explode(';', $metaData[$plugin]['parameters']);
@@ -203,7 +206,7 @@ class PluginManager
203 $metaData[$plugin]['parameters'][$param]['value'] = ''; 206 $metaData[$plugin]['parameters'][$param]['value'] = '';
204 // Optional parameter description in parameter.PARAM_NAME= 207 // Optional parameter description in parameter.PARAM_NAME=
205 if (isset($metaData[$plugin]['parameter.'. $param])) { 208 if (isset($metaData[$plugin]['parameter.'. $param])) {
206 $metaData[$plugin]['parameters'][$param]['desc'] = $metaData[$plugin]['parameter.'. $param]; 209 $metaData[$plugin]['parameters'][$param]['desc'] = t($metaData[$plugin]['parameter.'. $param]);
207 } 210 }
208 } 211 }
209 } 212 }
@@ -237,6 +240,6 @@ class PluginFileNotFoundException extends Exception
237 */ 240 */
238 public function __construct($pluginName) 241 public function __construct($pluginName)
239 { 242 {
240 $this->message = 'Plugin "'. $pluginName .'" files not found.'; 243 $this->message = sprintf(t('Plugin "%s" files not found.'), $pluginName);
241 } 244 }
242} 245}
diff --git a/application/Updater.php b/application/Updater.php
index 72b2def0..723a7a81 100644
--- a/application/Updater.php
+++ b/application/Updater.php
@@ -73,7 +73,7 @@ class Updater
73 } 73 }
74 74
75 if ($this->methods === null) { 75 if ($this->methods === null) {
76 throw new UpdaterException('Couldn\'t retrieve Updater class methods.'); 76 throw new UpdaterException(t('Couldn\'t retrieve Updater class methods.'));
77 } 77 }
78 78
79 foreach ($this->methods as $method) { 79 foreach ($this->methods as $method) {
@@ -482,7 +482,7 @@ class UpdaterException extends Exception
482 } 482 }
483 483
484 if (! empty($this->method)) { 484 if (! empty($this->method)) {
485 $out .= 'An error occurred while running the update '. $this->method . PHP_EOL; 485 $out .= t('An error occurred while running the update ') . $this->method . PHP_EOL;
486 } 486 }
487 487
488 if (! empty($this->previous)) { 488 if (! empty($this->previous)) {
@@ -522,11 +522,11 @@ function read_updates_file($updatesFilepath)
522function write_updates_file($updatesFilepath, $updates) 522function write_updates_file($updatesFilepath, $updates)
523{ 523{
524 if (empty($updatesFilepath)) { 524 if (empty($updatesFilepath)) {
525 throw new Exception('Updates file path is not set, can\'t write updates.'); 525 throw new Exception(t('Updates file path is not set, can\'t write updates.'));
526 } 526 }
527 527
528 $res = file_put_contents($updatesFilepath, implode(';', $updates)); 528 $res = file_put_contents($updatesFilepath, implode(';', $updates));
529 if ($res === false) { 529 if ($res === false) {
530 throw new Exception('Unable to write updates in '. $updatesFilepath . '.'); 530 throw new Exception(t('Unable to write updates in '. $updatesFilepath . '.'));
531 } 531 }
532} 532}
diff --git a/application/Utils.php b/application/Utils.php
index 4a2f5561..27eaafc5 100644
--- a/application/Utils.php
+++ b/application/Utils.php
@@ -452,7 +452,7 @@ function get_max_upload_size($limitPost, $limitUpload, $format = true)
452 */ 452 */
453function alphabetical_sort(&$data, $reverse = false, $byKeys = false) 453function alphabetical_sort(&$data, $reverse = false, $byKeys = false)
454{ 454{
455 $callback = function($a, $b) use ($reverse) { 455 $callback = function ($a, $b) use ($reverse) {
456 // Collator is part of PHP intl. 456 // Collator is part of PHP intl.
457 if (class_exists('Collator')) { 457 if (class_exists('Collator')) {
458 $collator = new Collator(setlocale(LC_COLLATE, 0)); 458 $collator = new Collator(setlocale(LC_COLLATE, 0));
@@ -470,3 +470,18 @@ function alphabetical_sort(&$data, $reverse = false, $byKeys = false)
470 usort($data, $callback); 470 usort($data, $callback);
471 } 471 }
472} 472}
473
474/**
475 * Wrapper function for translation which match the API
476 * of gettext()/_() and ngettext().
477 *
478 * @param string $text Text to translate.
479 * @param string $nText The plural message ID.
480 * @param int $nb The number of items for plural forms.
481 * @param string $domain The domain where the translation is stored (default: shaarli).
482 *
483 * @return String Text translated.
484 */
485function t($text, $nText = '', $nb = 1, $domain = 'shaarli') {
486 return dn__($domain, $text, $nText, $nb);
487}
diff --git a/application/config/ConfigJson.php b/application/config/ConfigJson.php
index 9ef2ef56..8c8d5610 100644
--- a/application/config/ConfigJson.php
+++ b/application/config/ConfigJson.php
@@ -22,10 +22,15 @@ class ConfigJson implements ConfigIO
22 $data = json_decode($data, true); 22 $data = json_decode($data, true);
23 if ($data === null) { 23 if ($data === null) {
24 $errorCode = json_last_error(); 24 $errorCode = json_last_error();
25 $error = 'An error occurred while parsing JSON configuration file ('. $filepath .'): error code #'; 25 $error = sprintf(
26 $error .= $errorCode. '<br>➜ <code>' . json_last_error_msg() .'</code>'; 26 'An error occurred while parsing JSON configuration file (%s): error code #%d',
27 $filepath,
28 $errorCode
29 );
30 $error .= '<br>➜ <code>' . json_last_error_msg() .'</code>';
27 if ($errorCode === JSON_ERROR_SYNTAX) { 31 if ($errorCode === JSON_ERROR_SYNTAX) {
28 $error .= '<br>Please check your JSON syntax (without PHP comment tags) using a JSON lint tool such as '; 32 $error .= '<br>';
33 $error .= 'Please check your JSON syntax (without PHP comment tags) using a JSON lint tool such as ';
29 $error .= '<a href="http://jsonlint.com/">jsonlint.com</a>.'; 34 $error .= '<a href="http://jsonlint.com/">jsonlint.com</a>.';
30 } 35 }
31 throw new \Exception($error); 36 throw new \Exception($error);
@@ -44,8 +49,8 @@ class ConfigJson implements ConfigIO
44 if (!file_put_contents($filepath, $data)) { 49 if (!file_put_contents($filepath, $data)) {
45 throw new \IOException( 50 throw new \IOException(
46 $filepath, 51 $filepath,
47 'Shaarli could not create the config file. 52 t('Shaarli could not create the config file. '.
48 Please make sure Shaarli has the right to write in the folder is it installed in.' 53 'Please make sure Shaarli has the right to write in the folder is it installed in.')
49 ); 54 );
50 } 55 }
51 } 56 }
diff --git a/application/config/ConfigManager.php b/application/config/ConfigManager.php
index 7ff2fe67..9e4c9f63 100644
--- a/application/config/ConfigManager.php
+++ b/application/config/ConfigManager.php
@@ -132,7 +132,7 @@ class ConfigManager
132 public function set($setting, $value, $write = false, $isLoggedIn = false) 132 public function set($setting, $value, $write = false, $isLoggedIn = false)
133 { 133 {
134 if (empty($setting) || ! is_string($setting)) { 134 if (empty($setting) || ! is_string($setting)) {
135 throw new \Exception('Invalid setting key parameter. String expected, got: '. gettype($setting)); 135 throw new \Exception(t('Invalid setting key parameter. String expected, got: '). gettype($setting));
136 } 136 }
137 137
138 // During the ConfigIO transition, map legacy settings to the new ones. 138 // During the ConfigIO transition, map legacy settings to the new ones.
@@ -339,6 +339,10 @@ class ConfigManager
339 $this->setEmpty('redirector.url', ''); 339 $this->setEmpty('redirector.url', '');
340 $this->setEmpty('redirector.encode_url', true); 340 $this->setEmpty('redirector.encode_url', true);
341 341
342 $this->setEmpty('translation.language', 'auto');
343 $this->setEmpty('translation.mode', 'php');
344 $this->setEmpty('translation.extensions', []);
345
342 $this->setEmpty('plugins', array()); 346 $this->setEmpty('plugins', array());
343 } 347 }
344 348
diff --git a/application/config/ConfigPhp.php b/application/config/ConfigPhp.php
index 2633824d..2f66e8e0 100644
--- a/application/config/ConfigPhp.php
+++ b/application/config/ConfigPhp.php
@@ -118,8 +118,8 @@ class ConfigPhp implements ConfigIO
118 ) { 118 ) {
119 throw new \IOException( 119 throw new \IOException(
120 $filepath, 120 $filepath,
121 'Shaarli could not create the config file. 121 t('Shaarli could not create the config file. '.
122 Please make sure Shaarli has the right to write in the folder is it installed in.' 122 'Please make sure Shaarli has the right to write in the folder is it installed in.')
123 ); 123 );
124 } 124 }
125 } 125 }
diff --git a/application/config/exception/MissingFieldConfigException.php b/application/config/exception/MissingFieldConfigException.php
index 6346c6a9..9e0a9359 100644
--- a/application/config/exception/MissingFieldConfigException.php
+++ b/application/config/exception/MissingFieldConfigException.php
@@ -18,6 +18,6 @@ class MissingFieldConfigException extends \Exception
18 public function __construct($field) 18 public function __construct($field)
19 { 19 {
20 $this->field = $field; 20 $this->field = $field;
21 $this->message = 'Configuration value is required for '. $this->field; 21 $this->message = sprintf(t('Configuration value is required for %s'), $this->field);
22 } 22 }
23} 23}
diff --git a/application/config/exception/PluginConfigOrderException.php b/application/config/exception/PluginConfigOrderException.php
index f9d68750..f82ec26e 100644
--- a/application/config/exception/PluginConfigOrderException.php
+++ b/application/config/exception/PluginConfigOrderException.php
@@ -12,6 +12,6 @@ class PluginConfigOrderException extends \Exception
12 */ 12 */
13 public function __construct() 13 public function __construct()
14 { 14 {
15 $this->message = 'An error occurred while trying to save plugins loading order.'; 15 $this->message = t('An error occurred while trying to save plugins loading order.');
16 } 16 }
17} 17}
diff --git a/application/config/exception/UnauthorizedConfigException.php b/application/config/exception/UnauthorizedConfigException.php
index 79672c1b..72311fae 100644
--- a/application/config/exception/UnauthorizedConfigException.php
+++ b/application/config/exception/UnauthorizedConfigException.php
@@ -13,6 +13,6 @@ class UnauthorizedConfigException extends \Exception
13 */ 13 */
14 public function __construct() 14 public function __construct()
15 { 15 {
16 $this->message = 'You are not authorized to alter config.'; 16 $this->message = t('You are not authorized to alter config.');
17 } 17 }
18} 18}
diff --git a/application/exceptions/IOException.php b/application/exceptions/IOException.php
index b563b23d..18e46b77 100644
--- a/application/exceptions/IOException.php
+++ b/application/exceptions/IOException.php
@@ -16,7 +16,7 @@ class IOException extends Exception
16 public function __construct($path, $message = '') 16 public function __construct($path, $message = '')
17 { 17 {
18 $this->path = $path; 18 $this->path = $path;
19 $this->message = empty($message) ? 'Error accessing' : $message; 19 $this->message = empty($message) ? t('Error accessing') : $message;
20 $this->message .= ' "' . $this->path .'"'; 20 $this->message .= ' "' . $this->path .'"';
21 } 21 }
22} 22}