improved language handling, thanks Vladyslav
This commit is contained in:
@@ -1 +1 @@
|
||||
Version 2.00.0 (2012-07-12 14:05:01) dev
|
||||
Version 2.00.0 (2012-07-12 14:58:26) dev
|
||||
|
||||
@@ -909,7 +909,10 @@ def plugin():
|
||||
statics.sort()
|
||||
|
||||
# Get all languages
|
||||
languages = listdir(apath('%s/languages/' % app, r=request), '[\w-]*\.py')
|
||||
languages = sorted([lang+'.py' for lang, info in
|
||||
T.get_possible_languages_info().iteritems()
|
||||
if info[2]!=0]) # info[2] is langfile_mtime:
|
||||
# get only existed files
|
||||
|
||||
#Get crontab
|
||||
crontab = apath('%s/cron/crontab' % app, r=request)
|
||||
|
||||
@@ -144,6 +144,7 @@
|
||||
'Use an url:': 'Use an url:',
|
||||
'Version': 'Version',
|
||||
'Views': 'Views',
|
||||
'Web Framework': 'Web Framework',
|
||||
'Welcome to web2py': 'Добре дошъл в web2py',
|
||||
'YES': 'YES',
|
||||
'additional code for your application': 'additional code for your application',
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
'Administrator Password:': 'Пароль администратора:',
|
||||
'Application name:': 'Название приложения:',
|
||||
'Are you sure you want to delete file "%s"?': 'Вы действительно хотите удалить файл "%s"?',
|
||||
'Are you sure you want to delete this object?': 'Are you sure you want to delete this object?',
|
||||
'Are you sure you want to uninstall application "%s"': 'Вы действительно хотите удалить приложение "%s"',
|
||||
'Are you sure you want to uninstall application "%s"?': 'Вы действительно хотите удалить приложение "%s"?',
|
||||
'Are you sure you want to upgrade web2py now?': 'Вы действительно хотите обновить web2py сейчас?',
|
||||
@@ -34,12 +35,17 @@
|
||||
'Cannot be empty': 'Не может быть пустым',
|
||||
'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': 'Невозможно компилировать: в приложении присутствуют ошибки. Отладьте его, исправьте ошибки и попробуйте заново.',
|
||||
'Change Password': 'Изменить пароль',
|
||||
'Change admin password': 'Изменить пароль администратора',
|
||||
'Check for upgrades': 'проверить обновления',
|
||||
'Check to delete': 'Поставьте для удаления',
|
||||
'Checking for upgrades...': 'Проверка обновлений...',
|
||||
'Clean': 'Очистить',
|
||||
'Client IP': 'IP клиента',
|
||||
'Compile': 'Компилировать',
|
||||
'Controller': 'Контроллер',
|
||||
'Controllers': 'Контроллеры',
|
||||
'Copyright': 'Copyright',
|
||||
'Create': 'Создать',
|
||||
'Create new simple application': 'Создать новое простое приложение',
|
||||
'Current request': 'Текущий запрос',
|
||||
'Current response': 'Текущий ответ',
|
||||
@@ -48,11 +54,15 @@
|
||||
'DESIGN': 'ДИЗАЙН',
|
||||
'Database': 'База данных',
|
||||
'Date and Time': 'Дата и время',
|
||||
'Debug': 'Debug',
|
||||
'Delete': 'Удалить',
|
||||
'Delete:': 'Удалить:',
|
||||
'Deploy': 'Развернуть',
|
||||
'Deploy on Google App Engine': 'Развернуть на Google App Engine',
|
||||
'Deploy to OpenShift': 'Deploy to OpenShift',
|
||||
'Description': 'Описание',
|
||||
'Design for': 'Дизайн для',
|
||||
'Disable': 'Disable',
|
||||
'E-mail': 'E-mail',
|
||||
'EDIT': 'ПРАВКА',
|
||||
'Edit': 'Правка',
|
||||
@@ -65,18 +75,22 @@
|
||||
'Editing file "%s"': 'Правка файла "%s"',
|
||||
'Enterprise Web Framework': 'Enterprise Web Framework',
|
||||
'Error logs for "%(app)s"': 'Журнал ошибок для "%(app)"',
|
||||
'Errors': 'Ошибка',
|
||||
'Exception instance attributes': 'Атрибуты объекта исключения',
|
||||
'Expand Abbreviation': 'Раскрыть аббревиатуру',
|
||||
'First name': 'Имя',
|
||||
'Functions with no doctests will result in [passed] tests.': 'Функции без doctest будут давать [прошел] в тестах.',
|
||||
'Get from URL:': 'Get from URL:',
|
||||
'Go to Matching Pair': 'К подходящей паре',
|
||||
'Group ID': 'ID группы',
|
||||
'Hello World': 'Привет, Мир',
|
||||
'Help': 'Помощь',
|
||||
'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.': 'Если отчет выше содержит номер ошибки, это указывает на ошибку при работе контроллера, до попытки выполнить doctest. Причиной чаще является неверные отступы или ошибки в коде вне функции. \nЗеленый заголовок указывает на успешное выполнение всех тестов. В этом случае результаты тестов не показываются.',
|
||||
'If you answer "yes", be patient, it may take a while to download': 'Если вы ответили "Да", потерпите, загрузка может потребовать времени',
|
||||
'If you answer yes, be patient, it may take a while to download': 'Если вы ответили "Да", потерпите, загрузка может потребовать времени',
|
||||
'Import/Export': 'Импорт/Экспорт',
|
||||
'Index': 'Индекс',
|
||||
'Install': 'Установить',
|
||||
'Installed applications': 'Установленные приложения',
|
||||
'Internal State': 'Внутренний статус',
|
||||
'Invalid Query': 'Неверный запрос',
|
||||
@@ -92,7 +106,7 @@
|
||||
'License for': 'Лицензия для',
|
||||
'Login': 'Логин',
|
||||
'Login to the Administrative Interface': 'Вход в интерфейс администратора',
|
||||
'Logout': 'Выход',
|
||||
'Logout': 'выход',
|
||||
'Lost Password': 'Забыли пароль',
|
||||
'Main Menu': 'Главное меню',
|
||||
'Match Pair': 'Найти пару',
|
||||
@@ -109,6 +123,9 @@
|
||||
'No databases in this application': 'В приложении нет базы данных',
|
||||
'Origin': 'Оригинал',
|
||||
'Original/Translation': 'Оригинал/Перевод',
|
||||
'Overwrite installed app': 'Переписать на установленное приложение',
|
||||
'Pack all': 'упаковать все',
|
||||
'Pack compiled': 'Архив скомпилирован',
|
||||
'Password': 'Пароль',
|
||||
'Peeking at file': 'Просмотр',
|
||||
'Plugin "%s" in application': 'Плагин "%s" в приложении',
|
||||
@@ -119,14 +136,19 @@
|
||||
'Record ID': 'ID записи',
|
||||
'Register': 'Зарегистрироваться',
|
||||
'Registration key': 'Ключ регистрации',
|
||||
'Reload routes': 'Reload routes',
|
||||
'Remove compiled': 'Удалить скомпилированное',
|
||||
'Reset Password key': 'Сброс пароля',
|
||||
'Resolve Conflict file': 'Решить конфликт в файле',
|
||||
'Role': 'Роль',
|
||||
'Rows in table': 'Строк в таблице',
|
||||
'Rows selected': 'Выбрано строк',
|
||||
'Running on %s': 'Running on %s',
|
||||
'Save via Ajax': 'Сохранить через Ajax',
|
||||
'Saved file hash:': 'Хэш сохраненного файла:',
|
||||
'Searching:': 'Поиск:',
|
||||
'Site': 'сайт',
|
||||
'Start wizard': 'запустить мастер',
|
||||
'Static files': 'Статические файлы',
|
||||
'Stylesheet': 'Таблицы стилей',
|
||||
'Sure you want to delete this object?': 'Действительно хотите удалить данный объект?',
|
||||
@@ -156,9 +178,11 @@
|
||||
'Unable to check for upgrades': 'Невозможно проверить обновления',
|
||||
'Unable to download': 'Невозможно загрузить',
|
||||
'Unable to download app': 'Невозможно загрузить',
|
||||
'Uninstall': 'Удалить',
|
||||
'Update:': 'Обновить:',
|
||||
'Upload & install packed application': 'Загрузить и установить приложение в архиве',
|
||||
'Upload a package:': 'Загрузить пакет:',
|
||||
'Upload and install packed application': 'Upload and install packed application',
|
||||
'Upload existing application': 'Загрузить существующее приложение',
|
||||
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Используйте (...)&(...) для AND, (...)|(...) для OR, и ~(...) для NOT при создании сложных запросов.',
|
||||
'Use an url:': 'Используйте url:',
|
||||
@@ -174,7 +198,6 @@
|
||||
'You are successfully running web2py': 'Вы успешно запустили web2by',
|
||||
'You can modify this application and adapt it to your needs': 'Вы можете изменить это приложение и подогнать под свои нужды',
|
||||
'You visited the url': 'Вы посетили URL',
|
||||
'About': 'О',
|
||||
'additional code for your application': 'добавочный код для вашего приложения',
|
||||
'admin disabled because no admin password': 'админка отключена, потому что отсутствует пароль администратора',
|
||||
'admin disabled because not supported on google apps engine': 'админка отключена, т.к. не поддерживается на google app engine',
|
||||
@@ -195,20 +218,15 @@
|
||||
'call': 'вызов',
|
||||
'cannot create file': 'Невозможно создать файл',
|
||||
'cannot upload file "%(filename)s"': 'Невозможно загрузить файл "%(filename)s"',
|
||||
'Change admin password': 'Изменить пароль администратора',
|
||||
'change password': 'изменить пароль',
|
||||
'check all': 'проверить все',
|
||||
'Check for upgrades': 'проверить обновления',
|
||||
'Clean': 'Очистить',
|
||||
'click here for online examples': 'нажмите здесь для онлайн примеров',
|
||||
'click here for the administrative interface': 'нажмите здесь для интерфейса администратора',
|
||||
'click to check for upgrades': 'нажмите для проверки обновления',
|
||||
'code': 'код',
|
||||
'collapse/expand all': 'свернуть/развернуть все',
|
||||
'Compile': 'Компилировать',
|
||||
'compiled application removed': 'скомпилированное приложение удалено',
|
||||
'controllers': 'контроллеры',
|
||||
'Create': 'Создать',
|
||||
'create file with filename:': 'Создать файл с названием:',
|
||||
'create new application:': 'создать новое приложение:',
|
||||
'created by': 'создано',
|
||||
@@ -225,18 +243,15 @@
|
||||
'delete': 'удалить',
|
||||
'delete all checked': 'удалить выбранные',
|
||||
'delete plugin': 'удалить плагин',
|
||||
'Deploy': 'Развернуть',
|
||||
'design': 'дизайн',
|
||||
'direction: ltr': 'направление: ltr',
|
||||
'documentation': 'документация',
|
||||
'done!': 'выполнено!',
|
||||
'download layouts': 'загрузить шаблоны',
|
||||
'download plugins': 'загрузить плагины',
|
||||
'Edit': 'Правка',
|
||||
'edit controller': 'правка контроллера',
|
||||
'edit profile': 'правка профиля',
|
||||
'edit views:': 'правка видов:',
|
||||
'Errors': 'Ошибка',
|
||||
'escape': 'escape',
|
||||
'export as csv file': 'Экспорт в CSV',
|
||||
'exposes': 'открывает',
|
||||
@@ -253,13 +268,11 @@
|
||||
'file saved on %s': 'файл сохранен %s',
|
||||
'files': 'файлы',
|
||||
'filter': 'фильтр',
|
||||
'Help': 'Помощь',
|
||||
'htmledit': 'htmledit',
|
||||
'includes': 'включает',
|
||||
'index': 'index',
|
||||
'insert new': 'вставить новый',
|
||||
'insert new %s': 'вставить новый %s',
|
||||
'Install': 'Установить',
|
||||
'internal error': 'внутренняя ошибка',
|
||||
'invalid password': 'неверный пароль',
|
||||
'invalid request': 'неверный запрос',
|
||||
@@ -270,7 +283,6 @@
|
||||
'loading...': 'загрузка...',
|
||||
'located in the file': 'расположенный в файле',
|
||||
'login': 'логин',
|
||||
'Logout': 'выход',
|
||||
'lost password?': 'Пароль утерен?',
|
||||
'merge': 'объединить',
|
||||
'models': 'модели',
|
||||
@@ -281,9 +293,6 @@
|
||||
'or import from csv file': 'или испорт из cvs файла',
|
||||
'or provide app url:': 'или URL приложения:',
|
||||
'or provide application url:': 'или URL приложения:',
|
||||
'Overwrite installed app': 'Переписать на установленное приложение',
|
||||
'Pack all': 'упаковать все',
|
||||
'Pack compiled': 'Архив скомпилирован',
|
||||
'pack plugin': 'Упаковать плагин',
|
||||
'please wait!': 'подождите, пожалуйста!',
|
||||
'plugins': 'плагины',
|
||||
@@ -292,16 +301,13 @@
|
||||
'record does not exist': 'запись не существует',
|
||||
'record id': 'id записи',
|
||||
'register': 'зарегистрироваться',
|
||||
'Remove compiled': 'Удалить скомпилированное',
|
||||
'restore': 'восстановить',
|
||||
'revert': 'откатиться',
|
||||
'save': 'сохранить',
|
||||
'selected': 'выбрано',
|
||||
'session expired': 'сессия истекла',
|
||||
'shell': 'shell',
|
||||
'Site': 'сайт',
|
||||
'some files could not be removed': 'некоторые файлы нельзя удалить',
|
||||
'Start wizard': 'запустить мастер',
|
||||
'state': 'статус',
|
||||
'static': 'статичные файлы',
|
||||
'submit': 'Отправить',
|
||||
@@ -324,7 +330,6 @@
|
||||
'unable to parse csv file': 'невозможно разобрать файл csv',
|
||||
'unable to uninstall "%s"': 'невозможно удалить "%s"',
|
||||
'uncheck all': 'снять выбор всего',
|
||||
'Uninstall': 'Удалить',
|
||||
'update': 'обновить',
|
||||
'update all languages': 'обновить все языки',
|
||||
'upgrade web2py now': 'обновить web2py сейчас',
|
||||
@@ -341,5 +346,3 @@
|
||||
'web2py is up to date': 'web2py обновлен',
|
||||
'xml': 'xml',
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
'Application name:': 'Application name:',
|
||||
'Are you sure you want to delete file "%s"?': '你真想删除文件"%s"?',
|
||||
'Are you sure you want to delete plugin "%s"?': 'Are you sure you want to delete plugin "%s"?',
|
||||
'Are you sure you want to delete this object?': 'Are you sure you want to delete this object?',
|
||||
'Are you sure you want to uninstall application "%s"': '你真确定要卸载应用 "%s"',
|
||||
'Are you sure you want to uninstall application "%s"?': '你真确定要卸载应用 "%s" ?',
|
||||
'Are you sure you want to upgrade web2py now?': 'Are you sure you want to upgrade web2py now?',
|
||||
@@ -34,23 +35,33 @@
|
||||
'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': '无法编译: 应用中有错误,请修订后再试.',
|
||||
'Cannot compile: there are errors in your app:': 'Cannot compile: there are errors in your app:',
|
||||
'Change Password': '修改密码',
|
||||
'Change admin password': 'change admin password',
|
||||
'Check for upgrades': 'check for upgrades',
|
||||
'Check to delete': '检验删除',
|
||||
'Checking for upgrades...': '是否有升级版本检查ing...',
|
||||
'Clean': '清除',
|
||||
'Client IP': '客户端IP',
|
||||
'Compile': '编译',
|
||||
'Controllers': '控制器s',
|
||||
'Create': 'create',
|
||||
'Create new simple application': '创建一个新应用',
|
||||
'Current request': '当前请求',
|
||||
'Current response': '当前返回',
|
||||
'Current session': '当前会话',
|
||||
'DESIGN': '设计',
|
||||
'Date and Time': '时间日期',
|
||||
'Debug': 'Debug',
|
||||
'Delete': '删除',
|
||||
'Delete:': '删除:',
|
||||
'Deploy': 'deploy',
|
||||
'Deploy on Google App Engine': '部署到GAE',
|
||||
'Deploy to OpenShift': 'Deploy to OpenShift',
|
||||
'Description': '描述',
|
||||
'Design for': '设计:',
|
||||
'Disable': 'Disable',
|
||||
'E-mail': '邮箱:',
|
||||
'EDIT': '编辑',
|
||||
'Edit': '修改',
|
||||
'Edit Profile': '修订配置',
|
||||
'Edit application': '修订应用',
|
||||
'Edit current record': '修订当前记录',
|
||||
@@ -59,13 +70,17 @@
|
||||
'Editing file "%s"': '修订文件 %s',
|
||||
'Enterprise Web Framework': '强悍的网络开发框架',
|
||||
'Error logs for "%(app)s"': '"%(app)s" 的错误日志',
|
||||
'Errors': '错误',
|
||||
'Exception instance attributes': 'Exception instance attributes',
|
||||
'First name': '姓',
|
||||
'Functions with no doctests will result in [passed] tests.': '',
|
||||
'Get from URL:': 'Get from URL:',
|
||||
'Group ID': '组ID',
|
||||
'Hello World': '道可道,非常道;名可名,非常名',
|
||||
'Help': '帮助',
|
||||
'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.': 'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.',
|
||||
'Import/Export': '导入/导出',
|
||||
'Install': 'install',
|
||||
'Installed applications': '已安装的应用',
|
||||
'Internal State': '内部状态',
|
||||
'Invalid Query': '无效查询',
|
||||
@@ -90,7 +105,10 @@
|
||||
'No databases in this application': '这应用没有数据库',
|
||||
'Origin': '起源',
|
||||
'Original/Translation': '原始文件/翻译文件',
|
||||
'Overwrite installed app': 'overwrite installed app',
|
||||
'PAM authenticated user, cannot change password here': 'PAM authenticated user, cannot change password here',
|
||||
'Pack all': '全部打包',
|
||||
'Pack compiled': '包编译完',
|
||||
'Password': '密码',
|
||||
'Peeking at file': '选个文件',
|
||||
'Plugin "%s" in application': 'Plugin "%s" in application',
|
||||
@@ -100,11 +118,16 @@
|
||||
'Record ID': '记录ID',
|
||||
'Register': '注册',
|
||||
'Registration key': '注册密匙',
|
||||
'Reload routes': 'Reload routes',
|
||||
'Remove compiled': '已移除',
|
||||
'Resolve Conflict file': '解决冲突文件',
|
||||
'Role': '角色',
|
||||
'Rows in table': '表行',
|
||||
'Rows selected': '行选择',
|
||||
'Running on %s': 'Running on %s',
|
||||
'Saved file hash:': '已存文件Hash:',
|
||||
'Site': '总站',
|
||||
'Start wizard': 'start wizard',
|
||||
'Static files': '静态文件',
|
||||
'Sure you want to delete this object?': '真的要删除这个对象?',
|
||||
'TM': '',
|
||||
@@ -126,9 +149,11 @@
|
||||
'Unable to download app': '无法下载应用',
|
||||
'Unable to download app because:': 'Unable to download app because:',
|
||||
'Unable to download because': 'Unable to download because',
|
||||
'Uninstall': '卸载',
|
||||
'Update:': '更新:',
|
||||
'Upload & install packed application': 'Upload & install packed application',
|
||||
'Upload a package:': 'Upload a package:',
|
||||
'Upload and install packed application': 'Upload and install packed application',
|
||||
'Upload existing application': '上传已有应用',
|
||||
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.',
|
||||
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': '',
|
||||
@@ -138,7 +163,6 @@
|
||||
'Views': '视图',
|
||||
'Welcome to web2py': '欢迎使用web2py',
|
||||
'YES': '是',
|
||||
'About': '关于',
|
||||
'additional code for your application': '给你的应用附加代码',
|
||||
'admin disabled because no admin password': '管理员需要设定密码,否则无法管理',
|
||||
'admin disabled because not supported on google app engine': '未支持GAE,无法管理',
|
||||
@@ -157,18 +181,13 @@
|
||||
'cache, errors and sessions cleaned': '缓存、错误、sesiones已被清空',
|
||||
'cannot create file': '无法创建文件',
|
||||
'cannot upload file "%(filename)s"': '无法上传文件 "%(filename)s"',
|
||||
'Change admin password': 'change admin password',
|
||||
'check all': '检查所有',
|
||||
'Check for upgrades': 'check for upgrades',
|
||||
'Clean': '清除',
|
||||
'click here for online examples': '猛击此处,查看在线实例',
|
||||
'click here for the administrative interface': '猛击此处,进入管理界面',
|
||||
'click to check for upgrades': '猛击查看是否有升级版本',
|
||||
'code': 'code',
|
||||
'Compile': '编译',
|
||||
'compiled application removed': '已编译应用已移走',
|
||||
'controllers': '控制器',
|
||||
'Create': 'create',
|
||||
'create file with filename:': '创建文件用这名:',
|
||||
'create new application:': '创建新应用:',
|
||||
'created by': '创建自',
|
||||
@@ -185,14 +204,11 @@
|
||||
'delete': '删除',
|
||||
'delete all checked': '删除所有选择的',
|
||||
'delete plugin': 'delete plugin',
|
||||
'Deploy': 'deploy',
|
||||
'design': '设计',
|
||||
'direction: ltr': 'direction: ltr',
|
||||
'done!': '搞定!',
|
||||
'Edit': '修改',
|
||||
'edit controller': '修订控制器',
|
||||
'edit views:': 'edit views:',
|
||||
'Errors': '错误',
|
||||
'export as csv file': '导出为CSV文件',
|
||||
'exposes': '暴露',
|
||||
'extends': '扩展',
|
||||
@@ -207,12 +223,10 @@
|
||||
'file does not exist': '文件并不存在',
|
||||
'file saved on %(time)s': '文件保存于 %(time)s',
|
||||
'file saved on %s': '文件保存在 %s',
|
||||
'Help': '帮助',
|
||||
'htmledit': 'html编辑',
|
||||
'includes': '包含',
|
||||
'insert new': '新插入',
|
||||
'insert new %s': '新插入 %s',
|
||||
'Install': 'install',
|
||||
'internal error': '内部错误',
|
||||
'invalid password': '无效密码',
|
||||
'invalid request': '无效请求',
|
||||
@@ -222,7 +236,6 @@
|
||||
'languages updated': '语言已被刷新',
|
||||
'loading...': '载入中...',
|
||||
'login': '登录',
|
||||
'Logout': '注销',
|
||||
'merge': '合并',
|
||||
'models': '模型s',
|
||||
'modules': '模块s',
|
||||
@@ -234,9 +247,6 @@
|
||||
'or import from csv file': '或者,从csv文件导入',
|
||||
'or provide app url:': 'or provide app url:',
|
||||
'or provide application url:': '或者,提供应用所在地址链接:',
|
||||
'Overwrite installed app': 'overwrite installed app',
|
||||
'Pack all': '全部打包',
|
||||
'Pack compiled': '包编译完',
|
||||
'pack plugin': 'pack plugin',
|
||||
'password changed': 'password changed',
|
||||
'plugin "%(plugin)s" deleted': 'plugin "%(plugin)s" deleted',
|
||||
@@ -244,16 +254,13 @@
|
||||
'record': 'record',
|
||||
'record does not exist': '记录并不存在',
|
||||
'record id': '记录ID',
|
||||
'Remove compiled': '已移除',
|
||||
'restore': '重存',
|
||||
'revert': '恢复',
|
||||
'save': '保存',
|
||||
'selected': '已选',
|
||||
'session expired': '会话过期',
|
||||
'shell': '',
|
||||
'Site': '总站',
|
||||
'some files could not be removed': '有些文件无法被移除',
|
||||
'Start wizard': 'start wizard',
|
||||
'state': '状态',
|
||||
'static': '静态文件',
|
||||
'submit': '提交',
|
||||
@@ -275,7 +282,6 @@
|
||||
'unable to uninstall "%s"': '无法卸载 "%s"',
|
||||
'unable to upgrade because "%s"': 'unable to upgrade because "%s"',
|
||||
'uncheck all': '反选全部',
|
||||
'Uninstall': '卸载',
|
||||
'update': '更新',
|
||||
'update all languages': '更新所有语言',
|
||||
'upgrade web2py now': 'upgrade web2py now',
|
||||
@@ -290,5 +296,3 @@
|
||||
'web2py is up to date': 'web2py现在已经是最新的版本了',
|
||||
'web2py upgraded; please restart it': 'web2py upgraded; please restart it',
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -28,17 +28,17 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="footer" class="fixed">
|
||||
{{=T('Powered by')}} {{=A('web2py', _href='http://www.web2py.com')}}™ {{=T('created by')}} Massimo Di Pierro ©2007-2011 -
|
||||
{{if hasattr(T,'get_possible_languages'):}}
|
||||
<span>
|
||||
{{=T('Admin language')}}
|
||||
<select name="adminlanguage" onchange="var date = new Date();cookieDate=date.setTime(date.getTime()+(100*24*60*60*1000));document.cookie='adminLanguage='+this.options[this.selectedIndex].value+'; expires='+cookieDate+'; path=/';window.location.reload()">
|
||||
{{for language in T.get_possible_languages():}}
|
||||
<option {{=T.accepted_language==language and 'selected' or ''}} >{{=language}}</option>
|
||||
{{pass}}
|
||||
</select>
|
||||
</span>
|
||||
{{pass}}
|
||||
{{=T('Powered by')}} {{=A('web2py', _href='http://www.web2py.com')}}™ {{=T('created by')}} Massimo Di Pierro ©2007-2012 -
|
||||
{{if hasattr(T,'get_possible_languages_info'):}}
|
||||
<span>
|
||||
{{=T('Admin language')}}
|
||||
<select name="adminlanguage" onchange="var date = new Date();cookieDate=date.setTime(date.getTime()+(100*24*60*60*1000));document.cookie='adminLanguage='+this.options[this.selectedIndex].id+'; expires='+cookieDate+'; path=/';window.location.reload()">
|
||||
{{for langinfo in sorted([(code,info[1]) for code,info in T.get_possible_languages_info().iteritems() if code != 'default']):}}
|
||||
<option {{=T.accepted_language==langinfo[0] and 'selected' or ''}} {{='id='+langinfo[0]}} >{{=langinfo[1]}}</option>
|
||||
{{pass}}
|
||||
</select>
|
||||
</span>
|
||||
{{pass}}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+269
-105
@@ -17,6 +17,8 @@ import copy_reg
|
||||
from fileutils import listdir
|
||||
import settings
|
||||
from cfs import getcfs
|
||||
from thread import allocate_lock
|
||||
from html import XML, xmlescape
|
||||
|
||||
__all__ = ['translator', 'findT', 'update_all_languages']
|
||||
|
||||
@@ -30,17 +32,86 @@ PY_STRING_LITERAL_RE = r'(?<=[^\w]T\()(?P<name>'\
|
||||
+ r'(?:"(?:[^"\\]|\\.)*"))'
|
||||
|
||||
regex_translate = re.compile(PY_STRING_LITERAL_RE, re.DOTALL)
|
||||
regex_param=re.compile(r'(?P<b>(?<!\\)(?:\\\\)*){(?P<s>.+?(?<!\\)(?:\\\\)*)}')
|
||||
|
||||
# patter for a valid accept_language
|
||||
|
||||
regex_language = \
|
||||
re.compile('^([a-zA-Z]{2})(\-[a-zA-Z]{2})?(\-[a-zA-Z]+)?$')
|
||||
regex_langfile = re.compile('^[a-zA-Z]{2}(-[a-zA-Z]{2})?\.py$')
|
||||
regex_langinfo = re.compile("^[^'\"]*['\"]([^'\"]*)['\"]\s*:\s*['\"]([^'\"]*)['\"].*$")
|
||||
|
||||
# cache of translated messages:
|
||||
# of structure:
|
||||
# { 'languages/xx.py':
|
||||
# ( {"def-message": "xx-message",
|
||||
# ...
|
||||
# "def-message": "xx-message"}, lock_object )
|
||||
# 'languages/yy.py': ( {dict}, lock_object )
|
||||
# ...
|
||||
# }
|
||||
tcache={}
|
||||
|
||||
def get_from_cache(cache, val, fun):
|
||||
lock=cache[1]
|
||||
lock.acquire()
|
||||
try:
|
||||
result=cache[0].get(val);
|
||||
finally:
|
||||
lock.release()
|
||||
if result:
|
||||
return result
|
||||
lock.acquire()
|
||||
try:
|
||||
result=cache[0].setdefault(val, fun())
|
||||
finally:
|
||||
lock.release()
|
||||
return result
|
||||
|
||||
def clear_cache(cache):
|
||||
cache[1].acquire()
|
||||
try:
|
||||
cache[0].clear();
|
||||
finally:
|
||||
cache[1].release()
|
||||
|
||||
|
||||
def lang_sampling(lang_tuple, langlist):
|
||||
""" search lang_tuple in langlist
|
||||
Args:
|
||||
lang_tuple (tuple of strings): ('aa'[[,'-bb'],'-cc'])
|
||||
langlist (list of strings): [available languages]
|
||||
Returns:
|
||||
language from langlist or None
|
||||
"""
|
||||
# step 1:
|
||||
# compare "aa-bb-cc" | "aa-bb" | "aa" from lang_tuple
|
||||
# with strings from langlist. Return appropriate string
|
||||
# from langlist:
|
||||
tries = range(len(lang_tuple),0,-1)
|
||||
for i in tries:
|
||||
language="".join(lang_tuple[:i])
|
||||
if language in langlist:
|
||||
return language
|
||||
# step 2 (if not found in step 1):
|
||||
# compare "aa-bb-cc" | "aa-bb" | "aa" from lang_tuple
|
||||
# with left part of a string from langlist. Return
|
||||
# appropriate string from langlist
|
||||
for i in tries:
|
||||
lang="".join(lang_tuple[:i])
|
||||
for language in langlist:
|
||||
if language.startswith(lang):
|
||||
return language
|
||||
return None
|
||||
|
||||
|
||||
def read_dict_aux(filename):
|
||||
fp = portalocker.LockedFile(filename, 'r')
|
||||
lang_text = fp.read().replace('\r\n', '\n')
|
||||
fp.close()
|
||||
# clear cache of translated messages:
|
||||
clear_cache(tcache.setdefault(filename, ({}, allocate_lock())))
|
||||
clear_cache(tcache.setdefault('@'+filename, ({}, allocate_lock())))
|
||||
if not lang_text.strip():
|
||||
return {}
|
||||
try:
|
||||
@@ -50,8 +121,75 @@ def read_dict_aux(filename):
|
||||
return {'__corrupted__':True}
|
||||
|
||||
def read_dict(filename):
|
||||
return getcfs('language:%s'%filename,filename,
|
||||
lambda filename=filename:read_dict_aux(filename))
|
||||
""" return dictionary with translation messages
|
||||
"""
|
||||
return getcfs('lang:'+filename, filename,
|
||||
lambda:read_dict_aux(filename))
|
||||
|
||||
|
||||
def get_lang_info(lang, langdir):
|
||||
"""retrieve lang information from *langdir*/*lang*.py file.
|
||||
Read few strings from lang.py file until keys !langname!,
|
||||
!langcode! or keys greater then '!*' were found
|
||||
|
||||
args:
|
||||
lang (str): lang-code or 'default'
|
||||
langdir (str): path to 'languages' directory in web2py app dir
|
||||
|
||||
returns:
|
||||
tuple(langcode, langname, langfile_mtime)
|
||||
e.g.: ('en', 'English', 1338549043.0)
|
||||
"""
|
||||
filename = os.path.join(langdir, lang+'.py')
|
||||
langcode=''
|
||||
langname=''
|
||||
f = portalocker.LockedFile(filename, 'r')
|
||||
try:
|
||||
while not (langcode and langname):
|
||||
line = f.readline()
|
||||
if not line:
|
||||
break
|
||||
match=regex_langinfo.match(line)
|
||||
if match:
|
||||
if match.group(1) == '!langname!':
|
||||
langname=match.group(2)
|
||||
elif match.group(1) == '!langcode!':
|
||||
langcode=match.group(2)
|
||||
elif match.group(1)[0] > '!':
|
||||
break
|
||||
finally:
|
||||
f.close()
|
||||
if not langcode:
|
||||
langcode = lang if lang != 'default' else 'en'
|
||||
return langcode, langname or langcode, os.stat(filename).st_mtime
|
||||
|
||||
def read_possible_languages_aux(langdir, item=None):
|
||||
if not item: item = {}
|
||||
langs = {}
|
||||
# scan languages directory for langfiles:
|
||||
for langfile in (listdir(langdir, regex_langfile) +
|
||||
listdir(langdir, '^default\.py$')):
|
||||
lang=langfile[:-3]
|
||||
if (lang in item and item[lang][2] ==
|
||||
os.stat(os.path.join(langdir, langfile)).st_mtime):
|
||||
# if langfile's mtime wasn't changed - use previous value:
|
||||
langs[lang]=item[lang]
|
||||
else:
|
||||
# otherwise, reread langfile:
|
||||
langs[lang]=get_lang_info(lang, langdir)
|
||||
if 'default' not in langs:
|
||||
# if default.py is not found, add default value:
|
||||
langs['default'] = ('en', 'English', 0)
|
||||
deflang=langs['default']
|
||||
if deflang[0] not in langs:
|
||||
# create language from default.py:
|
||||
langs[deflang[0]] = (deflang[0], deflang[1], 0)
|
||||
return langs
|
||||
|
||||
def read_possible_languages(path):
|
||||
lang_path = os.path.join(path, 'languages')
|
||||
return getcfs('langs:'+path.rstrip(os.path.sep), lang_path,
|
||||
lambda: read_possible_languages_aux(lang_path))
|
||||
|
||||
def utf8_repr(s):
|
||||
r''' # note that we use raw strings to avoid having to use double back slashes below
|
||||
@@ -108,26 +246,16 @@ class lazyT(object):
|
||||
"""
|
||||
never to be called explicitly, returned by translator.__call__
|
||||
"""
|
||||
m = s = T = None
|
||||
|
||||
m = None
|
||||
s = None
|
||||
T = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message,
|
||||
symbols = {},
|
||||
T = None,
|
||||
):
|
||||
self.m = message
|
||||
self.s = symbols
|
||||
self.T = T
|
||||
def __init__(self, message, symbols = {}, T = None, filter=None):
|
||||
self.m, self.s, self.T, self.f = message, symbols, T, filter
|
||||
|
||||
def __repr__(self):
|
||||
return "<lazyT %s>" % (repr(str(self.m)), )
|
||||
|
||||
def __str__(self):
|
||||
return self.T.translate(self.m, self.s)
|
||||
return self.T.translate(self.m, self.s, self.f)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.T.translate(self.m, self.s) == other
|
||||
@@ -175,7 +303,7 @@ class lazyT(object):
|
||||
return str(self)
|
||||
|
||||
def __mod__(self, symbols):
|
||||
return self.T.translate(self.m, symbols)
|
||||
return self.T.translate(self.m, symbols, filter=self.f)
|
||||
|
||||
|
||||
class translator(object):
|
||||
@@ -192,65 +320,83 @@ class translator(object):
|
||||
T(\"Hello World\") # translates \"Hello World\" using the selected file
|
||||
|
||||
notice 1: there is no need to force since, by default, T uses
|
||||
accept_language to determine a translation file.
|
||||
http_accept_language to determine a translation file.
|
||||
|
||||
notice 2: en and en-en are considered different languages!
|
||||
|
||||
notice 3: if language xx-yy is not found force() probes other similar
|
||||
languages using such algorithm: xx-yy.py -> xx.py -> xx-yy*.py -> xx*.py
|
||||
"""
|
||||
|
||||
def __init__(self, request):
|
||||
global tcache
|
||||
self.request = request
|
||||
self.folder = request.folder
|
||||
self.current_languages = ['en']
|
||||
self.accepted_language = None
|
||||
self.language_file = None
|
||||
default = os.path.join(self.folder,'languages','default.py')
|
||||
if os.path.exists(default):
|
||||
self.default_language_file = default
|
||||
self.default_t = read_dict(default)
|
||||
else: # languages/default.py is not found
|
||||
self.default_language_file = os.path.join(self.folder,'languages','')
|
||||
self.default_t = {}
|
||||
self.cache = tcache.setdefault(self.default_language_file, ({}, allocate_lock()))
|
||||
self.mcache = tcache.setdefault('@'+self.default_language_file, ({}, allocate_lock()))
|
||||
self.current_languages = [self.get_possible_languages_info('default')[0]]
|
||||
self.accepted_language = None # filed in self.force()
|
||||
self.language_file = None # filed in self.force()
|
||||
self.http_accept_language = request.env.http_accept_language
|
||||
self.requested_languages = self.force(self.http_accept_language)
|
||||
self.lazy = True
|
||||
self.otherTs = {}
|
||||
|
||||
def get_possible_languages_info(self, lang=None):
|
||||
""" return info for selected language or dictionary with all
|
||||
possible languages info from APP/languages/*.py
|
||||
args:
|
||||
*lang* (str): language
|
||||
returns:
|
||||
if *lang* is defined:
|
||||
return tuple(langcode, langname, langfile_mtime) or None
|
||||
|
||||
if *lang* is NOT defined:
|
||||
returns dictionary with all possible languages:
|
||||
{ langcode(from filename): ( langcode(from !langcode! key),
|
||||
langname(from !langname! key),
|
||||
langfile_mtime ) }
|
||||
"""
|
||||
if lang:
|
||||
return read_possible_languages(self.folder).get(lang)
|
||||
return read_possible_languages(self.folder)
|
||||
|
||||
def get_possible_languages(self):
|
||||
possible_languages = [lang for lang in self.current_languages]
|
||||
file_ending = re.compile("\.py$")
|
||||
for langfile in os.listdir(os.path.join(self.folder,'languages')):
|
||||
if file_ending.search(langfile):
|
||||
possible_languages.append(file_ending.sub('',langfile))
|
||||
return possible_languages
|
||||
""" get list of all possible languages for current applications """
|
||||
return sorted( set(lang for lang in
|
||||
read_possible_languages(self.folder).iterkeys()
|
||||
if lang != 'default')
|
||||
| set(self.current_languages))
|
||||
|
||||
def set_current_languages(self, *languages):
|
||||
""" set current AKA "default" languages
|
||||
setting one of this languages makes force() function
|
||||
turn translation off to use default language
|
||||
"""
|
||||
if len(languages) == 1 and isinstance(languages[0], (tuple, list)):
|
||||
languages = languages[0]
|
||||
self.current_languages = languages
|
||||
self.force(self.http_accept_language)
|
||||
|
||||
def force(self, *languages):
|
||||
def lang_sampling(lang_tuple, langlist):
|
||||
""" search lang_tuple in langlist
|
||||
Args:
|
||||
lang_tuple (tuple of strings): ('aa'[[,'-bb'],'-cc'])
|
||||
langlist (list of strings): [available languages]
|
||||
Returns:
|
||||
language from langlist or None
|
||||
"""
|
||||
# step 1:
|
||||
# compare "aa-bb-cc" | "aa-bb" | "aa" from lang_tuple
|
||||
# with strings from langlist. Return appropriate string
|
||||
# from langlist:
|
||||
tries = range(len(lang_tuple),0,-1)
|
||||
for i in tries:
|
||||
language="".join(lang_tuple[:i])
|
||||
if language in langlist:
|
||||
return language
|
||||
# step 2 (if not found in step 1):
|
||||
# compare "aa-bb-cc" | "aa-bb" | "aa" from lang_tuple
|
||||
# with left part of a string from langlist. Return
|
||||
# appropriate string from langlist
|
||||
for i in tries:
|
||||
lang="".join(lang_tuple[:i])
|
||||
for language in langlist:
|
||||
if language.startswith(lang):
|
||||
return language
|
||||
return None
|
||||
""" select language(s) for translation
|
||||
|
||||
if a list of languages is passed as a parameter,
|
||||
first language from this list that matches the ones
|
||||
from the possible_languages dictionary will be
|
||||
selected
|
||||
|
||||
default language will be selected if none
|
||||
of them matches possible_languages.
|
||||
"""
|
||||
global tcache
|
||||
if not languages or languages[0] is None:
|
||||
languages = []
|
||||
if len(languages) == 1 and isinstance(languages[0], (str, unicode)):
|
||||
@@ -260,7 +406,8 @@ class translator(object):
|
||||
if isinstance(languages, (str, unicode)):
|
||||
parts = languages.split(';')
|
||||
languages = []
|
||||
[languages.extend(al.split(',')) for al in parts]
|
||||
for al in parts:
|
||||
languages.extend(al.split(','))
|
||||
|
||||
possible_languages = self.get_possible_languages()
|
||||
for language in languages:
|
||||
@@ -276,79 +423,94 @@ class translator(object):
|
||||
break
|
||||
language = lang_sampling(match_language, possible_languages)
|
||||
if language:
|
||||
filename = os.path.join(
|
||||
self.folder,'languages/',language + '.py')
|
||||
if os.path.exists(filename):
|
||||
self.language_file = os.path.join(self.folder,
|
||||
'languages',
|
||||
language + '.py')
|
||||
if os.path.exists(self.language_file):
|
||||
self.t = read_dict(self.language_file)
|
||||
self.accepted_language = language
|
||||
self.language_file = filename
|
||||
self.t = read_dict(filename)
|
||||
self.cache = tcache.setdefault(self.language_file, ({},allocate_lock()))
|
||||
self.mcache = tcache.setdefault('@'+self.language_file, ({},allocate_lock()))
|
||||
return languages
|
||||
self.language_file = None
|
||||
self.t = {} # ## no language by default
|
||||
self.language_file = self.default_language_file
|
||||
self.cache = tcache[self.language_file]
|
||||
self.mcache = tcache['@'+self.language_file]
|
||||
self.t = self.default_t
|
||||
return languages
|
||||
|
||||
def __call__(self, message, symbols={}, language=None, lazy=None):
|
||||
if lazy is None:
|
||||
lazy = self.lazy
|
||||
if not language:
|
||||
if lazy:
|
||||
return lazyT(message, symbols, self)
|
||||
else:
|
||||
return self.translate(message, symbols)
|
||||
else:
|
||||
try:
|
||||
otherT = self.otherTs[language]
|
||||
except KeyError:
|
||||
otherT = self.otherTs[language] = translator(self.request)
|
||||
otherT.force(language)
|
||||
return otherT(message, symbols, lazy=lazy)
|
||||
def __call__(self, message, symbols={}, language=None, lazy=None, filter=None):
|
||||
""" get cached translated plain text message with inserted
|
||||
parameters(symbols)
|
||||
|
||||
def translate(self, message, symbols):
|
||||
if lazy==True lazyT object is returned
|
||||
"""
|
||||
lazy = lazy or self.lazy
|
||||
if not language and lazy:
|
||||
return lazyT(message, symbols, self, filter=filter)
|
||||
elif not language:
|
||||
return self.translate(message, symbols, filter=filter)
|
||||
elif language in self.otherTs:
|
||||
otherT = self.otherTs[language]
|
||||
else:
|
||||
otherT = self.otherTs[language] = translator(self.request)
|
||||
otherT.force(language)
|
||||
return otherT(message, symbols, lazy=lazy, filter=filter)
|
||||
|
||||
|
||||
def get_t(self, message, filter=None, startwith_templ='##'):
|
||||
"""
|
||||
user ## to add a comment into a translation string
|
||||
the comment can be useful do discriminate different possible
|
||||
translations for the same string (for example different locations)
|
||||
|
||||
T(' hello world ') -> ' hello world '
|
||||
T(' hello world ## token') -> 'hello world'
|
||||
T('hello ## world ## token') -> 'hello ## world'
|
||||
T(' hello world ## token') -> ' hello world '
|
||||
T('hello ## world## token') -> 'hello ## world'
|
||||
|
||||
the ## notation is ignored in multiline strings and strings that
|
||||
start with ##. this is to allow markmin syntax to be translated
|
||||
"""
|
||||
#for some reason languages.py gets executed before gaehandler.py
|
||||
# is able to set web2py_runtime_gae, so re-check here
|
||||
is_gae = settings.global_settings.web2py_runtime_gae
|
||||
if not message.startswith('#') and not '\n' in message:
|
||||
tokens = message.rsplit('##', 1)
|
||||
else:
|
||||
# this allows markmin syntax in translations
|
||||
tokens = [message]
|
||||
if len(tokens) == 2:
|
||||
tokens[0] = tokens[0].strip()
|
||||
message = tokens[0] + '##' + tokens[1].strip()
|
||||
mt = self.t.get(message, None)
|
||||
if mt is None:
|
||||
self.t[message] = mt = tokens[0]
|
||||
if self.language_file and not is_gae:
|
||||
if not message.startswith(startwith_templ) and not '\n' in message:
|
||||
tokens = message.rsplit('##', 1)
|
||||
else:
|
||||
# this allows markmin syntax in translations
|
||||
tokens = [message]
|
||||
self.t[message] = mt = self.default_t.get(message, tokens[0])
|
||||
if (self.language_file != self.default_language_file and not is_gae):
|
||||
write_dict(self.language_file, self.t)
|
||||
if symbols or symbols == 0 or symbols == "":
|
||||
return mt % symbols
|
||||
if filter:
|
||||
mt = filter(mt)
|
||||
return mt
|
||||
|
||||
def params_substitution(self, message, symbols):
|
||||
""" substitute parameters from symbols into message using %.
|
||||
also parse %%{} placeholders for plural-forms processing.
|
||||
returns: string with parameters
|
||||
"""
|
||||
return message % symbols
|
||||
|
||||
def findT(path, language='en-us'):
|
||||
def translate(self, message, symbols, filter=None):
|
||||
""" get cached translated message with inserted parameters(symbols) """
|
||||
message = get_from_cache(self.cache, message,
|
||||
lambda: self.get_t(message,filter))
|
||||
if symbols or symbols == 0 or symbols == "":
|
||||
return self.params_substitution(message, symbols)
|
||||
return message
|
||||
|
||||
def findT(path, language='en'):
|
||||
"""
|
||||
must be run by the admin app
|
||||
"""
|
||||
filename = os.path.join(path, 'languages', '%s.py' % language)
|
||||
filename = os.path.join(path, 'languages', language +'.py')
|
||||
sentences = read_dict(filename)
|
||||
mp = os.path.join(path, 'models')
|
||||
cp = os.path.join(path, 'controllers')
|
||||
vp = os.path.join(path, 'views')
|
||||
mop = os.path.join(path, 'modules')
|
||||
for file in listdir(mp, '.+\.py', 0) + listdir(cp, '.+\.py', 0)\
|
||||
+ listdir(vp, '.+\.html', 0) + listdir(mop, '.+\.py', 0):
|
||||
for file in listdir(mp, '^.+\.py$', 0) + listdir(cp, '^.+\.py$', 0)\
|
||||
+ listdir(vp, '^.+\.html$', 0) + listdir(mop, '^.+\.py$', 0):
|
||||
fp = portalocker.LockedFile(file, 'r')
|
||||
data = fp.read()
|
||||
fp.close()
|
||||
@@ -367,6 +529,12 @@ def findT(path, language='en-us'):
|
||||
sentences[message] = message
|
||||
except:
|
||||
pass
|
||||
if not '!langcode!' in sentences:
|
||||
sentences['!langcode!'] = ('en' if language in ('default', 'en')
|
||||
else language)
|
||||
if not '!langname!' in sentences:
|
||||
sentences['!langname!'] = ('English' if language in ('default', 'en')
|
||||
else sentences['!langcode!'])
|
||||
write_dict(filename, sentences)
|
||||
|
||||
### important to allow safe session.flash=T(....)
|
||||
@@ -376,9 +544,10 @@ def lazyT_pickle(data):
|
||||
return lazyT_unpickle, (marshal.dumps(str(data)),)
|
||||
copy_reg.pickle(lazyT, lazyT_pickle, lazyT_unpickle)
|
||||
|
||||
|
||||
def update_all_languages(application_path):
|
||||
path = os.path.join(application_path, 'languages/')
|
||||
for language in listdir(path, '^\w+(\-\w+)?\.py$'):
|
||||
for language in listdir(path, regex_langfile):
|
||||
findT(application_path, language[:-3])
|
||||
|
||||
|
||||
@@ -386,8 +555,3 @@ if __name__ == '__main__':
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user