4 * Fork this project on GitHub!
5 * https://github.com/Philipp15b/php-i18n
14 * This is the path for the language files. You must use the '{LANGUAGE}' placeholder for the language or the script wont find any language files.
18 protected $filePath = './lang/lang_{LANGUAGE}.ini';
22 * This is the path for all the cache files. Best is an empty directory with no other files in it.
26 protected $cachePath = './langcache/';
30 * This is the language which is used when there is no language file for all other user languages. It has the lowest priority.
31 * Remember to create a language file for the fallback!!
35 protected $fallbackLang = 'en';
38 * Merge in fallback language
39 * Whether to merge current language's strings with the strings of the fallback language ($fallbackLang).
43 protected $mergeFallback = false;
46 * The class name of the compiled class that contains the translated texts.
49 protected $prefix = 'L';
53 * If you want to force a specific language define it here.
57 protected $forcedLang = NULL;
60 * This is the separator used if you use sections in your ini-file.
61 * For example, if you have a string 'greeting' in a section 'welcomepage' you will can access it via 'L::welcomepage_greeting'.
62 * If you changed it to 'ABC' you could access your string via 'L::welcomepageABCgreeting'
66 protected $sectionSeparator = '_';
70 * The following properties are only available after calling init().
75 * These are the languages the user uses.
76 * Normally, if you use the getUserLangs-method this array will be filled in like this:
78 * 2. Language in $_GET['lang']
79 * 3. Language in $_SESSION['lang']
80 * 4. HTTP_ACCEPT_LANGUAGE
81 * 5. Language in $_COOKIE['lang']
82 * 6. Fallback language
86 protected $userLangs = array();
88 protected $appliedLang = NULL;
89 protected $langFilePath = NULL;
90 protected $cacheFilePath = NULL;
91 protected $isInitialized = false;
96 * The constructor sets all important settings. All params are optional, you can set the options via extra functions too.
98 * @param string [$filePath] This is the path for the language files. You must use the '{LANGUAGE}' placeholder for the language.
99 * @param string [$cachePath] This is the path for all the cache files. Best is an empty directory with no other files in it. No placeholders.
100 * @param string [$fallbackLang] This is the language which is used when there is no language file for all other user languages. It has the lowest priority.
101 * @param string [$prefix] The class name of the compiled class that contains the translated texts. Defaults to 'L'.
103 public function __construct($filePath = NULL, $cachePath = NULL, $fallbackLang = NULL, $prefix = NULL) {
105 if ($filePath != NULL) {
106 $this->filePath = $filePath;
109 if ($cachePath != NULL) {
110 $this->cachePath = $cachePath;
113 if ($fallbackLang != NULL) {
114 $this->fallbackLang = $fallbackLang;
117 if ($prefix != NULL) {
118 $this->prefix = $prefix;
122 public function init() {
123 if ($this->isInitialized()) {
124 throw new BadMethodCallException('This object from class ' . __CLASS__ . ' is already initialized. It is not possible to init one object twice!');
127 $this->isInitialized = true;
129 $this->userLangs = $this->getUserLangs();
131 // search for language file
132 $this->appliedLang = NULL;
133 foreach ($this->userLangs as $priority => $langcode) {
134 $this->langFilePath = $this->getConfigFilename($langcode);
135 if (file_exists($this->langFilePath)) {
136 $this->appliedLang = $langcode;
140 if ($this->appliedLang == NULL) {
141 throw new RuntimeException('No language file was found.');
144 // search for cache file
145 $this->cacheFilePath = $this->cachePath . '/php_i18n_' . md5_file(__FILE__) . '_' . $this->prefix . '_' . $this->appliedLang . '.cache.php';
147 // whether we need to create a new cache file
148 $outdated = !file_exists($this->cacheFilePath) ||
149 filemtime($this->cacheFilePath) < filemtime($this->langFilePath) || // the language config was updated
150 ($this->mergeFallback && filemtime($this->cacheFilePath) < filemtime($this->getConfigFilename($this->fallbackLang))); // the fallback language config was updated
153 $config = $this->load($this->langFilePath);
154 if ($this->mergeFallback)
155 $config = array_replace_recursive($this->load($this->getConfigFilename($this->fallbackLang)), $config);
157 $compiled = "<?php class " . $this->prefix . " {\n"
158 . $this->compile($config)
159 . 'public static function __callStatic($string, $args) {' . "\n"
160 . ' return vsprintf(constant("self::" . $string), $args);'
162 . "function ".$this->prefix .'($string, $args=NULL) {'."\n"
163 . ' $return = constant("'.$this->prefix.'::".$string);'."\n"
164 . ' return $args ? vsprintf($return,$args) : $return;'
167 if( ! is_dir($this->cachePath))
168 mkdir($this->cachePath, 0755, true);
170 if (file_put_contents($this->cacheFilePath, $compiled) === FALSE) {
171 throw new Exception("Could not write cache file to path '" . $this->cacheFilePath . "'. Is it writable?");
173 chmod($this->cacheFilePath, 0755);
177 require_once $this->cacheFilePath;
180 public function isInitialized() {
181 return $this->isInitialized;
184 public function getAppliedLang() {
185 return $this->appliedLang;
188 public function getCachePath() {
189 return $this->cachePath;
192 public function getFallbackLang() {
193 return $this->fallbackLang;
196 public function setFilePath($filePath) {
197 $this->fail_after_init();
198 $this->filePath = $filePath;
201 public function setCachePath($cachePath) {
202 $this->fail_after_init();
203 $this->cachePath = $cachePath;
206 public function setFallbackLang($fallbackLang) {
207 $this->fail_after_init();
208 $this->fallbackLang = $fallbackLang;
211 public function setMergeFallback($mergeFallback) {
212 $this->fail_after_init();
213 $this->mergeFallback = $mergeFallback;
216 public function setPrefix($prefix) {
217 $this->fail_after_init();
218 $this->prefix = $prefix;
221 public function setForcedLang($forcedLang) {
222 $this->fail_after_init();
223 $this->forcedLang = $forcedLang;
226 public function setSectionSeparator($sectionSeparator) {
227 $this->fail_after_init();
228 $this->sectionSeparator = $sectionSeparator;
232 * @deprecated Use setSectionSeparator.
234 public function setSectionSeperator($sectionSeparator) {
235 $this->setSectionSeparator($sectionSeparator);
240 * Returns the user languages
241 * Normally it returns an array like this:
243 * 2. Language in $_GET['lang']
244 * 3. Language in $_SESSION['lang']
245 * 4. HTTP_ACCEPT_LANGUAGE
246 * 5. Language in $_COOKIE['lang']
247 * 6. Fallback language
248 * Note: duplicate values are deleted.
250 * @return array with the user languages sorted by priority.
252 public function getUserLangs() {
253 $userLangs = array();
255 // Highest priority: forced language
256 if ($this->forcedLang != NULL) {
257 $userLangs[] = $this->forcedLang;
260 // 2nd highest priority: GET parameter 'lang'
261 if (isset($_GET['lang']) && is_string($_GET['lang'])) {
262 $userLangs[] = $_GET['lang'];
265 // 3rd highest priority: SESSION parameter 'lang'
266 if (isset($_SESSION['lang']) && is_string($_SESSION['lang'])) {
267 $userLangs[] = $_SESSION['lang'];
270 // 4th highest priority: HTTP_ACCEPT_LANGUAGE
271 if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
272 foreach (explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']) as $part) {
273 $userLangs[] = strtolower(substr($part, 0, 2));
277 // 5th highest priority: COOKIE
278 if (isset($_COOKIE['lang'])) {
279 $userLangs[] = $_COOKIE['lang'];
282 // Lowest priority: fallback
283 $userLangs[] = $this->fallbackLang;
285 // remove duplicate elements
286 $userLangs = array_unique($userLangs);
288 // remove illegal userLangs
289 $userLangs2 = array();
290 foreach ($userLangs as $key => $value) {
291 // only allow a-z, A-Z and 0-9 and _ and -
292 if (preg_match('/^[a-zA-Z0-9_-]*$/', $value) === 1)
293 $userLangs2[$key] = $value;
299 protected function getConfigFilename($langcode) {
300 return str_replace('{LANGUAGE}', $langcode, $this->filePath);
303 protected function load($filename) {
304 $ext = substr(strrchr($filename, '.'), 1);
308 $config = parse_ini_file($filename, true);
312 $config = spyc_load_file($filename);
315 $config = json_decode(file_get_contents($filename), true);
318 throw new InvalidArgumentException($ext . " is not a valid extension!");
324 * Recursively compile an associative array to PHP code.
326 protected function compile($config, $prefix = '') {
328 foreach ($config as $key => $value) {
329 if (is_array($value)) {
330 $code .= $this->compile($value, $prefix . $key . $this->sectionSeparator);
332 $fullName = $prefix . $key;
333 if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $fullName)) {
334 throw new InvalidArgumentException(__CLASS__ . ": Cannot compile translation key " . $fullName . " because it is not a valid PHP identifier.");
336 $code .= 'const ' . $fullName . ' = \'' . str_replace('\'', '\\\'', $value) . "';\n";
342 protected function fail_after_init() {
343 if ($this->isInitialized()) {
344 throw new BadMethodCallException('This ' . __CLASS__ . ' object is already initalized, so you can not change any settings.');