diff --git a/src/includes/class.lang.php b/src/includes/class.lang.php new file mode 100644 index 0000000..dc2e5d4 --- /dev/null +++ b/src/includes/class.lang.php @@ -0,0 +1,119 @@ +langDir}{$code}.json")) { + $jsonString = file_get_contents("{$lang->langDir}{$code}.json"); + $lang->loadJsonString($code, $jsonString); + } + else { + die("Language file not found ($code.json)"); + } + } + + function loadJsonString($code, $jsonString) + { + $this->code = $code; + $json = json_decode($jsonString, true); + + //load default language + if ( $code != $this->default ) { + $defStr = file_get_contents("{$this->langDir}{$this->default}.json"); + $default_json = json_decode($defStr, true); + $this->strings = array_replace($default_json, $json); + } + else { + $this->strings = $json; + } + } + + function get($key) + { + if ( isset($this->strings[$key]) ) { + return $this->strings[$key]; + } + return $key; + } + + function rtl() + { + if ( isset($this->strings['_rtl']) ) { + return intval($this->strings['_rtl']); + } + return 0; + } + + function makeJS($pretty = 0) + { + $a = array(); + $a['daysMin'] = $this->get('days_min'); + $a['daysLong'] = $this->get('days_long'); + $a['monthsLong'] = $this->get('months_long'); + + $this->fillWithValues($a, [ + 'confirmDelete', + 'confirmLeave', + 'actionNoteSave', + 'actionNoteCancel', + 'error', + 'denied', + 'invalidpass', + 'addList', + 'addListDefault', + 'renameList', + 'deleteList', + 'clearCompleted', + 'settingsSaved', + 'tags', + 'tasks', + 'f_past', + 'f_today', + 'f_soon' + ]); + + $opts = JSON_UNESCAPED_UNICODE; + if ($pretty) { + $opts |= JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES; + } + return json_encode($a, $opts); + } + + protected function fillWithValues(array &$a, array $keys) + { + foreach ( $keys as $key ) { + $a[$key] = $this->get($key); + } + } + + function langDir() + { + return $this->langDir; + } + +} + +?> \ No newline at end of file diff --git a/src/includes/lang/.htaccess b/src/includes/lang/.htaccess new file mode 100644 index 0000000..3418e55 --- /dev/null +++ b/src/includes/lang/.htaccess @@ -0,0 +1 @@ +deny from all \ No newline at end of file diff --git a/src/includes/lang/en.json b/src/includes/lang/en.json new file mode 100644 index 0000000..8c85bb0 --- /dev/null +++ b/src/includes/lang/en.json @@ -0,0 +1,169 @@ +{ + "_header": { + "ver": "v1.6", + "date": "2020-09-04", + "language": "English", + "original_name": "English", + "author": "Max Pozdeev", + "author_url": "http://www.mytinytodo.net" + }, + "My Tiny Todolist": "My Tiny Todolist", + "htab_newtask": "New task", + "htab_search": "Search", + "btn_add": "Add", + "btn_search": "Search", + "advanced_add": "Advanced", + "searching": "Searching for", + "tasks": "Tasks", + "taskdate_inline_created": "created at %s", + "taskdate_inline_completed": "Completed at %s", + "taskdate_inline_duedate": "Due %s", + "taskdate_created": "Created", + "taskdate_completed": "Completed", + "go_back": "<< Back", + "edit_task": "Edit Task", + "add_task": "New Task", + "priority": "Priority", + "task": "Task", + "note": "Note", + "tags": "Tags", + "save": "Save", + "cancel": "Cancel", + "password": "Password", + "btn_login": "Login", + "a_login": "Login", + "a_logout": "Logout", + "public_tasks": "Public Tasks", + "tagcloud": "Tags", + "tagfilter_cancel": "cancel filter", + "sortByHand": "Sort by hand", + "sortByPriority": "Sort by priority", + "sortByDueDate": "Sort by due date", + "sortByDateCreated": "Sort by date created", + "sortByDateModified": "Sort by date modified", + "due": "Due", + "daysago": "%d days ago", + "indays": "in %d days", + "months_short": [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec" + ], + "months_long": [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December" + ], + "days_min": [ + "Su", + "Mo", + "Tu", + "We", + "Th", + "Fr", + "Sa" + ], + "days_long": [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday" + ], + "today": "today", + "yesterday": "yesterday", + "tomorrow": "tomorrow", + "f_past": "Overdue", + "f_today": "Today and tomorrow", + "f_soon": "Soon", + "action_edit": "Edit", + "action_note": "Edit Note", + "action_delete": "Delete", + "action_priority": "Priority", + "action_move": "Move to", + "notes": "Notes:", + "notes_show": "Show", + "notes_hide": "Hide", + "list_new": "New list", + "list_rename": "Rename list", + "list_delete": "Delete list", + "list_publish": "Publish list", + "list_showcompleted": "Show completed tasks", + "list_clearcompleted": "Clear completed tasks", + "list_select": "Select list", + "list_export": "Export", + "list_export_csv": "CSV", + "list_export_ical": "iCalendar", + "list_rssfeed": "RSS Feed", + "alltags": "All tags:", + "alltags_show": "Show all", + "alltags_hide": "Hide all", + "a_settings": "Settings", + "rss_feed": "RSS Feed", + "feed_title": "%s", + "feed_completed_tasks": "Completed tasks", + "feed_modified_tasks": "Modified tasks", + "feed_new_tasks": "New tasks", + "alltasks": "All tasks", + "set_header": "Settings", + "set_title": "Title", + "set_title_descr": "(specify if you want to change default title)", + "set_language": "Language", + "set_protection": "Password protection", + "set_enabled": "Enabled", + "set_disabled": "Disabled", + "set_newpass": "New password", + "set_newpass_descr": "(leave blank if won't change current password)", + "set_smartsyntax": "Smart syntax", + "set_smartsyntax_descr": "(/priority/ task /tags/)", + "set_timezone": "Time zone", + "set_autotag": "Autotagging", + "set_autotag_descr": "(automatically adds tag of current tag filter to newly created task)", + "set_sessions": "Session handling mechanism", + "set_sessions_php": "PHP", + "set_sessions_files": "Files", + "set_firstdayofweek": "First day of week", + "set_custom": "Custom", + "set_date": "Date format", + "set_date2": "Short Date format", + "set_shortdate": "Short Date (current year)", + "set_clock": "Clock format", + "set_12hour": "12-hour", + "set_24hour": "24-hour", + "set_submit": "Submit changes", + "set_cancel": "Cancel", + "set_showdate": "Show task date in list", + "confirmDelete": "Are you sure you want to delete the task?", + "confirmLeave": "There can be unsaved data. Do you really want to leave?", + "actionNoteSave": "save", + "actionNoteCancel": "cancel", + "error": "Some error occurred (click for details)", + "denied": "Access denied", + "invalidpass": "Wrong password", + "addList": "Create new list", + "addListDefault": "Todo", + "renameList": "Rename list", + "deleteList": "This will delete current list with all tasks in it.\\nAre you sure?", + "clearCompleted": "This will delete all completed tasks in the list.\\nAre you sure?", + "settingsSaved": "Settings saved. Reloading..." +} \ No newline at end of file diff --git a/src/includes/lang/ru.json b/src/includes/lang/ru.json new file mode 100644 index 0000000..b07aba0 --- /dev/null +++ b/src/includes/lang/ru.json @@ -0,0 +1,169 @@ +{ + "_header": { + "ver": "v1.6", + "date": "2020-09-04", + "language": "Russian", + "original_name": "Русский", + "author": "Max Pozdeev", + "author_url": "http://www.mytinytodo.net" + }, + "My Tiny Todolist": "My Tiny Todolist", + "htab_newtask": "Новая задача", + "htab_search": "Поиск", + "btn_add": "Добавить", + "btn_search": "Искать", + "advanced_add": "Расширенная форма", + "searching": "Поиск", + "tasks": "Задачи", + "taskdate_inline_created": "добавлена %s", + "taskdate_inline_completed": "Завершена %s", + "taskdate_inline_duedate": "В срок %s", + "taskdate_created": "Дата создания", + "taskdate_completed": "Дата завершения", + "go_back": "<< Назад", + "edit_task": "Редактирование задачи", + "add_task": "Новая задача", + "priority": "Приоритет", + "task": "Задача", + "note": "Заметка", + "tags": "Теги", + "save": "Сохранить", + "cancel": "Отмена", + "password": "Пароль", + "btn_login": "Войти", + "a_login": "Вход", + "a_logout": "Выйти", + "public_tasks": "Опубликованные задачи", + "tagcloud": "Теги", + "tagfilter_cancel": "отменить фильтр по тегу", + "sortByHand": "Сортировка вручную", + "sortByPriority": "Сортировка по приоритету", + "sortByDueDate": "Сортировка по сроку", + "sortByDateCreated": "Сортировка по дате добавления", + "sortByDateModified": "Сортировка по дате изменения", + "due": "Срок", + "daysago": "%d дн. назад", + "indays": "через %d дн.", + "months_short": [ + "Янв", + "Фев", + "Мар", + "Апр", + "Май", + "Июн", + "Июл", + "Авг", + "Сен", + "Окт", + "Ноя", + "Дек" + ], + "months_long": [ + "Январь", + "Февраль", + "Март", + "Апрель", + "Май", + "Июнь", + "Июль", + "Август", + "Сентябрь", + "Октябрь", + "Ноябрь", + "Декабрь" + ], + "days_min": [ + "Вс", + "Пн", + "Вт", + "Ср", + "Чт", + "Пт", + "Сб" + ], + "days_long": [ + "Воскресенье", + "Понедельник", + "Вторник", + "Среда", + "Четверг", + "Пятница", + "Суббота" + ], + "today": "сегодня", + "yesterday": "вчера", + "tomorrow": "завтра", + "f_past": "Просроченные", + "f_today": "Сегодня и завтра", + "f_soon": "Скоро", + "action_edit": "Редактировать", + "action_note": "Заметка", + "action_delete": "Удалить", + "action_priority": "Приоритет", + "action_move": "Переместить в", + "notes": "Заметки:", + "notes_show": "Показать", + "notes_hide": "Скрыть", + "list_new": "Новый список", + "list_rename": "Переименовать список", + "list_delete": "Удалить список", + "list_publish": "Опубликовать список", + "list_showcompleted": "Показать завершенные задачи", + "list_clearcompleted": "Удалить завершенные задачи", + "list_select": "Выбрать список", + "list_export": "Экспортировать", + "list_export_csv": "CSV", + "list_export_ical": "iCalendar", + "list_rssfeed": "RSS-лента", + "alltags": "Все теги:", + "alltags_show": "Показать все", + "alltags_hide": "Скрыть все", + "a_settings": "Настройки", + "rss_feed": "RSS-лента", + "feed_title": "%s", + "feed_completed_tasks": "Завершенные задачи", + "feed_modified_tasks": "Изменившиеся задачи", + "feed_new_tasks": "Новые задачи", + "alltasks": "Все задачи", + "set_header": "Настройки", + "set_title": "Заголовок страницы", + "set_title_descr": "(если поле не заполнено, будет использован заголовок по-умолчанию)", + "set_language": "Язык (Language)", + "set_protection": "Парольная защита", + "set_enabled": "Включено", + "set_disabled": "Выключено", + "set_newpass": "Новый пароль", + "set_newpass_descr": "(не заполняйте поле если не хотите менять текущий пароль)", + "set_smartsyntax": "Smart syntax", + "set_smartsyntax_descr": "(возможность использовать синтаксис: /приоритет/ задача /теги/)", + "set_timezone": "Часовой пояс", + "set_autotag": "Autotagging", + "set_autotag_descr": "(автодобавление текущего тега из фильтра в новую задачу)", + "set_sessions": "Хранилище сессий", + "set_sessions_php": "PHP", + "set_sessions_files": "Файлы", + "set_firstdayofweek": "Первый день недели", + "set_custom": "другой", + "set_date": "Формат даты", + "set_date2": "Формат короткой даты", + "set_shortdate": "Короткая дата (в текущем году)", + "set_clock": "Формат часов", + "set_12hour": "12-часовой", + "set_24hour": "24-часовой", + "set_submit": "Сохранить изменения", + "set_cancel": "Отмена", + "set_showdate": "Показывать дату создания задачи", + "confirmDelete": "Вы действительно хотите удалить задачу?", + "confirmLeave": "На странице могут быть несохраненные данные. Вы действительно хотите закрыть страницу?", + "actionNoteSave": "сохранить", + "actionNoteCancel": "отмена", + "error": "Ошибка", + "denied": "Доступ запрещен", + "invalidpass": "Неверный пароль", + "addList": "Новый список", + "addListDefault": "Todo", + "renameList": "Переименовать список", + "deleteList": "Вы действительно хотите удалить этот список со всеми задачами?", + "clearCompleted": "Удалить все выполненные задачи из списка?", + "settingsSaved": "Настройки сохранены. Перезагрузка..." +} \ No newline at end of file diff --git a/src/init.php b/src/init.php index e65a51a..d2eefd0 100644 --- a/src/init.php +++ b/src/init.php @@ -13,6 +13,7 @@ if (version_compare(PHP_VERSION, '5.4.0') < 0) { } if(!defined('MTTPATH')) define('MTTPATH', dirname(__FILE__) .'/'); +if(!defined('MTTINC')) define('MTTINC', MTTPATH. 'includes/'); require_once(MTTPATH. 'common.php'); require_once(MTTPATH. 'db/config.php'); @@ -57,8 +58,8 @@ if(isset($_COOKIE['lang']) && preg_match("/^[a-z-]+$/i", $_COOKIE['lang']) && fi Config::set('lang', $_COOKIE['lang']); } -require_once(MTTPATH. 'lang/class.default.php'); -require_once(MTTPATH. 'lang/'.Config::get('lang').'.php'); +require_once(MTTINC. 'class.lang.php'); +Lang::loadLang(Config::get('lang')); $_mttinfo = array(); diff --git a/src/lang/class.default.php b/src/lang/class.default.php index e7b5ccf..7206c3e 100644 --- a/src/lang/class.default.php +++ b/src/lang/class.default.php @@ -186,7 +186,7 @@ class DefaultLang return $this->rtl ? 1 : 0; } - public static function instance() + public static function instance_deprecated() { if (!isset(self::$instance)) { //$c = __CLASS__; @@ -195,6 +195,82 @@ class DefaultLang } return self::$instance; } + + public function json($langFile = '', $pretty = 0) + { + $a = array(); + + if ($langFile != '') { + $ah = $this->loadLangHeader($langFile); + $a['_header'] = $ah; + } + + if ($this->rtl()) { + $a['_rtl'] = $this->rtl(); + } + + foreach ( $this->default_inc as $k=>$v ) { + if ( isset($this->inc[$k]) ) { + $v = $this->inc[$k]; + } + $a[$k] = $v; + } + + foreach ( $this->default_js as $k=>$v ) { + if ( isset($this->js[$k]) ) { + $v = $this->js[$k]; + } + $a[$k] = $v; + } + + $encOptions = JSON_UNESCAPED_UNICODE;; + if ($pretty) { + $encOptions |= JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES; + } + return json_encode($a, $encOptions); + } + + function loadLangHeader($langFile) + { + if (!file_exists($langFile)) { + die("file does not exist: $langFile"); + } + $contents = file_get_contents($langFile); + + $a = array(); + + # parse head comment + if(preg_match("|/\\*\s*myTinyTodo language pack([\s\S]+?)\\*/|", $contents, $m)) + { + $str = $m[1]; + if(preg_match("|AppVersion\s*:\s*(.+)|i", $str, $m)) { + $a['ver'] = trim($m[1]); + } + if(preg_match("|Date\s*:\s*(.+)|i", $str, $m)) { + $a['date'] = trim($m[1]); + } + if(preg_match("|Language\s*:\s*(.+)|i", $str, $m)) { + $a['language'] = trim($m[1]); + } + if(preg_match("|Original name\s*:\s*(.+)|i", $str, $m)) { + $a['original_name'] = trim($m[1]); + } + if(preg_match("|Author\s*:\s*(.+)|i", $str, $m)) { + $a['author'] = trim($m[1]); + } + if(preg_match("|Author URL\s*:(.+)|i", $str, $m)) { + $a['author_url'] = trim($m[1]); + } + if(preg_match("|Author Email\s*:(.+)|i", $str, $m)) { + $a['author_email'] = trim($m[1]); + } + if(preg_match("|Modified by\s*:(.+)|i", $str, $m)) { + $a['modified_by'] = trim($m[1]); + } + } + + return $a; + } } ?> \ No newline at end of file diff --git a/src/mytinytodo_lang.php b/src/mytinytodo_lang.php index 08c3797..c89042d 100644 --- a/src/mytinytodo_lang.php +++ b/src/mytinytodo_lang.php @@ -1,12 +1,30 @@ json('lang/'. $config['lang']. '.php', 1); + exit; +} + +require_once(MTTINC. 'class.lang.php'); +Lang::loadLang($config['lang']); header('Content-type: text/javascript; charset=utf-8'); -echo "mytinytodo.lang.init(". Lang::instance()->makeJS() .");"; +echo "mytinytodo.lang.init(". Lang::instance()->makeJS(1) .");"; -?> \ No newline at end of file diff --git a/src/settings.php b/src/settings.php index 4b79a82..035bc02 100644 --- a/src/settings.php +++ b/src/settings.php @@ -55,6 +55,9 @@ if(isset($_POST['save'])) jsonExit($t); } +$oldLangs = getOldLangs(1); +if (is_array($oldLangs) && count($oldLangs) == 0) $oldLangs = 0; + function _c($key) { @@ -62,6 +65,37 @@ function _c($key) } function getLangs($withContents = 0) +{ + $langDir = Lang::instance()->langDir(); + if ( ! $h = opendir($langDir) ) { + return false; + } + $a = array(); + while ( false !== ($file = readdir($h)) ) + { + if ( preg_match('/(.+)\.json$/', $file, $m) ) { + $jsonText = file_get_contents($langDir. $file); + if (false === $jsonText) { + die("false "); + continue; + } + $a[$m[1]] = $m[1]; + + $j = json_decode($jsonText, true); + if ( isset($j['_header']['language']) && isset($j['_header']['original_name']) ) { + $a[$m[1]]= [ + 'name' => $j['_header']['original_name'], + 'title' => $j['_header']['language'] + ]; + } + } + } + closedir($h); + return $a; +} + +/* deprecated */ +function getOldLangs($withContents = 0) { if (!$h = opendir(MTTPATH. 'lang')) return false; $a = array(); @@ -70,7 +104,7 @@ function getLangs($withContents = 0) if(preg_match('/(.+)\.php$/', $file, $m) && $file != 'class.default.php') { $a[$m[1]] = $m[1]; if($withContents) { - $a[$m[1]] = getLangDetails(MTTPATH. 'lang'. DIRECTORY_SEPARATOR. $file, $m[1]); + $a[$m[1]] = getOldLangDetails(MTTPATH. 'lang'. DIRECTORY_SEPARATOR. $file, $m[1]); $a[$m[1]]['name'] = $a[$m[1]]['original_name']; $a[$m[1]]['title'] = $a[$m[1]]['language']; } @@ -80,7 +114,8 @@ function getLangs($withContents = 0) return $a; } -function getLangDetails($filename, $default) +/* deprecated */ +function getOldLangDetails($filename, $default) { $contents = file_get_contents($filename); $a = array('language'=>$default, 'original_name'=>$default); @@ -156,7 +191,21 @@ header('Content-type:text/html; charset=utf-8'); : - + + + +

+ Notice! You can use old language files after downloading in the new format and copying to the /includes/lang/ folder.
+ + Download json file + + +