ILIAS  release_5-2 Revision v5.2.25-18-g3f80b828510
class.ilObjLanguage.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (c) 1998-2009 ILIAS open source, Extended GPL, see docs/LICENSE */
3 
4 require_once "./Services/Object/classes/class.ilObject.php";
5 
14 class ilObjLanguage extends ilObject
15 {
28 
29  var $key;
30  var $status;
31 
32 
40  function __construct($a_id = 0, $a_call_by_reference = false)
41  {
42  global $lng;
43 
44  $this->type = "lng";
45  parent::__construct($a_id,$a_call_by_reference);
46 
47  $this->type = "lng";
48  $this->key = $this->title;
49  $this->status = $this->desc;
50  $this->lang_default = $lng->lang_default;
51  $this->lang_user = $lng->lang_user;
52  $this->lang_path = $lng->lang_path;
53  $this->cust_lang_path = $lng->cust_lang_path;
54  $this->separator = $lng->separator;
55  $this->comment_separator = $lng->comment_separator;
56  }
57 
58 
63  public static function getInstalledLanguages()
64  {
65  $objects = array();
67  foreach ($languages as $lang)
68  {
69  $langObj = new ilObjLanguage($lang["obj_id"], false);
70  if ($langObj->isInstalled())
71  {
72  $objects[] = $langObj;
73  }
74  else
75  {
76  unset($langObj);
77  }
78  }
79  return $objects;
80  }
81 
82 
88  function getKey()
89  {
90  return $this->key;
91  }
92 
98  function getStatus()
99  {
100  return $this->status;
101  }
102 
106  function isSystemLanguage()
107  {
108  if ($this->key == $this->lang_default)
109  return true;
110  else
111  return false;
112  }
113 
117  function isUserLanguage()
118  {
119  if ($this->key == $this->lang_user)
120  {
121  return true;
122  }
123  else
124  {
125  return false;
126  }
127  }
128 
129 
135  function isInstalled()
136  {
137  if (substr($this->getStatus(), 0, 9) == "installed")
138  {
139  return true;
140  }
141  else
142  {
143  return false;
144  }
145  }
146 
153  function isLocal()
154  {
155  if (substr($this->getStatus(), 10) == "local")
156  {
157  return true;
158  }
159  else
160  {
161  return false;
162  }
163  }
164 
171  function install($scope = '')
172  {
173  if (!empty($scope))
174  {
175  if ($scope == 'global')
176  {
177  $scope = '';
178  }
179  else
180  {
181  $scopeExtension = '.' . $scope;
182  }
183  }
184 
185  if (($this->isInstalled() == false) ||
186  ($this->isInstalled() == true && $this->isLocal() == false && !empty($scope)))
187  {
188  if ($this->check($scope))
189  {
190  // lang-file is ok. Flush data in db and...
191  if (empty($scope))
192  {
193  $this->flush('keep_local');
194  }
195 
196  // ...re-insert data from lang-file
197  $this->insert($scope);
198 
199  // update information in db-table about available/installed languages
200  if (empty($scope))
201  {
202  $newDesc = 'installed';
203  }
204  else if ($scope == 'local')
205  {
206  $newDesc = 'installed_local';
207  }
208  $this->setDescription($newDesc);
209  $this->update();
210  return $this->getKey();
211  }
212  }
213  return "";
214  }
215 
216 
222  function uninstall()
223  {
224  if ((substr($this->status, 0, 9) == "installed") && ($this->key != $this->lang_default) && ($this->key != $this->lang_user))
225  {
226  $this->flush('all');
227  $this->setTitle($this->key);
228  $this->setDescription("not_installed");
229  $this->update();
230  $this->resetUserLanguage($this->key);
231 
232  return $this->key;
233  }
234  return "";
235  }
236 
237 
242  function refresh()
243  {
244  if ($this->isInstalled() == true)
245  {
246  if ($this->check())
247  {
248  $this->flush('keep_local');
249  $this->insert();
250  $this->setTitle($this->getKey());
251  $this->setDescription($this->getStatus());
252  $this->update();
253 
254  if ($this->isLocal() == true)
255  {
256  if ($this->check('local'))
257  {
258  $this->insert('local');
259  $this->setTitle($this->getKey());
260  $this->setDescription($this->getStatus());
261  $this->update();
262  }
263  }
264  return true;
265  }
266  }
267  return false;
268  }
269 
273  static function refreshAll()
274  {
276  $refreshed = array();
277 
278  foreach ($languages as $lang)
279  {
280  $langObj = new ilObjLanguage($lang["obj_id"],false);
281  if ($langObj->refresh())
282  {
283  $refreshed[] = $langObj->getKey();
284  }
285  unset($langObj);
286  }
287 
288  self::refreshPlugins($refreshed);
289  }
290 
291 
296  public static function refreshPlugins($a_lang_keys = null)
297  {
298  global $ilPluginAdmin;
299 
300  // refresh languages of activated plugins
301  include_once("./Services/Component/classes/class.ilPluginSlot.php");
302  $slots = ilPluginSlot::getAllSlots();
303  foreach ($slots as $slot)
304  {
305  $act_plugins = $ilPluginAdmin->getActivePluginsForSlot($slot["component_type"],
306  $slot["component_name"], $slot["slot_id"]);
307  foreach ($act_plugins as $plugin)
308  {
309  include_once("./Services/Component/classes/class.ilPlugin.php");
310  $pl = ilPlugin::getPluginObject($slot["component_type"],
311  $slot["component_name"], $slot["slot_id"], $plugin);
312  if (is_object($pl))
313  {
314  $pl->updateLanguages($a_lang_keys);
315  }
316  }
317  }
318  }
319 
320 
326  static function _deleteLangData($a_lang_key, $a_keep_local_change = false)
327  {
328  global $ilDB;
329  if (!$a_keep_local_change)
330  {
331  $ilDB->manipulate("DELETE FROM lng_data WHERE lang_key = ".
332  $ilDB->quote($a_lang_key, "text"));
333  }
334  else
335  {
336  $ilDB->manipulate("DELETE FROM lng_data WHERE lang_key = ".
337  $ilDB->quote($a_lang_key, "text").
338  " AND local_change IS NULL");
339  }
340  }
341 
346  function flush($a_mode = 'all')
347  {
348  global $ilDB;
349 
350  ilObjLanguage::_deleteLangData($this->key, ($a_mode == 'keep_local'));
351 
352  if ($a_mode == 'all')
353  {
354  $ilDB->manipulate("DELETE FROM lng_modules WHERE lang_key = ".
355  $ilDB->quote($this->key, "text"));
356  }
357  }
358 
359 
366  function getLocalChanges($a_min_date = "", $a_max_date = "")
367  {
368  global $ilDB;
369 
370  if ($a_min_date == "")
371  {
372  $a_min_date = "1980-01-01 00:00:00";
373  }
374  if ($a_max_date == "")
375  {
376  $a_max_date = "2200-01-01 00:00:00";
377  }
378 
379  $q = sprintf("SELECT * FROM lng_data WHERE lang_key = %s ".
380  "AND local_change >= %s AND local_change <= %s",
381  $ilDB->quote($this->key, "text"), $ilDB->quote($a_min_date, "timestamp"),
382  $ilDB->quote($a_max_date, "timestamp"));
383  $result = $ilDB->query($q);
384 
385  $changes = array();
386  while ($row = $result->fetchRow(ilDBConstants::FETCHMODE_ASSOC))
387  {
388  $changes[$row["module"]][$row["identifier"]] = $row["value"];
389  }
390  return $changes;
391  }
392 
393 
399  static function _getLastLocalChange($a_key)
400  {
401  global $ilDB;
402 
403  $q = sprintf("SELECT MAX(local_change) last_change FROM lng_data ".
404  "WHERE lang_key = %s AND local_change IS NOT NULL",
405  $ilDB->quote($a_key, "text"));
406  $result = $ilDB->query($q);
407 
408  if ($row = $result->fetchRow(ilDBConstants::FETCHMODE_ASSOC))
409  {
410  return $row['last_change'];
411  }
412  else
413  {
414  return "";
415  }
416  }
417 
418 
425  static function _getLocalChangesByModule($a_key, $a_module)
426  {
428  global $ilDB;
429 
430  $changes = array();
431  $result = $ilDB->queryF("SELECT * FROM lng_data WHERE lang_key = %s AND module = %s AND local_change IS NOT NULL",
432 
433  array('text', 'text'),
434  array($a_key, $a_module));
435 
436  while ($row = $ilDB->fetchAssoc($result))
437  {
438  $changes[$row['identifier']] = $row['value'];
439  }
440  return $changes;
441  }
442 
443 
449  function insert($scope = '')
450  {
451  global $ilDB;
452 
453  if (!empty($scope))
454  {
455  if ($scope == 'global')
456  {
457  $scope = '';
458  }
459  else
460  {
461  $scopeExtension = '.' . $scope;
462  }
463  }
464 
466  if ($scope == "local")
467  {
468  $path = $this->cust_lang_path;
469  }
470 
471  $lang_file = $path . "/ilias_" . $this->key . ".lang" . $scopeExtension;
472  if (is_file($lang_file))
473  {
474  // initialize the array for updating lng_modules below
475  $lang_array = array();
476  $lang_array["common"] = array();
477 
478  // remove header first
479  if ($content = $this->cut_header(file($lang_file)))
480  {
481  if (empty($scope))
482  {
483  // reset change date for a global file
484  // get all local changes for a global file
485  $change_date = null;
486  $local_changes = $this->getLocalChanges();
487  }
488  else if ($scope == 'local')
489  {
490  // set the change date to import time for a local file
491  // get the modification date of the local file
492  // get the newer local changes for a local file
493  $change_date = date("Y-m-d H:i:s",time());
494  $min_date = date("Y-m-d H:i:s", filemtime($lang_file));
495  $local_changes = $this->getLocalChanges($min_date);
496  }
497 
498  foreach ($content as $key => $val)
499  {
500  // split the line of the language file
501  // [0]: module
502  // [1]: identifier
503  // [2]: value
504  // [3]: comment (optional)
505  $separated = explode($this->separator,trim($val));
506  $pos = strpos($separated[2], $this->comment_separator);
507  if ($pos !== false)
508  {
509  $separated[3] = substr($separated[2], $pos + strlen($this->comment_separator));
510  $separated[2] = substr($separated[2] , 0 , $pos);
511  }
512 
513  // check if the value has a local change
514  $local_value = $local_changes[$separated[0]][$separated[1]];
515 
516  if (empty($scope))
517  {
518  // import of a global language file
519 
520  if ($local_value != "" and $local_value != $separated[2])
521  {
522  // keep an existing and different local calue
523  $lang_array[$separated[0]][$separated[1]] = $local_value;
524  }
525  else
526  {
527  // check for double entries in global file
528  if ($double_checker[$separated[0]][$separated[1]][$this->key])
529  {
530  $this->ilias->raiseError("Duplicate Language Entry in $lang_file:\n$val",
531  $this->ilias->error_obj->MESSAGE);
532  }
533  $double_checker[$separated[0]][$separated[1]][$this->key] = true;
534 
535  // insert a new value if no local value exists
536  // reset local change date if the values are equal
537  ilObjLanguage::replaceLangEntry($separated[0], $separated[1],
538  $this->key, $separated[2], $change_date, $separated[3]);
539 
540  $lang_array[$separated[0]][$separated[1]] = $separated[2];
541  }
542  }
543  else if ($scope == 'local')
544  {
545  // import of a local language file
546 
547  if ($local_value != "")
548  {
549  // keep a locally changed value that is newer than the file
550  $lang_array[$separated[0]][$separated[1]] = $local_value;
551  }
552  else
553  {
554  // insert a new value if no global value exists
555  // (local files may have additional entries for customizations)
556  // set the change date to the import date
557  ilObjLanguage::replaceLangEntry($separated[0], $separated[1],
558  $this->key, $separated[2], $change_date, $separated[3]);
559 
560  $lang_array[$separated[0]][$separated[1]] = $separated[2];
561  }
562  }
563  }
564 
565  $ld = "";
566  if (empty($scope))
567  {
568  $ld = "installed";
569  }
570  else if ($scope == 'local')
571  {
572  $ld = "installed_local";
573  }
574  if ($ld)
575  {
576  $query = "UPDATE object_data SET " .
577  "description = ".$ilDB->quote($ld, "text").", " .
578  "last_update = ".$ilDB->now()." " .
579  "WHERE title = ".$ilDB->quote($this->key, "text")." " .
580  "AND type = 'lng'";
581  $ilDB->manipulate($query);
582  }
583  }
584 
585  foreach($lang_array as $module => $lang_arr)
586  {
587  if ($scope == "local")
588  {
589  $q = "SELECT * FROM lng_modules WHERE ".
590  " lang_key = ".$ilDB->quote($this->key, "text").
591  " AND module = ".$ilDB->quote($module, "text");
592  $set = $ilDB->query($q);
593  $row = $ilDB->fetchAssoc($set);
594  $arr2 = unserialize($row["lang_array"]);
595  if (is_array($arr2))
596  {
597  $lang_arr = array_merge($arr2, $lang_arr);
598  }
599  }
600  ilObjLanguage::replaceLangModule($this->key, $module, $lang_arr);
601  }
602  }
603  }
604 
608  static final function replaceLangModule($a_key, $a_module, $a_array)
609  {
610  global $DIC;
611  $ilDB = $DIC->database();
612 
613  ilGlobalCache::flushAll();
614 
615  $ilDB->manipulate(sprintf("DELETE FROM lng_modules WHERE lang_key = %s AND module = %s",
616  $ilDB->quote($a_key, "text"), $ilDB->quote($a_module, "text")));
617 
618  /*$ilDB->manipulate(sprintf("INSERT INTO lng_modules (lang_key, module, lang_array) VALUES ".
619  "(%s,%s,%s)", $ilDB->quote($a_key, "text"),
620  $ilDB->quote($a_module, "text"),
621  $ilDB->quote(serialize($a_array), "clob")));*/
622  $ilDB->insert("lng_modules", array(
623  "lang_key" => array("text", $a_key),
624  "module" => array("text", $a_module),
625  "lang_array" => array("clob", serialize((array) $a_array))
626  ));
627 
628  // check if the module is correctly saved
629  // see mantis #20046 and #19140
630  $result = $ilDB->queryF("SELECT lang_array FROM lng_modules WHERE lang_key = %s AND module = %s",
631  array('text','text'), array($a_key, $a_module));
632  $row = $ilDB->fetchAssoc($result);
633 
634  $unserialied = unserialize($row['lang_array']);
635  if (!is_array($unserialied))
636  {
638  $ilErr = $DIC['ilErr'];
639  $ilErr->raiseError("Data for module '" . $a_module . "' of language '" . $a_key . "' is not correctly saved. ".
640  "Please check the collation of your database tables lng_data and lng_modules. It must be utf8_unicode_ci.",
641  $ilErr->MESSAGE);
642  }
643  }
644 
648  static final function replaceLangEntry($a_module, $a_identifier,
649  $a_lang_key, $a_value, $a_local_change = null, $a_remarks = null)
650  {
651  global $ilDB;
652 
653  ilGlobalCache::flushAll();
654 
655  if (isset($a_remarks))
656  {
657  $a_remarks = substr($a_remarks, 0, 250);
658  }
659  if ($a_remarks == '')
660  {
661  unset($a_remarks);
662  }
663 
664  if (isset($a_value))
665  {
666  $a_value = substr($a_value, 0, 4000);
667  }
668  if ($a_value == '')
669  {
670  unset($a_value);
671  }
672 
673  $ilDB->replace(
674  'lng_data',
675  array(
676  'module' => array('text',$a_module),
677  'identifier' => array('text',$a_identifier),
678  'lang_key' => array('text',$a_lang_key)
679  ),
680  array(
681  'value' => array('text',$a_value),
682  'local_change' => array('timestamp',$a_local_change),
683  'remarks' => array('text', $a_remarks)
684  )
685  );
686  return true;
687 
688  /*
689  $ilDB->manipulate(sprintf("DELETE FROM lng_data WHERE module = %s AND ".
690  "identifier = %s AND lang_key = %s",
691  $ilDB->quote($a_module, "text"), $ilDB->quote($a_identifier, "text"),
692  $ilDB->quote($a_lang_key, "text")));
693 
694 
695  $ilDB->manipulate(sprintf("INSERT INTO lng_data " .
696  "(module, identifier, lang_key, value, local_change) " .
697  "VALUES (%s,%s,%s,%s,%s)",
698  $ilDB->quote($a_module, "text"), $ilDB->quote($a_identifier, "text"),
699  $ilDB->quote($a_lang_key, "text"), $ilDB->quote($a_value, "text"),
700  $ilDB->quote($a_local_change, "timestamp")));
701  */
702  }
703 
707  static final function updateLangEntry($a_module, $a_identifier,
708  $a_lang_key, $a_value, $a_local_change = null, $a_remarks = null)
709  {
710  global $ilDB;
711 
712  if (isset($a_remarks))
713  {
714  $a_remarks = substr($a_remarks, 0, 250);
715  }
716  if ($a_remarks == '')
717  {
718  unset($a_remarks);
719  }
720 
721  if (isset($a_value))
722  {
723  $a_value = substr($a_value, 0, 4000);
724  }
725  if ($a_value == '')
726  {
727  unset($a_value);
728  }
729 
730  $ilDB->manipulate(sprintf("UPDATE lng_data " .
731  "SET value = %s, local_change = %s, remarks = %s ".
732  "WHERE module = %s AND identifier = %s AND lang_key = %s ",
733  $ilDB->quote($a_value, "text"), $ilDB->quote($a_local_change, "timestamp"),
734  $ilDB->quote($a_remarks, "text"),
735  $ilDB->quote($a_module, "text"), $ilDB->quote($a_identifier, "text"),
736  $ilDB->quote($a_lang_key, "text")));
737  }
738 
739 
743  static final function deleteLangEntry($a_module, $a_identifier, $a_lang_key)
744  {
745  global $ilDB;
746 
747  $ilDB->manipulate(sprintf("DELETE FROM lng_data " .
748  "WHERE module = %s AND identifier = %s AND lang_key = %s ",
749  $ilDB->quote($a_module, "text"),
750  $ilDB->quote($a_identifier, "text"),
751  $ilDB->quote($a_lang_key, "text")));
752 
753  return true;
754  }
755 
756 
763  function resetUserLanguage($lang_key)
764  {
765  global $ilDB;
766 
767  $query = "UPDATE usr_pref SET " .
768  "value = ".$ilDB->quote($this->lang_default, "text")." " .
769  "WHERE keyword = ".$ilDB->quote('language', "text")." ".
770  "AND value = ".$ilDB->quote($lang_key, "text");
771  $ilDB->manipulate($query);
772  }
773 
782  static function cut_header($content)
783  {
784  foreach ($content as $key => $val)
785  {
786  if (trim($val) == "<!-- language file start -->")
787  {
788 
789  return array_slice($content,$key +1);
790  }
791  }
792 
793  return false;
794  }
795 
802  function optimizeData()
803  {
804  // Mantis #22313: removed table optimization
805  return true;
806  }
807 
817  function check($scope = '')
818  {
819  include_once("./Services/Utilities/classes/class.ilStr.php");
820 
821  if (!empty($scope))
822  {
823  if ($scope == 'global')
824  {
825  $scope = '';
826  }
827  else
828  {
829  $scopeExtension = '.' . $scope;
830  }
831  }
832 
834  if ($scope == "local")
835  {
836  $path = $this->cust_lang_path;
837  }
838 
839  $tmpPath = getcwd();
840 
841  // dir check
842  if (!is_dir($path))
843  {
844  $this->ilias->raiseError("Directory not found: ".$path, $this->ilias->error_obj->MESSAGE);
845  }
846 
847  chdir($path);
848 
849  // compute lang-file name format
850  $lang_file = "ilias_" . $this->key . ".lang" . $scopeExtension;
851 
852  // file check
853  if (!is_file($lang_file))
854  {
855  $this->ilias->raiseError("File not found: ".$lang_file,$this->ilias->error_obj->MESSAGE);
856  }
857 
858  // header check
859  $content = $this->cut_header(file($lang_file));
860  if ($content === false)
861  {
862  $this->ilias->raiseError("Wrong Header in ".$lang_file,$this->ilias->error_obj->MESSAGE);
863  }
864 
865  // check (counting) elements of each lang-entry
866  $line = 0;
867  foreach ($content as $key => $val)
868  {
869  $separated = explode($this->separator, trim($val));
870  $num = count($separated);
871  ++$n;
872  if ($num != 3)
873  {
874  $line = $n + 36;
875  $this->ilias->raiseError("Wrong parameter count in ".$lang_file." in line $line (Value: $val)! Please check your language file!",$this->ilias->error_obj->MESSAGE);
876  }
877  if (!ilStr::isUtf8($separated[2]))
878  {
879  $this->ilias->raiseError("Non UTF8 character found in ".$lang_file." in line $line (Value: $val)! Please check your language file!",$this->ilias->error_obj->MESSAGE);
880  }
881  }
882 
883  chdir($tmpPath);
884 
885  // no error occured
886  return true;
887  }
888 
892  static function countUsers($a_lang)
893  {
894  global $ilDB, $lng;
895 
896  $set = $ilDB->query("SELECT COUNT(*) cnt FROM usr_data ud JOIN usr_pref up".
897  " ON ud.usr_id = up.usr_id ".
898  " WHERE up.value = ".$ilDB->quote($a_lang, "text").
899  " AND up.keyword = ".$ilDB->quote("language", "text"));
900  $rec = $ilDB->fetchAssoc($set);
901 
902  // add users with no usr_pref set to default language
903  if ($a_lang == $lng->lang_default)
904  {
905  $set2 = $ilDB->query("SELECT COUNT(*) cnt FROM usr_data ud LEFT JOIN usr_pref up".
906  " ON (ud.usr_id = up.usr_id AND up.keyword = ".$ilDB->quote("language", "text").")".
907  " WHERE up.value IS NULL ");
908  $rec2 = $ilDB->fetchAssoc($set2);
909  }
910 
911  return (int) $rec["cnt"] + (int) $rec2["cnt"];
912  }
913 
914 
915 } // END class.LanguageObject
916 ?>
optimizeData()
optimizes the db-table langdata
static getPluginObject($a_ctype, $a_cname, $a_slot_id, $a_pname)
Get plugin object.
global $ilErr
Definition: raiseError.php:16
$path
Definition: aliased.php:25
static replaceLangEntry($a_module, $a_identifier, $a_lang_key, $a_value, $a_local_change=null, $a_remarks=null)
Replace lang entry.
isUserLanguage()
check if language is system language
static updateLangEntry($a_module, $a_identifier, $a_lang_key, $a_value, $a_local_change=null, $a_remarks=null)
Replace lang entry.
$result
Class ilObjLanguage.
Class ilObject Basic functions for all objects.
static getInstalledLanguages()
Get the language objects of the installed languages.
static _getObjectsByType($a_obj_type="", $a_owner="")
Get objects by type.
static countUsers($a_lang)
Count number of users that use a language.
isInstalled()
Check language object status, and return true if language is installed.
insert($scope='')
insert language data from file into database
__construct($a_id=0, $a_call_by_reference=false)
Constructor.
setTitle($a_title)
set object title
getStatus()
get language status
$ld
Definition: langwiz.php:244
uninstall()
uninstall current language
check($scope='')
Validate the logical structure of a lang file.
static _deleteLangData($a_lang_key, $a_keep_local_change=false)
Delete languge data.
date( 'd-M-Y', $objPHPExcel->getProperties() ->getCreated())
getLocalChanges($a_min_date="", $a_max_date="")
get locally changed language entries
isSystemLanguage()
check if language is system language
static deleteLangEntry($a_module, $a_identifier, $a_lang_key)
Delete lang entry.
refresh()
refresh current language
resetUserLanguage($lang_key)
search ILIAS for users which have selected &#39;$lang_key&#39; as their prefered language and reset them to d...
redirection script todo: (a better solution should control the processing via a xml file) ...
Reload workbook from saved file
$n
Definition: RandomTest.php:80
install($scope='')
install current language
Create styles array
The data for the language used.
flush($a_mode='all')
remove language data from database
global $ilDB
for($i=1; $i<=count($kw_cases_sel); $i+=1) $lang
Definition: langwiz.php:349
static refreshAll()
Refresh all installed languages.
isLocal()
Check language object status, and return true if a local language file is installed.
setDescription($a_desc)
set object description
global $DIC
Add data(end) time
Method that wraps PHPs time in order to allow simulations with the workflow.
$languages
Definition: cssgen2.php:34
update()
update object in db
static _getLastLocalChange($a_key)
get the date of the last local change
static refreshPlugins($a_lang_keys=null)
static getAllSlots()
Get all plugin slots.
static cut_header($content)
remove lang-file haeder information from &#39;$content&#39; This function seeks for a special keyword where t...
static isUtf8($a_str)
Check whether string is utf-8.
getKey()
get language key