Просмотр исходного кода

Merge pull request #2412 from rs-ink/master

I18N verification extension and auto deep merge to zh-CN
Yuki Takei 5 лет назад
Родитель
Сommit
2ae74c8199
29 измененных файлов с 2098 добавлено и 118 удалено
  1. 3 0
      package.json
  2. 3 0
      resource/locales/en-US/_conf.json
  3. 3 0
      resource/locales/ja/_conf.json
  4. 3 0
      resource/locales/zh-CN/_conf.json
  5. 318 0
      resource/locales/zh-CN/admin/admin.json
  6. 14 0
      resource/locales/zh-CN/admin/userInvitation.txt
  7. 21 0
      resource/locales/zh-CN/admin/userWaitingActivation.txt
  8. 9 0
      resource/locales/zh-CN/notifications/comment.txt
  9. 5 0
      resource/locales/zh-CN/notifications/pageCreate.txt
  10. 5 0
      resource/locales/zh-CN/notifications/pageDelete.txt
  11. 5 0
      resource/locales/zh-CN/notifications/pageEdit.txt
  12. 5 0
      resource/locales/zh-CN/notifications/pageLike.txt
  13. 5 0
      resource/locales/zh-CN/notifications/pageMove.txt
  14. 253 0
      resource/locales/zh-CN/sandbox-bootstrap4.md
  15. 7 0
      resource/locales/zh-CN/sandbox-diagrams.md
  16. 71 0
      resource/locales/zh-CN/sandbox-math.md
  17. 457 0
      resource/locales/zh-CN/sandbox.md
  18. 700 0
      resource/locales/zh-CN/translation.json
  19. 27 0
      resource/locales/zh-CN/welcome.md
  20. 9 0
      rs-i18n.env
  21. 40 36
      src/client/js/components/Admin/App/AppSetting.jsx
  22. 21 30
      src/client/js/components/InstallerForm.jsx
  23. 18 23
      src/client/js/components/Me/BasicInfoSettings.jsx
  24. 1 1
      src/client/js/services/AppContainer.js
  25. 5 0
      src/server/crowi/index.js
  26. 1 1
      src/server/models/user.js
  27. 1 1
      src/server/routes/apiv3/app-settings.js
  28. 16 23
      src/server/routes/apiv3/personal-setting.js
  29. 72 3
      yarn.lock

+ 3 - 0
package.json

@@ -54,6 +54,8 @@
     "preserver:prod": "npm run migrate",
     "prestart": "npm run build:prod",
     "resource": "node bin/download-cdn-resources.js",
+    "translations": "rs-i18n -lan zh-CN -t",
+    "i18n-json-merge:noTran": "rs-i18n -lan zh-CN",
     "server:nolazy": "env-cmd -f config/env.dev.js node-dev --nolazy --inspect src/server/app.js",
     "server:dev": "env-cmd -f config/env.dev.js node-dev --inspect src/server/app.js",
     "server:prod:ci": "npm run server:prod -- --ci",
@@ -232,6 +234,7 @@
     "reactstrap": "^8.0.1",
     "replacestream": "^4.0.3",
     "reveal.js": "^3.5.0",
+    "rs-i18n": "^0.0.9",
     "sass-loader": "^8.0.0",
     "simple-load-script": "^1.0.2",
     "socket.io-client": "^2.0.3",

+ 3 - 0
resource/locales/en-US/_conf.json

@@ -0,0 +1,3 @@
+{
+  "name": "English"
+}

+ 3 - 0
resource/locales/ja/_conf.json

@@ -0,0 +1,3 @@
+{
+  "name": "日本語"
+}

+ 3 - 0
resource/locales/zh-CN/_conf.json

@@ -0,0 +1,3 @@
+{
+  "name": "简体中文"
+}

+ 318 - 0
resource/locales/zh-CN/admin/admin.json

@@ -0,0 +1,318 @@
+{
+	"admin_top": {
+		"management_wiki": "管理Wiki",
+		"system_information": "系统信息",
+		"wiki_administrator": "只有wiki管理员可以访问此页",
+		"assign_administrator": "您可以使用“授予管理员访问权限”按钮在“用户管理”页上将所选用户指定为wiki管理员",
+		"list_of_installed_plugins": "已安装插件列表",
+		"package_name": "包名称",
+		"specified_version": "指定版本",
+		"installed_version": "已安装版本",
+		"list_of_env_vars": "环境变量列表",
+		"env_var_priority": "对于安全性以外的环境变量,优先获取数据库的值。",
+		"about_security": "检查安全环境变量的<a href='/admin/security'>安全设置</a>。"
+	},
+	"app_setting": {
+		"site_name": "网站名称 ",
+		"sitename_change": "您可以更改用于标题和HTML标题的网站名称。",
+		"header_content": "此处输入的内容将显示在标题等中。",
+		"site_url_desc": "用于网站URL设置。",
+		"site_url_warn": "某些功能不起作用,因为未设置网站URL。",
+		"siteurl_help": "网站完整URL起始于 <code>http://</code> or <code>https://</code>.",
+		"confidential_name": "内部名称",
+		"confidential_example": "ex):仅供内部使用",
+		"default_language": "新用户的默认语言",
+		"file_uploading": "文件上传",
+		"enable_files_except_image": "启用此选项将允许上传任何文件类型。如果没有此选项,则仅支持图像文件上载。",
+		"attach_enable": "如果启用此选项,则可以附加图像文件以外的文件。",
+		"update": "更新",
+		"mail_settings": "邮件设置",
+		"smtp_used": "如果您有SMTP设置,将使用它。",
+		"smtp_but_aws": "如果您没有SMTP设置,但有AWS设置,则电子邮件将由SES发送。",
+		"neihter_of": "如果两者都未选中,则不会发送电子邮件。",
+		"from_e-mail_address": "From e-mail address",
+		"smtp_settings": "SMTP 设置",
+		"host": "服务器",
+		"port": "端口号",
+		"user": "用户名",
+		"aws_settings": "AWS设置",
+		"aws_access": "这是用于AWS设置的。如果您完成了AWS设置,文件上传功能,个人资料图片功能等将被启用。",
+		"no_smtp_setting": "如果您没有SMTP设置,电子邮件将通过SES发送。您需要从电子邮件地址和生产设置进行验证。",
+		"change_setting": "注意:如果你更改此设置未完成,您将无法访问迄今为止上传的文件。",
+		"region": "Region",
+		"bucket_name": "Bucket name",
+		"custom_endpoint": "Custom endpoint",
+		"custom_endpoint_change": "输入对象存储服务(如MinIO)端点的URL,MinIO具有与S3兼容的API。如果为空,则使用Amazon S3。",
+		"plugin_settings": "插件设置",
+		"enable_plugin_loading": "启用插件加载",
+		"load_plugins": "加载插件",
+		"enable": "启用",
+		"disable": "停用",
+		"use_env_var_if_empty": "如果数据库中的值为空,则环境变量的值 <cod>{{variable}}</code> 启用。"
+	},
+	"markdown_setting": {
+		"lineBreak_header": "换行设置",
+		"lineBreak_desc": "您可以更改换行设置。",
+		"lineBreak_options": {
+			"enable_lineBreak": "启用换行符",
+			"enable_lineBreak_desc": "HTML中将文本页中的换行符转换为<code>&lt;br&gt;</code>",
+			"enable_lineBreak_for_comment": "注释中启用换行符",
+			"enable_lineBreak_for_comment_desc": "HTML中将注释中的换行符转换为<code>&lt;br&gt;</code>"
+		},
+		"presentation_header": "演示文稿设置",
+		"presentation_desc": "您可以更改演示文稿设置。",
+		"presentation_options": {
+			"page_break_setting": "分页设置",
+			"preset_one_separator": "预设 1",
+			"preset_one_separator_desc": "3 空行",
+			"preset_one_separator_value": "\\n\\n\\n",
+			"preset_two_separator": "预设 2",
+			"preset_two_separator_desc": "5 连字符",
+			"preset_two_separator_value": "-----",
+			"custom_separator": "自定义",
+			"custom_separator_desc": "正则表达式"
+		},
+		"xss_header": "阻止XSS(跨站点脚本)设置",
+		"xss_desc": "您可以更改标记文本中HTML标记的处理方式。",
+		"xss_options": {
+			"enable_xss_prevention": "启用XSS预防",
+			"remove_all_tags": "删除所有标记",
+			"remove_all_tags_desc": "Stripe all HTML tags and attributes",
+			"recommended_setting": "推荐设置",
+			"custom_whitelist": "自定义白名单",
+			"tag_names": "标记名",
+			"tag_attributes": "标记属性",
+			"import_recommended": "导入建议 {{target}}"
+		}
+	},
+	"customize_setting": {
+		"recommended": "推荐",
+		"layout": "布局",
+		"theme": "主体",
+		"layout_desc": {
+			"growi_title": "简约",
+			"growi_text1": "全屏布局 窄边距/填充",
+			"growi_text2": "页面底部显示和发布评论",
+			"growi_text3": "附目录",
+			"kibela_title": "清晰",
+			"kibela_text1": "内容居中对齐",
+			"kibela_text2": "在页面底部显示和发布评论",
+			"kibela_text3": "附目录",
+			"crowi_title": "分栏",
+			"crowi_text1": "可折叠边栏",
+			"crowi_text2": "在侧边栏中显示和发布评论",
+			"crowi_text3": "可折叠目录"
+		},
+		"behavior": "行为",
+		"behavior_desc": {
+			"growi_text1": "<code>/page</code> and <code>/page/</code> 都显示同一页。",
+			"growi_text2": "<code>/nonexistent_page</code> 显示编辑表单",
+			"growi_text3": "如果使用GROWI增强布局,则所有页面都显示子页面列表",
+			"crowi_text1": "<code>/page</code> 显示页面",
+			"crowi_text2": "<code>/page/</code> 显示子页列表",
+			"crowi_text3": "如果portal应用于<code>/page/</code>,则会显示portal和子页面列表",
+			"crowi_text4": "<code>/nonexistent_page</code> 显示编辑表单<",
+			"crowi_text5": "<code>/nonexistent_page/</code> 子页列表"
+		},
+		"theme_desc": {
+			"light_and_dark": "明暗模式",
+			"unique": "只有一种模式"
+		},
+		"function": "功能",
+		"function_desc": "您可以选择函数的有效/无效",
+		"function_options": {
+			"timeline": "时间线函数",
+			"timeline_desc1": "您可以显示子页的时间线。",
+			"timeline_desc2": "如果有许多子页,则在加载页时性能会降低。",
+			"timeline_desc3": "通过使列表页无效,可以加快列表页的显示速度。",
+			"tab_switch": "在浏览器中保存选项卡切换",
+			"tab_switch_desc1": "在浏览器中保存编辑选项卡和历史选项卡切换,并使其成为浏览器的前向/后向命令的对象。",
+			"tab_switch_desc2": "通过失效,您可以将页面转换作为浏览器的前向/后向命令的唯一对象。",
+			"attach_title_header": "自动创建新页面时添加h1节",
+			"attach_title_header_desc": "创建新页面时,将页面路径作为h1节添加到第一行",
+			"recent_created__n_draft_num_desc": "显示最近创建的页数和草稿数",
+			"recently_created_n_draft_num_desc": "用户页上显示的最近创建的页和草稿数",
+			"stale_notification": "在过期页上显示通知",
+			"stale_notification_desc": "显示自上次更新以来超过1年的页面通知。",
+			"show_all_reply_comments": "显示所有回复评论",
+			"show_all_reply_comments_desc": "当设置值为“关”时,将忽略最近两个之外的注释。"
+		},
+		"code_highlight": "代码突出显示",
+		"nocdn_desc": "当强制应用环境变量<code>NO_CDN=true</code><br>Github样式时,此函数被禁用。",
+		"custom_title": "自定义标题",
+		"custom_title_detail": "您可以自定义<code>&lt;title&gt;</code>标记。<br><code>&123;&123;sitename&&125;&125;</code>将自动替换为应用程序名称,并且<code>&123;&123;page&&125;&125;</code>将替换为页面名称/路径。",
+		"custom_title_detail_placeholder1": "<code>&#123;&#123;站点名称&#125;&#125;</code>-此wiki的站点名称。",
+		"custom_title_detail_placeholder2": "<code>&#123;&#123;页名&#125;&#125;</code>-当前页的页名。",
+		"custom_title_detail_placeholder3": "<code>&#123;&#123;页面路径&#125;&#125;</code>-当前页面的页面路径。",
+		"custom_header": "自定义HTML标题",
+		"custom_header_detail": "您可以自定义应用所有页面的HTML标题。您的自定义脚本将插入<code>&lt;header&gt;</code>中,但位于其他<code>&lt;script&gt;</code>标记之上。<br>重新链接页面以查看更改。",
+		"custom_css": "自定义CSS",
+		"write_css": "您可以编写应用于整个系统的CSS。",
+		"ctrl_space": "Ctrl+Space 自动完成",
+		"custom_script": "定制纸条",
+		"write_java": "您可以编写应用于整个系统的Javascript。",
+		"reflect_change": "您需要重新加载页面以反映更改。"
+	},
+	"importer_management": {
+		"beta_warning": "这个函数是Beta。",
+		"import_from": "Import from {{from}}",
+		"import_growi_archive": "Import GROWI archive",
+		"growi_settings": {
+			"description_of_import_mode": {
+				"about": "When you import data with the same name as an existing one, choose from the following three modes below.",
+				"insert": "Insert: Skip importing the data.",
+				"upsert": "Upsert: Overwrite and update the existing data with imported data.",
+				"flash_and_insert": "Flash and Insert: After deleting the existing data completely, import the data"
+			},
+			"growi_archive_file": "GROWI Archive File",
+			"uploaded_data": "Uploaded Data",
+			"extracted_file": "Extracted File",
+			"collection": "Collection",
+			"upload": "Upload",
+			"discard": "Discard uploaded data",
+			"errors": {
+				"at_least_one": "Select one or more collections.",
+				"page_and_revision": "'Pages' and 'Revisions' must be imported both.",
+				"depends": "'{{target}}' must be selected when '{{condition}}' is selected."
+			},
+			"configuration": {
+				"pages": {
+					"overwrite_author": {
+						"label": "Overwrite page's author with the current user",
+						"desc": "Recommended <span class=\"text-danger\">NOT</span> to check this when users will also be restored."
+					},
+					"set_public_to_page": {
+						"label": "Set 'Public' to the pages that is '{{from}}'",
+						"desc": "Make sure that this configuration makes all <b>'{{from}}'</b> pages readable from <span class=\"text-danger\">ANY users</span>."
+					},
+					"initialize_meta_datas": {
+						"label": "Initialize page's like, read users and comment count",
+						"desc": "Recommended <span class=\"text-danger\">NOT</span> to check this when users will also be restored."
+					},
+					"initialize_hackmd_related_datas": {
+						"label": "Initialize HackMD related data",
+						"desc": "Recommended to check this unless there is important drafts on HackMD."
+					}
+				},
+				"revisions": {
+					"overwrite_author": {
+						"label": "Overwrite revision's author with the current user",
+						"desc": "Recommended <span class=\"text-danger\">NOT</span> to check this when users will also be restored."
+					}
+				}
+			}
+		},
+		"esa_settings": {
+			"team_name": "Team name",
+			"access_token": "Access token",
+			"test_connection": "Test connection to esa"
+		},
+		"qiita_settings": {
+			"team_name": "Team name",
+			"access_token": "Access token",
+			"test_connection": "Test connection to qiita:team"
+		},
+		"import": "Import",
+		"page_skip": "Pages with a name that already exists on GROWI are not imported",
+		"Directory_hierarchy_tag": "Directory hierarchy tag"
+	},
+	"export_management": {
+		"exporting_collection_list": "正在导出集合列表",
+		"exported_data_list": "导出的存档数据列表",
+		"export_collections": "导出集合",
+		"check_all": "全部检查",
+		"uncheck_all": "全部取消选中",
+		"desc_password_seed": "DO NOT FORGET to set current <code>PASSWORD_SEED</code> to your new GROWI system when restoring user data, or users will NOT be able to login with their password.<br><br><strong>HINT:</strong><br>The current <code>PASSWORD_SEED</code> will be stored in <code>meta.json</code> in exported ZIP.",
+		"create_new_archive_data": "创建新的存档数据",
+		"export": "导出",
+		"cancel": "取消",
+		"file": "文件",
+		"growi_version": "Growi Version",
+		"collections": "Collections",
+		"exported_at": "Exported At",
+		"export_menu": "导出菜单",
+		"download": "下载",
+		"delete": "删除"
+	},
+	"user_management": {
+		"invite_users": "邀请新用户",
+		"click_twice_same_checkbox": "您应该至少选中一个复选框。",
+		"invite_modal": {
+			"emails": "电子邮件",
+			"invite_thru_email": "发送邀请电子邮件",
+			"valid_email": "需要有效的电子邮件地址",
+			"temporary_password": "创建的用户具有临时密码",
+			"send_new_password": "请将新密码发送给用户。",
+			"send_temporary_password": "请确保复制此屏幕上的临时密码并将其发送给用户。",
+			"existing_email": "以下电子邮件已存在"
+		},
+		"user_table": {
+			"administrator": "管理员",
+			"edit_menu": "编辑菜单",
+			"reset_password": "重置密码",
+			"administrator_menu": "管理员菜单",
+			"accept": "接受",
+			"deactivate_account": "停用帐户",
+			"your_own": "您不能停用自己的帐户",
+			"remove_admin_access": "删除管理员访问权限",
+			"cannot_remove": "您不能从管理员中删除自己",
+			"give_admin_access": "授予管理员访问权限"
+		},
+		"reset_password": "重置密码",
+		"reset_password_modal": {
+			"password_never_seen": "The temporary password can never be retrieved after this screen is closed.",
+			"password_reset_message": "Let the user know the new password below and strongly recommend to change another one immediately.",
+			"send_new_password": "Please send the new password to the user.",
+			"target_user": "Target User",
+			"new_password": "New Password"
+		},
+		"external_account": "外部账户管理",
+		"external_accounts": "外部账户",
+		"create_external_account": "创建外部账户",
+		"external_account_list": "外部账户列表",
+		"invite": "邀请",
+		"invited": "已邀请用户",
+		"back_to_user_management": "返回用户管理",
+		"authentication_provider": "身份认证",
+		"manage": "管理",
+		"password_setting": "密码设置",
+		"password_setting_help": "是否设置了密码?",
+		"set": "是",
+		"unset": "否",
+		"related_username": "相关用户的",
+		"cannot_invite_maximum_users": "邀请的用户数不能超过最大值。",
+		"current_users": "当前用户:"
+	},
+	"user_group_management": {
+		"create_group": "创建新组",
+		"deny_create_group": "不能用当前设置创建新组。",
+		"group_name": "组名",
+		"group_example": "e.g.:第1组",
+		"add_modal": {
+			"add_user": "将用户添加到创建的组",
+			"search_option": "搜索选项",
+			"enable_option": "启用{{option}",
+			"forward_match": "Forword匹配",
+			"partial_match": "部分匹配",
+			"backward_match": "向后匹配"
+		},
+		"group_list": "组列表",
+		"back_to_list": "返回组列表",
+		"basic_info": "基本信息",
+		"user_list": "用户列表",
+		"created_group": "已创建组",
+		"is_loading_data": "获取数据。。。",
+		"no_pages": "组没有查看权限的页面。",
+		"remove_from_group": "删除此用户",
+		"delete_modal": {
+			"header": "删除组",
+			"desc": "删除后,将无法检索已删除的组及其私人页。",
+			"dropdown_desc": "为私人页选择操作",
+			"select_group": "选择组",
+			"no_groups": "没有可选择的组",
+			"publish_pages": "全部发布",
+			"delete_pages": "全部删除",
+			"transfer_pages": "转移到另一组"
+		}
+	}
+}

+ 14 - 0
resource/locales/zh-CN/admin/userInvitation.txt

@@ -0,0 +1,14 @@
+Hi, {{ email }}
+
+You are invited to our Wiki, you can log in with following account:
+
+Email: {{ email }}
+Password: {{ password }}
+(This password was auto generated. Update required at the first time you logging in)
+
+We are waiting for you!
+{{ url }}
+
+--
+{{ appTitle }}
+{{ url }}

+ 21 - 0
resource/locales/zh-CN/admin/userWaitingActivation.txt

@@ -0,0 +1,21 @@
+Hi, {{ adminUser.name }}
+
+A user registered to {{ appTitle }}.
+
+
+====
+Created user:
+
+Name: {{ createdUser.name }}
+User Name: {{ createdUser.username }}
+Email: {{ createdUser.email }}
+====
+
+Please do some action with following URL:
+{{ url }}/admin/users
+
+
+--
+{{ appTitle }}
+{{ url }}
+

+ 9 - 0
resource/locales/zh-CN/notifications/comment.txt

@@ -0,0 +1,9 @@
+{{ username }} commented on {{ path }}.
+
+----------------------
+
+{{ comment }}
+
+----------------------
+
+Growi: {{ appTitle }}

+ 5 - 0
resource/locales/zh-CN/notifications/pageCreate.txt

@@ -0,0 +1,5 @@
+{{ username }} created a new page under {{ path }}.
+
+----------------------
+
+Growi: {{ appTitle }}

+ 5 - 0
resource/locales/zh-CN/notifications/pageDelete.txt

@@ -0,0 +1,5 @@
+{{ username }} deleted the page  {{ path }}.
+
+----------------------
+
+Growi: {{ appTitle }}

+ 5 - 0
resource/locales/zh-CN/notifications/pageEdit.txt

@@ -0,0 +1,5 @@
+{{ username }} edited the page {{ path }}.
+
+----------------------
+
+Growi: {{ appTitle }}

+ 5 - 0
resource/locales/zh-CN/notifications/pageLike.txt

@@ -0,0 +1,5 @@
+{{ username }} liked the page {{ path }}.
+
+----------------------
+
+Growi: {{ appTitle }}

+ 5 - 0
resource/locales/zh-CN/notifications/pageMove.txt

@@ -0,0 +1,5 @@
+{{ username }} renamed the page {{ oldPath }} to {{ newPath }}.
+
+----------------------
+
+Growi: {{ appTitle }}

+ 253 - 0
resource/locales/zh-CN/sandbox-bootstrap4.md

@@ -0,0 +1,253 @@
+# Labels
+
+<span class="badge badge-primary">Primary</span>
+<span class="badge badge-secondary">Secondary</span>
+<span class="badge badge-success">Success</span>
+<span class="badge badge-info">Info</span>
+<span class="badge badge-warning">Warning</span>
+<span class="badge badge-danger">Danger</span>
+<span class="badge badge-light text-dark">Light</span>
+<span class="badge badge-dark">Dark</span>
+
+<span class="badge badge-blue">Blue</span>
+<span class="badge badge-indigo">Indigo</span>
+<span class="badge badge-purple">Purple</span>
+<span class="badge badge-pink">Pink</span>
+<span class="badge badge-red">Red</span>
+<span class="badge badge-orange">Orange</span>
+<span class="badge badge-yellow">Yellow</span>
+<span class="badge badge-green">Green</span>
+<span class="badge badge-teal">Teal</span>
+<span class="badge badge-cyan">Cyan</span>
+
+
+# Alerts
+
+<div class="alert alert-primary" role="alert">
+  This is a primary alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
+</div>
+<div class="alert alert-secondary" role="alert">
+  This is a secondary alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
+</div>
+<div class="alert alert-success" role="alert">
+  This is a success alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
+</div>
+<div class="alert alert-danger" role="alert">
+  This is a danger alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
+</div>
+<div class="alert alert-warning" role="alert">
+  This is a warning alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
+</div>
+<div class="alert alert-info" role="alert">
+  This is a info alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
+</div>
+<div class="alert alert-light text-dark" role="alert">
+  This is a light alert with <a href="#" class="alert-link text-dark">an example link</a>. Give it a click if you like.
+</div>
+<div class="alert alert-dark" role="alert">
+  This is a dark alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
+</div>
+
+# Cards
+
+<div class="d-flex">
+
+<div class="mr-3">
+<div class="card text-white bg-primary mb-3" style="max-width: 18rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Primary card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+<div class="card text-white bg-secondary mb-3" style="max-width: 18rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Secondary card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+<div class="card text-white bg-success mb-3" style="max-width: 18rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Success card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+<div class="card text-white bg-danger mb-3" style="max-width: 18rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Danger card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+<div class="card text-white bg-warning mb-3" style="max-width: 18rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Warning card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+<div class="card text-white bg-info mb-3" style="max-width: 18rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Info card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+<div class="card bg-light mb-3" style="max-width: 18rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Light card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+<div class="card text-white bg-dark mb-3" style="max-width: 18rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Dark card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+</div>
+
+<div>
+<div class="card border-primary mb-3" style="max-width: 18rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body text-primary">
+    <h5 class="card-title">Primary card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+<div class="card border-secondary mb-3" style="max-width: 18rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body text-secondary">
+    <h5 class="card-title">Secondary card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+<div class="card border-success mb-3" style="max-width: 18rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body text-success">
+    <h5 class="card-title">Success card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+<div class="card border-danger mb-3" style="max-width: 18rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body text-danger">
+    <h5 class="card-title">Danger card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+<div class="card border-warning mb-3" style="max-width: 18rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body text-warning">
+    <h5 class="card-title">Warning card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+<div class="card border-info mb-3" style="max-width: 18rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body text-info">
+    <h5 class="card-title">Info card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+<div class="card border-light mb-3" style="max-width: 18rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Light card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+<div class="card border-dark mb-3" style="max-width: 18rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body text-dark">
+    <h5 class="card-title">Dark card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+</div>
+
+</div>
+
+# Wells
+
+## Default well
+
+<div class="card card-body">Look, I'm in a well! </div>
+
+## Optional classes
+
+<div class="card card-body bg-primary text-light p-2">Look, I'm in a well! </div>
+
+# Typography
+
+## Lead body copy
+
+<p class="lead">Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Duis mollis, est non commodo luctus.</p>
+
+## Marked text
+
+You can use the mark tag to <mark>highlight</mark> text.
+
+## Small text
+
+<small>This line of text is meant to be treated as fine print.</small>
+
+## Alignment classes
+
+<div class="card">
+  <div class="card-body">
+    <p class="text-left">Left aligned text.</p>
+    <p class="text-center">Center aligned text.</p>
+    <p class="text-right">Right aligned text.</p>
+    <p class="text-justify">Justified text.</p>
+    <p class="text-nowrap">No wrap text.</p>
+  </div>
+</div>
+
+## Transformation classes
+
+<div class="card">
+  <div class="card-body">
+    <p class="text-lowercase">Lowercased text.</p>
+    <p class="text-uppercase">Uppercased text.</p>
+    <p class="text-capitalize">Capitalized text.</p>
+  </div>
+</div>
+
+
+# Helper classes
+
+## Contextual colors
+
+<div class="card">
+  <div class="card-body">
+    <p class="text-muted">Fusce dapibus, tellus ac cursus commodo, tortor mauris nibh.</p>
+    <p class="text-light">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
+    <p class="text-secondary">Sed luctus venenatis tellus, in aliquam ligula scelerisque eget.</p>
+    <p class="text-dark">Ut vel lorem aliquet, rhoncus libero at, condimentum mi. Fusce pellentesque quam nec magna maximus porta.</p>
+    <p class="text-primary">Nullam id dolor id nibh ultricies vehicula ut id elit.</p>
+    <p class="text-success">Duis mollis, est non commodo luctus, nisi erat porttitor ligula.</p>
+    <p class="text-info">Maecenas sed diam eget risus varius blandit sit amet non magna.</p>
+    <p class="text-warning">Etiam porta sem malesuada magna mollis euismod.</p>
+    <p class="text-danger">Donec ullamcorper nulla non metus auctor fringilla.</p>
+  </div>
+</div>
+
+## Contextual backgrounds
+
+<div class="card">
+  <div class="card-body">
+    <p class="bg-light">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
+    <p class="bg-secondary text-white">Sed luctus venenatis tellus, in aliquam ligula scelerisque eget.</p>
+    <p class="bg-dark text-white">Ut vel lorem aliquet, rhoncus libero at, condimentum mi.</p>
+    <p class="bg-primary text-white">Nullam id dolor id nibh ultricies vehicula ut id elit.</p>
+    <p class="bg-success text-white">Duis mollis, est non commodo luctus, nisi erat porttitor ligula.</p>
+    <p class="bg-info text-white">Maecenas sed diam eget risus varius blandit sit amet non magna.</p>
+    <p class="bg-warning text-white">Etiam porta sem malesuada magna mollis euismod.</p>
+    <p class="bg-danger text-white">Donec ullamcorper nulla non metus auctor fringilla.</p>
+  </div>
+</div>

Разница между файлами не показана из-за своего большого размера
+ 7 - 0
resource/locales/zh-CN/sandbox-diagrams.md


+ 71 - 0
resource/locales/zh-CN/sandbox-math.md

@@ -0,0 +1,71 @@
+# :pencil: Math
+
+See [MathJax](https://www.mathjax.org/).
+
+## Inline Formula
+
+When $a \ne 0$, there are two solutions to \(ax^2 + bx + c = 0\) and they are
+  $$x = {-b \pm \sqrt{b^2-4ac} \over 2a}.$$
+
+## The Lorenz Equations
+
+$$
+\begin{align}
+\dot{x} & = \sigma(y-x) \\
+\dot{y} & = \rho x - y - xz \\
+\dot{z} & = -\beta z + xy
+\end{align}
+$$
+
+
+## The Cauchy-Schwarz Inequality
+
+$$
+\left( \sum_{k=1}^n a_k b_k \right)^{\!\!2} \leq
+ \left( \sum_{k=1}^n a_k^2 \right) \left( \sum_{k=1}^n b_k^2 \right)
+$$
+
+## A Cross Product Formula
+
+$$
+\mathbf{V}_1 \times \mathbf{V}_2 =
+ \begin{vmatrix}
+  \mathbf{i} & \mathbf{j} & \mathbf{k} \\
+  \frac{\partial X}{\partial u} & \frac{\partial Y}{\partial u} & 0 \\
+  \frac{\partial X}{\partial v} & \frac{\partial Y}{\partial v} & 0 \\
+ \end{vmatrix}
+$$
+
+
+## The probability of getting $\left(k\right)$ heads when flipping $\left(n\right)$ coins is:
+
+$$
+P(E) = {n \choose k} p^k (1-p)^{ n-k}
+$$
+
+## An Identity of Ramanujan
+
+$$
+\frac{1}{(\sqrt{\phi \sqrt{5}}-\phi) e^{\frac25 \pi}} =
+     1+\frac{e^{-2\pi}} {1+\frac{e^{-4\pi}} {1+\frac{e^{-6\pi}}
+      {1+\frac{e^{-8\pi}} {1+\ldots} } } }
+$$
+
+## A Rogers-Ramanujan Identity
+
+$$
+1 +  \frac{q^2}{(1-q)}+\frac{q^6}{(1-q)(1-q^2)}+\cdots =
+    \prod_{j=0}^{\infty}\frac{1}{(1-q^{5j+2})(1-q^{5j+3})},
+     \quad\quad \text{for $|q|<1$}.
+$$
+
+## Maxwell's Equations
+
+$$
+\begin{align}
+  \nabla \times \vec{\mathbf{B}} -\, \frac1c\, \frac{\partial\vec{\mathbf{E}}}{\partial t} & = \frac{4\pi}{c}\vec{\mathbf{j}} \\
+  \nabla \cdot \vec{\mathbf{E}} & = 4 \pi \rho \\
+  \nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} & = \vec{\mathbf{0}} \\
+  \nabla \cdot \vec{\mathbf{B}} & = 0
+\end{align}
+$$

+ 457 - 0
resource/locales/zh-CN/sandbox.md

@@ -0,0 +1,457 @@
+<div class="card">
+  <div class="card-body">
+
+# Table of Contents
+
+```
+@[toc]
+```
+
+@[toc]
+
+  </div>
+</div>
+
+# :pencil: Block Elements
+
+## Headers
+
+Add one `#` per level at the start of the line
+
+```
+# Header 1
+## Header 2
+### Header 3
+#### Header 4
+##### Header 5
+###### Header 6
+```
+
+### Header 3
+
+#### Header 4
+
+##### Header 5
+
+###### Header 6
+
+## Block paragraph
+
+Pararaphs are created by inserting a newline character
+A paragraph can be created by pressing Enter at the end of the previous paragraph.
+
+```
+paragraph1
+(Blank line)
+paragraph2
+```
+
+paragraph1
+
+paragraph2
+
+## Br new line
+
+Add two spaces before break.
+***This behaviour can be modified in the options menu.***
+
+```
+hoge
+fuga(two spaces)
+piyo
+```
+
+hoge
+fuga
+piyo
+
+## Blockquotes
+
+Add one `>` per level at the start of the line
+
+```
+> quote
+> quote
+>> nested quotes
+```
+
+> quote
+> quote
+>> nested quotes
+
+## Code
+
+Wrap code with three back quotes or tildes.
+
+```
+print 'hoge'
+```
+
+### Syntax highlight and file name
+
+- corresponding [highlight.js Demo](https://highlightjs.org/static/demo/) of common category
+
+
+~~~
+```javascript:mersenne-twister.js
+function MersenneTwister(seed) {
+  if (arguments.length == 0) {
+    seed = new Date().getTime();
+  }
+
+  this._mt = new Array(624);
+  this.setSeed(seed);
+}
+```
+~~~
+
+```javascript:mersenne-twister.js
+function MersenneTwister(seed) {
+  if (arguments.length == 0) {
+    seed = new Date().getTime();
+  }
+
+  this._mt = new Array(624);
+  this.setSeed(seed);
+}
+```
+
+### Inline code
+
+Words wrapped by `` `back quotes` `` will be formatted as inline code.
+
+```
+This is `Inline Code`.
+```
+
+This is  `Inline Code`.
+
+## Pre-arranged text
+
+Code blocks should be preceded by four spaces or one tab.
+
+```
+    class Hoge
+        def hoge
+            print 'hoge'
+        end
+    end
+```
+
+    class Hoge
+        def hoge
+            print 'hoge'
+        end
+    end
+
+## Horizontal Line
+
+Write three underscores `_`, or asterisks`*`.
+
+```
+***
+___
+---
+```
+
+***
+___
+---
+
+
+
+# :pencil: Typography
+
+## Strong Text
+
+### Italic
+
+To italicize text, add One asterisk or underscores before and after a word or phrase.
+
+```
+This is *Italic* .
+This is _Italic_ .
+```
+
+This is *Italic* .
+This is _Italic_ .
+
+### Bold
+
+To bold text, add two asterisks or underscores before and after a word or phrase.
+
+```
+This is **bold**.
+This is __bold__.
+```
+
+This is **bold**.
+This is __bold__.
+
+### Bold + Italic
+
+To bold and italicize text, add three asterisks or underscores before and after a word or phrase.
+
+```
+This is ***Italic & Bold***.
+This is ___Italic & Bold___.
+```
+
+This is ***Italic & Bold***.
+This is ___Italic & Bold___.
+
+# :pencil: Images
+
+You can insert `<img>` tag using `![description](URL)`.
+
+```markdown
+![Minion](https://octodex.github.com/images/minion.png)
+![Stormtroopocat](https://octodex.github.com/images/stormtroopocat.jpg "The Stormtroopocat")
+```
+
+![Minion](https://octodex.github.com/images/minion.png)
+![Stormtroopocat](https://octodex.github.com/images/stormtroopocat.jpg "The Stormtroopocat")
+
+The size of the image can be set by using an HTML image tag
+
+```html
+<img src="https://octodex.github.com/images/dojocat.jpg" width="200px">
+```
+
+<img src="https://octodex.github.com/images/dojocat.jpg" width="200px">
+
+
+# :pencil: Link
+
+## Markdown standard
+
+You can create links using `[Display text](URL)`.
+
+```
+[Google](https://www.google.co.jp/)
+```
+
+[Google](https://www.google.co.jp/)
+
+## Crowi compatibility
+
+```
+[/Sandbox]
+&lt;/user/admin1>
+```
+
+[/Sandbox]
+</user/admin1>
+
+## Pukiwiki like linker
+
+(available by [weseek/growi-plugin-pukiwiki-like-linker
+](https://github.com/weseek/growi-plugin-pukiwiki-like-linker) )
+
+This is the most flexible linker.
+Both the page description and link address can be displayed on the page.
+
+```
+[[./Bootstrap4]]
+Example of Bootstrap4 is[[here>./Bootstrap4]]
+```
+
+[[../user]]
+Example of Bootstrap4 is[[here>./Bootstrap4]]
+
+# :pencil: Lists
+
+## Ul Bulleted list
+
+To create an unordered list, add dashes (-), asterisks (*), or plus signs (+) in front of line items. 
+Items can be nested using indentation.
+
+```
+- List1
+    - List1_1
+        - List1_1_1
+        - List1_1_2
+    - List1_2
+- List2
+- List3
+```
+
+- List1
+    - List1_1
+        - List1_1_1
+        - List1_1_2
+    - List1_2
+- List2
+- List3
+
+## Ol Numbered List
+
+To create an ordered list, add line items with numbers followed by periods. 
+The numbers don’t have to be in numerical order, but the list should start with the number one.
+
+```
+1. Number list 1
+    1. Number list 1-1
+    1. Number list 1-2
+1. Number list 2
+1. Number list 3
+```
+
+1. Number list 1
+    1. Number list 1-1
+    1. Number list 1-2
+1. Number list 2
+1. Number list 3
+
+
+## Check list
+
+```
+- [ ] Task 1
+    - [x] Task 1.1
+    - [ ] Task 1.2
+- [x] Task2
+```
+
+- [ ] Task 1
+    - [x] Task 1.1
+    - [ ] Task 1.2
+- [x] Task2
+
+
+# :pencil: Table
+
+## Markdown Standard
+
+```markdown
+| Left align | Right align | Center align |
+|:-----------|------------:|:------------:|
+| This       | This        | This         |
+| column     | column      | column       |
+| will       | will        | will         |
+| be         | be          | be           |
+| left       | right       | center       |
+| aligned    | aligned     | aligned      |
+
+OR
+
+Left align | Right align | Center align
+:--|--:|:-:
+This       | This        | This
+column     | column      | column
+will       | will        | will
+be         | be          | be
+left       | right       | center
+aligned    | aligned     | aligned
+```
+
+| Left align | Right align | Center align |
+|:-----------|------------:|:------------:|
+| This       | This        | This         |
+| column     | column      | column       |
+| will       | will        | will         |
+| be         | be          | be           |
+| left       | right       | center       |
+| aligned    | aligned     | aligned      |
+
+## TSV (crowi-plus notation)
+
+```
+::: tsv
+Content Cell  Content Cell
+Content Cell  Content Cell
+:::
+```
+
+::: tsv
+Content Cell Content Cell
+Content Cell Content Cell
+:::
+
+## TSV with header (crowi-plus notation)
+
+```
+::: tsv-h
+First Header Second Header
+Content Cell Content Cell
+Content Cell Content Cell
+:::
+```
+
+::: tsv-h
+First Header Second Header
+Content Cell Content Cell
+Content Cell Content Cell
+:::
+
+## CSV (crowi-plus original notation)
+
+```
+::: csv
+Content Cell,Content Cell
+Content Cell,Content Cell
+:::
+```
+
+::: csv
+Content Cell,Content Cell
+Content Cell,Content Cell
+:::
+
+## CSV with header (crowi-plus original notation)
+
+```
+::: csv-h
+First Header,Second Header
+Content Cell,Content Cell
+Content Cell,Content Cell
+:::
+```
+
+::: csv-h
+First Header,Second Header
+Content Cell,Content Cell
+Content Cell,Content Cell
+:::
+
+
+# :pencil: Footnote
+
+You can write a reference [^1] to a footnote. You can also add an inline footnote^[Inline_footnote].
+
+Long footnotes can be written as [^longnote].
+
+[^1]: A_reference_to_the_first_footnote.
+
+[^longnote]: An_example_of_writing_a_footnote_in_multiple_blocks.
+
+    Subsequent paragraphs are indented and belong to the previous footnote.
+
+
+# :pencil: Emoji
+
+See [emojione](https://www.emojione.com/)
+
+:smiley: :smile: :laughing: :innocent: :drooling_face:
+
+:family: :family_man_boy: :family_man_girl: :family_man_girl_girl: :family_woman_girl_girl:
+
+:thumbsup: :thumbsdown: :open_hands: :raised_hands: :point_right:
+
+:apple: :green_apple: :strawberry: :cake: :hamburger:
+
+:basketball: :football: :baseball: :volleyball: :8ball:
+
+:hearts: :broken_heart: :heartbeat: :heartpulse: :heart_decoration:
+
+:watch: :gear: :gem: :wrench: :envelope:
+
+
+# :heavy_plus_sign: More..
+
+- Try to attach Bootstrap4 Tags?
+    - :arrow_right: [/Sandbox/Bootstrap4]
+- Try to draw Diagrams?
+    - :arrow_right: [/Sandbox/Diagrams]
+- Try to write Math Formulas?
+    - :arrow_right: [/Sandbox/Math]

+ 700 - 0
resource/locales/zh-CN/translation.json

@@ -0,0 +1,700 @@
+{
+	"Help": "帮助",
+	"Edit": "编辑",
+	"Delete": "删除",
+	"delete_all": "删除所有",
+	"Duplicate": "复制",
+	"Copy": "复制",
+	"Login": "登录",
+	"Click to copy": "点击复制",
+	"Move/Rename": "移动/重命名",
+	"Moved": "移动",
+	"Redirected": "重定向",
+	"Unlinked": "Unlinked",
+	"Like!": "Like!",
+	"Seen by": "Seen by",
+	"Cancel": "取消",
+	"Create": "创建",
+	"Admin": "管理",
+	"administrator": "管理员",
+	"Tag": "标签",
+	"Tags": "Tags",
+	"New": "新建",
+	"Shortcuts": "快捷方式",
+	"eg": "e.g.",
+	"add": "添加",
+	"Undo": "撤销",
+	"Article": "主题",
+	"Page": "页面",
+	"Page Path": "相对路径",
+	"Category": "分类",
+	"User": "用户",
+	"status": "状态",
+	"account_id": "用户Id",
+	"Update": "更新",
+	"Update Page": "更新本页",
+	"Warning": "警告",
+	"Sign in": "登录",
+	"Sign up is here": "注册",
+	"Sign in is here": "登录",
+	"Sign up": "注册",
+	"Sign up with Google Account": "Sign up with Google Account",
+	"Sign in with Google Account": "Sign in with Google Account",
+	"Sign up with this Google Account": "Sign up with this Google Account",
+	"Example": "例如",
+	"Taro Yamada": "John Doe",
+	"List View": "列表",
+	"Timeline View": "时间线",
+	"History": "历史",
+	"Presentation Mode": "演示文稿",
+	"Not available for guest": "Not available for guest",
+	"username": "用户名",
+	"Created": "创建",
+	"Last updated": "上次更新",
+	"Last_Login": "上次登录",
+	"Share": "分享",
+	"Share Link": "分享链接",
+	"Markdown Link": "Markdown链接",
+	"Create/Edit Template": "创建/编辑 模板页面",
+	"Unportalize": "未启动",
+	"Go to this version": "查看此版本",
+	"View diff": "查看差异",
+	"No diff": "无差异",
+	"Shrink versions that have no diffs": "收缩没有差异的版本",
+	"User ID": "用户ID",
+	"Home": "首页",
+	"User Settings": "用户设置",
+	"User Information": "用户信息",
+	"Basic Info": "基础信息",
+	"Name": "姓名",
+	"Email": "邮箱",
+	"Language": "语言",
+	"English": "英语",
+	"Japanese": "日语",
+	"Chinese": "简体中文",
+	"Set Profile Image": "头像",
+	"Upload Image": "上传图片",
+	"Current Image": "当前图片",
+	"Delete Image": "删除图片",
+	"Delete this image?": "删除图片?",
+	"Updated": "更新",
+	"Upload new image": "上传新图像",
+	"Connected": "Connected",
+	"Show": "显示",
+	"Hide": "隐藏",
+	"Reset": "重置",
+	"Disclose E-mail": "显示邮箱",
+	"page exists": "页面已存在",
+	"Error occurred": "Error occurred",
+	"Create today's": "Create today's ...",
+	"Memo": "memo",
+	"Input page name": "Input page name",
+	"Input page name (optional)": "Input page name (optional)",
+	"New Page": "新页面",
+	"Create under": "Create page under below:",
+	"Table of Contents": "Table of Contents",
+	"Wiki Management Home Page": "Wiki管理首页",
+	"App Settings": "系统设置",
+	"Site URL settings": "主页URL设置",
+	"Markdown Settings": "Markdown设置",
+	"Customize": "页面定制",
+	"Notification Settings": "通知设置",
+	"User_Management": "用户管理",
+	"external_account_management": "外部账户管理",
+	"UserGroup Management": "用户组管理",
+	"Full Text Search Management": "全文搜索管理",
+	"Import Data": "导入数据",
+	"Export Archive Data": "导出主题数据",
+	"Basic Settings": "基础设置",
+	"Basic authentication": "基本身份验证",
+	"Register limitation": "注册限制",
+	"The contents entered here will be shown in the header etc": "此处输入的内容将显示在标题等中",
+	"Public": "公共",
+	"Anyone with the link": "任何人",
+	"Specified users only": "仅指定用户",
+	"Only me": "只有我",
+	"Only inside the group": "仅组内",
+	"page_list_and_search_results": "页面列表/搜索结果",
+	"scope_of_page_disclosure": "页面公开范围",
+	"set_point": "设定值",
+	"always_displayed": "始终显示",
+	"always_hidden": "总是隐藏",
+	"displayed_or_hidden": "显示/隐藏",
+	"page_access_and_delete_rights": "页面访问/删除权限",
+	"Reselect the group": "重新选择组",
+	"Shareable link": "可分享链接",
+	"The whitelist of registration permission E-mail address": "注册许可电子邮件地址的白名单",
+	"Add tags for this page": "添加标签",
+	"Edit tags for this page": "编辑标签",
+	"You have no tag, You can set tags on pages": "你没有标签,可以在页面上设置标签",
+	"Show latest": "显示最新",
+	"Load latest": "家在最新",
+	"edited this page": "edited this page.",
+	"List Drafts": "草稿",
+	"Deleted Pages": "已删除页",
+	"Sign out": "退出",
+	"Disassociate": "解除关联",
+	"Recent Created": "最新创建",
+	"Recent Changes": "最新修改",
+	"form_validation": {
+		"error_message": "有些值不正确",
+		"required": "%s 是必需的",
+		"invalid_syntax": "%s的语法无效。"
+	},
+	"installer": {
+		"setup": "安装",
+		"create_initial_account": "创建初始用户",
+		"initial_account_will_be_administrator_automatically": "初始帐户将自动成为管理员。",
+		"unavaliable_user_id": "用户ID不可用"
+	},
+	"breaking_changes": {
+		"v346_using_basic_auth": "当前使用的基本身份验证在不久的将来将不再可用。从%s中删除设置"
+	},
+	"page_register": {
+		"notice": {
+			"restricted": "需要管理员批准。",
+			"restricted_defail": "一旦管理员批准您的注册,您就可以访问此wiki。"
+		},
+		"form_help": {
+			"email": "您必须有下面列出的电子邮件地址才能注册此wiki。",
+			"password": "密码长度必须至少为6个字符。",
+			"user_id": "您创建的网页的URL将包含您的用户ID。您的用户ID可以由字母、数字和一些符号组成。"
+		}
+	},
+	"Settings": "设置",
+	"page_me": {
+		"form_help": {
+			"profile_image1": "图像上传设置未完成。",
+			"profile_image2": "设置AWS或启用本地上传。"
+		}
+	},
+	"page_me_apitoken": {
+		"notice": {
+			"apitoken_issued": "API token 未发布。",
+			"update_token1": "您可以更新以生成新的API令牌。",
+			"update_token2": "您需要更新任何现有进程中的API令牌。"
+		}
+	},
+	"Password": "密码",
+	"Password Settings": "密码设置",
+	"personal_settings": {
+		"disassociate_external_account": "解除与外部帐户的关联",
+		"disassociate_external_account_desc": "是否确实要解除与<strong>{{providerType}}</strong>帐户<strong>{{providerType}}</strong> 的关联?",
+		"set_new_password": "设置新密码",
+		"update_password": "更新密码",
+		"current_password": "当前密码",
+		"new_password": "新密码",
+		"new_password_confirm": "重复新密码",
+		"password_is_not_set": "密码未设置"
+	},
+	"Security Settings": "安全设置",
+	"API Settings": "API设置",
+	"API Token Settings": "API token 设置",
+	"Current API Token": "当前 API token",
+	"Update API Token": "更新 API token",
+	"header_search_box": {
+		"label": {
+			"All pages": "所有页面",
+			"This tree": "当前分支"
+		},
+		"item_label": {
+			"All pages": "所有页面",
+			"This tree": "当前分支以下内容"
+		}
+	},
+	"copy_to_clipboard": {
+		"Copy to clipboard": "复制到剪贴板",
+		"Page path": "页面路径",
+		"Page URL": "页面Url",
+		"Parmanent link": "参数化链接",
+		"Page path and parmanent link": "页面路径及参数化链接",
+		"Markdown link": "Markdown链接"
+	},
+	"search_help": {
+		"title": "搜索帮助",
+		"and": {
+			"syntax help": "用空格分隔",
+			"desc": "在标题或正文中同时包含{{word1}、{{word2}的搜索页"
+		},
+		"exclude": {
+			"desc": "排除标题或正文中包含{{word}的页"
+		},
+		"phrase": {
+			"syntax help": "用双引号括起来",
+			"desc": "包含短语“{{phrase}”的搜索页"
+		},
+		"prefix": {
+			"desc": "只搜索标题以{{path}开头的页"
+		},
+		"exclude_prefix": {
+			"desc": "排除标题以{{path}开头的页"
+		},
+		"tag": {
+			"desc": "搜索带有{{tag}标记的页面"
+		},
+		"exclude_tag": {
+			"desc": "排除带有{{tag}标记的页"
+		}
+	},
+	"search": {
+		"search page bodies": "按[回车]键进行全文搜索"
+	},
+	"page_page": {
+		"notice": {
+			"version": "这不是当前版本。",
+			"moved": "此页已从<code>%s</code>",
+			"redirected": "您将从<code>%s</code>",
+			"duplicated": "此页来自<code>%s</code>",
+			"unlinked": "将网页重定向到此网页已被删除。",
+			"restricted": "访问此页受到限制",
+			"stale": "自上次更新以来,已超过{{count}年。",
+			"stale_plural": "自上次更新以来已过去{{count}年以上。"
+		}
+	},
+	"page_edit": {
+		"Show active line": "显示活动行",
+		"overwrite_scopes": "{{operation}和覆盖所有子体的作用域",
+		"notice": {
+			"conflict": "无法保存您所做的更改,因为其他人正在编辑此页。请在重新加载页面后重新编辑受影响的部分。"
+		}
+	},
+	"page_api_error": {
+		"notfound_or_forbidden": "未找到或禁止原始页。",
+		"already_exists": "新建页面已存在",
+		"outdated": "页面已被某人更新,现在已过时。",
+		"user_not_admin": "仅管理员用户可以完全删除"
+	},
+	"modal_rename": {
+		"label": {
+			"Move/Rename page": "页面 移动/重命名",
+			"New page name": "新建页面名称",
+			"Current page name": "当前页面名称",
+			"Recursively": "递归地",
+			"Do not update metadata": "不更新元数据",
+			"Redirect": "重定向"
+		},
+		"help": {
+			"redirect": "Redirect to new page if someone accesses <code>%s</code>",
+			"metadata": "Remains last update user and updated date as is",
+			"recursive": "Move/Rename children of under <code>%s</code> recursively"
+		}
+	},
+	"Put Back": "Put back",
+	"Delete Completely": "Delete completely",
+	"modal_delete": {
+		"delete_page": "Delete page",
+		"deleting_page": "Deleting page",
+		"delete_recursively": "Delete child pages recursively.",
+		"delete_completely": "Delete completely",
+		"delete_completely_restriction": "You don't have the authority to delete pages completely.",
+		"recursively": "Delete children of <code>%s</code> recursively.",
+		"completely": "Delete completely instead of putting it into trash."
+	},
+	"modal_empty": {
+		"empty_the_trash": "Empty The Trash",
+		"notice": "完全删除的页面是不可恢复的。"
+	},
+	"modal_duplicate": {
+		"label": {
+			"Duplicate page": "Duplicate page",
+			"New page name": "New page name",
+			"Current page name": "Current page name"
+		}
+	},
+	"modal_putback": {
+		"label": {
+			"Put Back Page": "Put back page",
+			"recursively": "Put back recursively"
+		},
+		"help": {
+			"recursively": "Put back children of under <code>%s</code> recursively"
+		}
+	},
+	"modal_shortcuts": {
+		"global": {
+			"title": "全局快捷方式",
+			"Open/Close shortcut help": "打开/关闭快捷方式帮助",
+			"Edit Page": "编辑页面",
+			"Create Page": "创建页面",
+			"Show Contributors": "显示参与者",
+			"Konami Code": "Konami Code",
+			"konami_code_url": "https://en.wikipedia.org/wiki/Konami_Code"
+		},
+		"editor": {
+			"title": "编辑器快捷方式",
+			"Indent": "缩进",
+			"Outdent": "回退缩进",
+			"Save Page": "保存页面",
+			"Delete Line": "删除行"
+		},
+		"commentform": {
+			"title": "注释窗体快捷方式",
+			"Post": "提交"
+		}
+	},
+	"toaster": {
+		"update_successed": "Succeeded to update {{target}}",
+		"give_user_admin": "Succeeded to give {{username}} admin",
+		"remove_user_admin": "Succeeded to remove {{username}} admin ",
+		"activate_user_success": "Succeeded to activating {{username}}",
+		"deactivate_user_success": "Succeeded to deactivate {{username}}",
+		"remove_user_success": "Succeeded to removing {{username}} ",
+		"remove_external_user_success": "Succeeded to remove {{accountId}} "
+	},
+	"template": {
+		"modal_label": {
+			"Create/Edit Template Page": "创建/编辑模板页",
+			"Create template under": "在下面创建模板页:<br/><code><small>%s</small></code>"
+		},
+		"option_label": {
+			"create/edit": "创建/编辑模板页。",
+			"select": "选择模板页面类型"
+		},
+		"children": {
+			"label": "子模板",
+			"desc": "仅应用于模板存在的同一级别页"
+		},
+		"decendants": {
+			"label": "子代模板",
+			"desc": "适用于所有分散页"
+		}
+	},
+	"sandbox": {
+		"header": "标题",
+		"header_x": "标题{{index}",
+		"block": "段落",
+		"block_detail": "写一段",
+		"empty_line": "空行",
+		"line_break": "换行符",
+		"line_break_detail": "(2空格)换行",
+		"typography": "排版",
+		"italics": "斜体",
+		"bold": "加粗",
+		"italic_bold": "斜体加粗",
+		"strikethrough": "删除线",
+		"link": "链接",
+		"code_highlight": "代码突出显示",
+		"list": "列表",
+		"unordered_list_x": "无序列表{{index}}",
+		"ordered_list_x": "有序列表{{index}}",
+		"task": "任务",
+		"task_checked": "选中的",
+		"task_unchecked": "未选中的",
+		"quote": "引用",
+		"quote1": "你可以写",
+		"quote2": "多行引用",
+		"quote_nested": "嵌套引用",
+		"table": "表格",
+		"image": "图片",
+		"alt_text": "Alt文本",
+		"insert_image": "插入图像",
+		"open_sandbox": "开放式沙箱"
+	},
+	"hackmd": {
+		"not_set_up": "HackMD is not set up.",
+		"start_to_edit": "Start to edit with HackMD",
+		"clone_page_content": "Click to clone page content and start to edit.",
+		"unsaved_draft": "HackMD has unsaved draft.",
+		"draft_outdated": "DRAFT MAY BE OUTDATED",
+		"based_on_revision": "The current draft on HackMD is based on",
+		"view_outdated_draft": "View the outdated draft on HackMD",
+		"resume_to_edit": "Resume to edit with HackMD",
+		"discard_changes": "Discard changes of HackMD",
+		"integration_failed": "HackMD Integration failed",
+		"fail_to_connect": "GROWI client failed to connect to GROWI agent for HackMD.",
+		"check_configuration": "Check your configuration following <a href='https://docs.growi.org/guide/admin-cookbook/integrate-with-hackmd.html'>the manual</a>.",
+		"not_initialized": "HackmdEditor component has not initialized",
+		"someone_editing": "Someone editing this page on HackMD",
+		"this_page_has_draft": "This page has a draft on HackMD"
+	},
+	"security_settings": "安全设置",
+	"security_setting": {
+		"Security settings": "安全设置",
+		"Guest Users Access": "来宾用户访问",
+		"Fixed by env var": "这是由env var<code>%s=%s</code>修复的。",
+		"Register limitation": "注册限制",
+		"Register limitation desc": "限制新用户注册",
+		"The whitelist of registration permission E-mail address": "注册许可电子邮件地址的白名单",
+		"users_without_account": "无法访问没有帐户的用户",
+		"example": "例子",
+		"restrict_emails": "您可以通过编写电子邮件域(以@开头)将电子邮件注册限制为wiki。",
+		"for_example": " 例如,如果要将注册限制为growi.org网站域,你可以写",
+		"in_this_case": ";在这种情况下,只有growi.org网站域将能够注册,所有其他用户将被拒绝。",
+		"insert_single": "请每行插入一个电子邮件地址。",
+		"page_listing_1": "页面列表/搜索<br>受“仅限我”限制",
+		"page_listing_1_desc": "列出/搜索时显示受“仅限我”选项限制的页面",
+		"page_listing_2": "页面列表/搜索<br>受用户组限制",
+		"page_listing_2_desc": "显示列出/搜索时受用户组限制的页面",
+		"complete_deletion": "限制完全删除页面",
+		"complete_deletion_explain": "限制可以完全删除页面的用户。",
+		"admin_only": "仅管理员",
+		"admin_and_author": "管理员|作者",
+		"anyone": "任何人",
+		"Authentication mechanism settings": "身份验证机制设置",
+		"setup_is_not_yet_complete": "安装尚未完成",
+		"alert_siteUrl_is_not_set": "主页URL未设置,通过 {{link}} 设置",
+		"xss_prevent_setting": "阻止XSS(跨站点脚本)",
+		"xss_prevent_setting_link": "转到Markdown设置",
+		"callback_URL": "回调URL",
+		"providerName": "提供程序名称",
+		"issuerHost": "发行者主机",
+		"scope": "Scope",
+		"desc_of_callback_URL": "在{{AuthName}}身份提供程序的设置中使用它",
+		"clientID": "Client ID",
+		"client_secret": "客户机密",
+		"updated_general_security_setting": "更新安全设置成功",
+		"setup_not_completed_yet": "安装尚未完成",
+		"guest_mode": {
+			"deny": "拒绝(仅限注册用户)",
+			"readonly": "接受(来宾可以只读)"
+		},
+		"registration_mode": {
+			"open": "打开(任何人都可以注册)",
+			"restricted": "受限(需要管理员批准)",
+			"closed": "已关闭(仅限邀请)"
+		},
+		"configuration": " 配置",
+		"optional": "可选的",
+		"Treat username matching as identical": "Automatically bind external accounts newly logged in to local accounts when <code>username</code> match",
+		"Treat username matching as identical_warn": "WARNING: Be aware of security because the system treats the same user as a match of <code>username</code>.",
+		"Treat email matching as identical": "Automatically bind external accounts newly logged in to local accounts when <code>email</code> match",
+		"Treat email matching as identical_warn": "WARNING: Be aware of security because the system treats the same user as a match of <code>email</code>.",
+		"Use env var if empty": "Use env var <code>{{env}}</code> if empty",
+		"Use default if both are empty": "If both ​​are empty, the default value <code>{{target}}</code> is used.",
+		"missing mandatory configs": "The following mandatory items are not set in either database nor environment variables.",
+		"Local": {
+			"name": "ID/Password",
+			"note for the only env option": "The LOCAL authentication is limited by the value of environment variable.<br>To change this setting, please change to false or delete the value of the environment variable <code>{{env}}/code> .",
+			"enable_local": "Enable ID/Password"
+		},
+		"ldap": {
+			"enable_ldap": "Enable LDAP",
+			"server_url_detail": "The LDAP URL of the directory service in the format <code>ldap://host:port/DN</code> or <code>ldaps://host:port/DN</code>.",
+			"bind_mode": "Binding Mode",
+			"bind_manager": "Manager Bind",
+			"bind_user": "User Bind",
+			"bind_DN_manager_detail": "The DN of the account that authenticates and queries the directory service",
+			"bind_DN_user_detail1": "The query used to bind with the directory service.",
+			"bind_DN_user_detail2": "Use <code>&#123;&#123;username&#125;&#125;</code> to reference the username entered in the login page.",
+			"bind_DN_password": "Bind DN Password",
+			"bind_DN_password_manager_detail": "The password for the Bind DN account.",
+			"bind_DN_password_user_detail": "The password that is entered in the login page will be used to bind.",
+			"search_filter": "Search Filter",
+			"search_filter_detail1": "The query used to locate the authenticated user.",
+			"search_filter_detail2": "Use <code>&#123;&#123;username&#125;&#125;</code> to reference the username entered in the login page.",
+			"search_filter_detail3": "If empty, the filter <code>(uid=&#123;&#123;username&#125;&#125;)</code> is used.",
+			"search_filter_example1": "Match with 'uid' or 'mail'",
+			"search_filter_example2": "Match with 'sAMAccountName' for Active Directory",
+			"username_detail": "Specification of mappings for <code>username</code> when creating new users",
+			"name_detail": "Specification of mappings for full name when creating new users",
+			"mail_detail": "Specification of mappings for mail address when creating new users",
+			"group_search_base_DN": "Group Search Base DN",
+			"group_search_base_DN_detail": "The base DN from which to search for groups. If defined, also <code>Group Search Filter</code> must be defined for the search to work.",
+			"group_search_filter": "Group Search Filter",
+			"group_search_filter_detail1": "The query used to filter for groups.",
+			"group_search_filter_detail2": "Login via LDAP is accepted only when this query hits one or more groups.",
+			"group_search_filter_detail3": "Use <code>&#123;&#123;dn&#125;&#125;</code> to have it replaced of the found user object.",
+			"group_search_filter_detail4": "<code>(&(cn=group1)(memberUid=&#123;&#123;dn&#125;&#125;))</code> hits the groups which has <code>cn=group1</code> and <code>memberUid</code> includes the user's <code>uid</code>(when <code>Group DN Property</code> is not changed from the default value.)",
+			"group_search_user_DN_property": "User DN Property",
+			"group_search_user_DN_property_detail": "The property of user object to use in <code>&#123;&#123;dn&#125;&#125;</code> interpolation of <code>Group Search Filter</code>.",
+			"test_config": "Test Saved Configuration",
+			"updated_ldap": "Succeeded to update LDAP setting"
+		},
+		"SAML": {
+			"name": "SAML",
+			"enable_saml": "Enable SAML",
+			"id_detail": "Specification of the name of attribute which can identify the user in SAML Identity Provider",
+			"username_detail": "Specification of mappings for <code>username</code> when creating new users",
+			"mapping_detail": "Specification of mappings for {{target}} when creating new users",
+			"cert_detail": "PEM-encoded X.509 signing certificate to validate the response from IdP",
+			"Use env var if empty": "If the value in the database is empty, the value of the environment variable <code>{{env}}</code> is used.",
+			"note for the only env option": "The setting item that enables or disables the SAML authentication and the highlighted setting items use only the value of environment variables.<br>To change this setting, please change to false or delete the value of the environment variable <code>{{env}}</code> .",
+			"attr_based_login_control_detail": "Limit who can sign up by using <code>&lt;saml: Attribute&gt;</code> element included in <code>&lt;saml: AttributeStatement&gt;</code> element and its child element <code>&lt;saml: AttributeValue&gt;</code>.",
+			"attr_based_login_control_rule_detail": "See <a href=\"https://lucene.apache.org/core/2_9_4/queryparsersyntax.html\" target=\"_blank\">Apache Lucene - Query Parser Syntax</a>.<h6>Supported Queries:</h6><ul><li>Terms</li><li>Fields</li><li>AND/NOT/OR Operator</li><li>Grouping</li></ul><h6>Unsupported Queries:</h6><ul><li>Wildcard, Fuzzy, Proximity, Range and Boosting</li><li>+/- Operator</li><li>Field Grouping</li></ul>",
+			"attr_based_login_control_rule_example": "<h6>Example</h6>If a rule is <code>(Department: A || Department: B) && Position: Leader</code>, users who have either <code>Department: A</code> or <code>Department: B</code> and have <code>Position: Leader</code> <strong>can</strong> sign in.",
+			"updated_saml": "Succeeded to update SAML setting"
+		},
+		"Basic": {
+			"enable_basic": "Enable Basic",
+			"name": "Basic Authentication",
+			"desc_1": "Login with <code>username</code> in Authorization header.",
+			"desc_2": "User will be automatically generated if not exist.",
+			"updated_basic": "Succeeded to update Basic setting"
+		},
+		"OAuth": {
+			"enable_oidc": "Enable OIDC",
+			"register": "Register for %s",
+			"change_redirect_url": "Enter <code>%s</code> <br>(where <code>%s</code> is your host name) for \"Authorized redirect URIs\".",
+			"Google": {
+				"enable_google": "Enable Google OAuth",
+				"name": "Google OAuth",
+				"register_1": "Access {{link}}",
+				"register_2": "Create Project if no projects exist",
+				"register_3": "Create Credentials &rightarrow; OAuth client ID &rightarrow; Select \"Web application\"",
+				"register_4": "Register your OAuth App with one of Authorized redirect URIs as <code>{{url}}</code>",
+				"register_5": "Copy and paste your ClientID and Client Secret above",
+				"updated_google": "Succeeded to update Google OAuth setting"
+			},
+			"Facebook": {
+				"name": "Facebook OAuth"
+			},
+			"Twitter": {
+				"enable_twitter": "Enable Twitter OAuth",
+				"name": "Twitter OAuth",
+				"register_1": "Access {{link}}",
+				"register_2": "Sign in Twitter",
+				"register_3": "Create Credentials &rightarrow; OAuth client ID &rightarrow; Select \"Web application\"",
+				"register_4": "Register your OAuth App with one of Authorized redirect URIs as <code>{{url}}</code>",
+				"register_5": "Copy and paste your ClientID and Client Secret above",
+				"updated_twitter": "Succeeded to update Twitter OAuth setting"
+			},
+			"GitHub": {
+				"enable_github": "Enable GitHub OAuth",
+				"name": "GitHub OAuth",
+				"register_1": "Access {{link}}",
+				"register_2": "Register your OAuth App with \"Authorization callback URL\" as <code>{{url}}</code>",
+				"register_3": "Copy and paste your ClientID and Client Secret above",
+				"updated_github": "Succeeded to update GitHub OAuth setting"
+			},
+			"OIDC": {
+				"name": "OpenID Connect",
+				"id_detail": "Specification of the name of attribute which can identify the user in OIDC claims",
+				"username_detail": "Specification of mappings for <code>username</code> when creating new users",
+				"name_detail": "Specification of mappings for <code>name</code> when creating new users",
+				"mapping_detail": "Specification of mappings for %s when creating new users",
+				"register_1": "Contant to OIDC IdP Administrator",
+				"register_2": "Register your OIDC App with \"Authorization callback URL\" as <code>%s</code>",
+				"register_3": "Copy and paste your ClientID and Client Secret above",
+				"updated_oidc": "Succeeded to update OpenID Connect"
+			},
+			"how_to": {
+				"google": "How to configure Google OAuth?",
+				"github": "How to configure GitHub OAuth?",
+				"twitter": "How to configure Twitter OAuth?",
+				"oidc": "How to configure OIDC?"
+			}
+		},
+		"form_item_name": {
+			"entryPoint": "Entry point",
+			"issuer": "Issuer",
+			"cert": "Certificate",
+			"attrMapId": "ID",
+			"attrMapUsername": "Username",
+			"attrMapMail": "Mail Address",
+			"attrMapFirstName": "First Name",
+			"attrMapLastName": "Last Name",
+			"ABLCRule": "Rule"
+		}
+	},
+	"notification_setting": {
+		"slack_incoming_configuration": "Slack Incoming Webhooks configuration",
+		"prioritize_webhook": "Prioritize incoming webhook than Slack App",
+		"prioritize_webhook_desc": "Check this option and GROWI use Incoming Webhooks even if Slack App settings are enabled.",
+		"slack_app_configuration": "Slack app configuration",
+		"slack_app_configuration_desc": "This is the way that compatible with Crowi,<br /> but not recommended in GROWI because it is <strong>too complex</strong>.",
+		"use_instead": "Please use Slack Incoming Webhooks Configuration instead.",
+		"how_to": {
+			"header": "How to configure Incoming Webhooks?",
+			"workspace": "(At Workspace) Add a hook",
+			"workspace_desc1": "Go to <a href='https: //slack.com/services/new/incoming-webhook'>Incoming Webhooks configuration page</a>.",
+			"workspace_desc2": "Choose the default channel to post.",
+			"workspace_desc3": "Add.",
+			"at_growi": "(At GROWI admin page) Set Webhook URL",
+			"at_growi_desc": "Input &rdquo;Webhook URL&rdquo; and submit on this page."
+		},
+		"user_trigger_notification_header": "Default notification settings for patterns",
+		"pattern": "Pattern",
+		"channel": "Channel",
+		"pattern_desc": "Path name of wiki. Pattern expression with <code>*</code> can be used.",
+		"channel_desc": "Slack channel name. Without <code>#</code>.",
+		"valid_page": "启用/禁用通知",
+		"link_notification_help": "<strong>只有那些知道“链接的任何人”链接的人才能查看的页面并不总是得到通知。</strong> ",
+		"just_me_notification_help": "<strong>被“仅限我”限制的页在编辑时被通知。</strong>",
+		"group_notification_help": "<strong>被“用户组”限制的页面在编辑时被通知。</strong>",
+		"notification_list": "List of notification settings",
+		"add_notification": "Add new",
+		"trigger_path": "Trigger path",
+		"trigger_path_help": "(expression with <code>*</code> is supported)",
+		"trigger_events": "Trigger events",
+		"notify_to": "Notify to",
+		"back_to_list": "Go back to list",
+		"notification_detail": "Notification Setting Details",
+		"event_pageCreate": "When new page is \"CREATED\"",
+		"event_pageEdit": "When page is \"EDITED\"",
+		"event_pageDelete": "When page is \"DELETED\"",
+		"event_pageMove": "When page is \"MOVED\" (renamed)",
+		"event_pageLike": "When someone \"LIKES\" page",
+		"event_comment": "When someone \"COMMENTS\" on page",
+		"email": {
+			"ifttt_link": "Create a new IFTTT applet with Email trigger"
+		},
+		"updated_slackApp": "Succeeded to update Slack App Configuration setting",
+		"add_notification_pattern": "Add user trigger notification patterns",
+		"delete_notification_pattern": "Delete notification pattern",
+		"delete_notification_pattern_desc1": "Delete Path: {{path}}",
+		"delete_notification_pattern_desc2": "Once deleted, it cannot be recovered",
+		"toggle_notification": "Updated setting of {{path}}"
+	},
+	"full_text_search_management": {
+		"elasticsearch_management": "Elasticsearch管理",
+		"connection_status": "连接状态",
+		"connection_status_label_unconfigured": "未配置",
+		"connection_status_label_connected": "已连接",
+		"connection_status_label_disconnected": "断开的",
+		"connection_status_label_erroroccured": "搜索服务出错",
+		"indices_status": "索引状态",
+		"indices_status_label_normalized": "标准化",
+		"indices_status_label_unnormalized": "重建或损坏",
+		"indices_summary": "索引摘要",
+		"reconnect": "重新连接",
+		"reconnect_button": "尝试重新连接",
+		"reconnect_description": "单击按钮尝试重新连接到Elasticsearch。",
+		"normalize": "规范化",
+		"normalize_button": "规范化索引",
+		"normalize_description": "单击按钮修复损坏的索引。",
+		"rebuild": "重建",
+		"rebuild_button": "重建索引",
+		"rebuild_description_1": "单击按钮以重新生成索引并添加所有页面数据。",
+		"rebuild_description_2": "这可能需要一段时间。"
+	},
+	"export_management": {
+		"exporting_collection_list": "正在导出集合列表",
+		"exported_data_list": "导出的存档数据列表",
+		"export_collections": "导出集合",
+		"check_all": "全部选中",
+		"uncheck_all": "全部取消选中",
+		"desc_password_seed": "还原用户数据时,不要忘记将当前的<code>密码种子设置到新的GROWI系统,否则用户将无法使用其密码登录。<br><br><strong>提示:</strong><br>当前的<code>密码种子将存储在<code>meta.json格式</code>在导出的zip压缩包中。",
+		"create_new_archive_data": "创建新的存档数据",
+		"export": "导出",
+		"cancel": "取消",
+		"file": "文件",
+		"growi_version": "Growi 版本号",
+		"collections": "收藏",
+		"exported_at": "导出在",
+		"export_menu": "导出菜单",
+		"download": "下载",
+		"delete": "删除"
+	},
+	"personal_dropdown": {
+		"home": "家",
+		"settings": "设置",
+		"color_mode": "颜色模式",
+		"sidebar_mode": "边栏模式",
+		"sidebar_mode_editor": "编辑器上的边栏模式",
+		"use_os_settings": "使用操作系统设置"
+	},
+	"search_result": {
+		"result_meta": "在{{total}中找到了{{keyword}。",
+		"deletion_mode_btn_lavel": "选择并删除页面",
+		"cancel": "取消",
+		"delete": "删除",
+		"check_all": "全部检查",
+		"deletion_modal_header": "删除页",
+		"delete_completely": "完全删除"
+	},
+	"login": {
+		"Sign in error": "登录错误",
+		"Registration successful": "注册成功",
+		"Setup": "安装程序"
+	}
+}

+ 27 - 0
resource/locales/zh-CN/welcome.md

@@ -0,0 +1,27 @@
+# 欢迎来到GROWI :anchor:
+
+[![GitHub Releases](https://img.shields.io/github/release/weseek/growi.svg)](https://github.com/weseek/growi/releases/latest)
+[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE)
+
+<div class="card border-primary">
+  <div class="card-header bg-primary text-light">提示</div>
+  <div class="card-body"><ul>
+    <li>(按Ctrl>)+“/”to show quick help</li>
+    <li>>你可以写HTML与</a href=”https://getbootstrap.com docs/4.5 components/“Bootstrap 4</a></li>
+  </ul></div>
+</div>
+
+Contents
+=========
+
+|All Pages|[/Sandbox]|
+| --- | --- |
+| $lsx(/) | <div class="alert alert-success"><span style="font-size: x-large;"><i class="icon-check"></i> [Go to Sandbox](/Sandbox)</span></div> $lsx(/Sandbox)|
+
+Slack 
+=====
+
+<a href="https://growi-slackin.weseek.co.jp/"><img src="https://growi-slackin.weseek.co.jp/badge.svg"></a>
+
+让我们加入我们所有人的休闲渠道,帮助成长。
+除了讨论发展,我们在介绍时也接受提问。

+ 9 - 0
rs-i18n.env

@@ -0,0 +1,9 @@
+RS_I18N_BAIDU_APP_ID=
+RS_I18N_BAIDU_APP_SECRET=
+RS_I18N_LOCALES_DIR=./resource/locales
+RS_I18N_BASE_LANGUAGE=en-US
+RS_I18N_TARGET_FILE_SUFFIX= '.json'
+RS_I18N_DEBUG_FORMAT=__{%s}__
+
+RS_I18N_LAN_zh-CN=zh
+RS_I18N_LAN_ja=jp

+ 40 - 36
src/client/js/components/Admin/App/AppSetting.jsx

@@ -26,16 +26,16 @@ class AppSetting extends React.Component {
     try {
       await adminAppContainer.updateAppSettingHandler();
       toastSuccess(t('toaster.update_successed', { target: t('App Settings') }));
-    }
-    catch (err) {
+    } catch (err) {
       toastError(err);
       logger.error(err);
     }
   }
 
   render() {
-    const { t, adminAppContainer } = this.props;
-
+    const { t, adminAppContainer, appContainer } = this.props;
+    const locales = appContainer.locales;
+    const languages = Object.keys(locales);
     return (
       <React.Fragment>
         <div className="form-group row">
@@ -45,7 +45,9 @@ class AppSetting extends React.Component {
               className="form-control"
               type="text"
               defaultValue={adminAppContainer.state.title || ''}
-              onChange={(e) => { adminAppContainer.changeTitle(e.target.value) }}
+              onChange={(e) => {
+                adminAppContainer.changeTitle(e.target.value);
+              }}
               placeholder="GROWI"
             />
             <p className="form-text text-muted">{t('admin:app_setting.sitename_change')}</p>
@@ -53,13 +55,16 @@ class AppSetting extends React.Component {
         </div>
 
         <div className="row form-group mb-5">
-          <label className="text-left text-md-right col-md-3 col-form-label">{t('admin:app_setting.confidential_name')}</label>
+          <label
+            className="text-left text-md-right col-md-3 col-form-label">{t('admin:app_setting.confidential_name')}</label>
           <div className="col-md-6">
             <input
               className="form-control"
               type="text"
               defaultValue={adminAppContainer.state.confidential || ''}
-              onChange={(e) => { adminAppContainer.changeConfidential(e.target.value) }}
+              onChange={(e) => {
+                adminAppContainer.changeConfidential(e.target.value);
+              }}
               placeholder={t('admin:app_setting.confidential_example')}
             />
             <p className="form-text text-muted">{t('admin:app_setting.header_content')}</p>
@@ -67,37 +72,33 @@ class AppSetting extends React.Component {
         </div>
 
         <div className="row form-group mb-5">
-          <label className="text-left text-md-right col-md-3 col-form-label">{t('admin:app_setting.default_language')}</label>
+          <label
+            className="text-left text-md-right col-md-3 col-form-label">{t('admin:app_setting.default_language')}</label>
           <div className="col-md-6">
-            <div className="custom-control custom-radio custom-control-inline">
-              <input
-                type="radio"
-                id="radioLangEn"
-                className="custom-control-input"
-                name="globalLang"
-                value="en-US"
-                checked={adminAppContainer.state.globalLang === 'en-US'}
-                onChange={(e) => { adminAppContainer.changeGlobalLang(e.target.value) }}
-              />
-              <label className="custom-control-label" htmlFor="radioLangEn">{t('English')}</label>
-            </div>
-            <div className="custom-control custom-radio custom-control-inline">
-              <input
-                type="radio"
-                id="radioLangJa"
-                className="custom-control-input"
-                name="globalLang"
-                value="ja"
-                checked={adminAppContainer.state.globalLang === 'ja'}
-                onChange={(e) => { adminAppContainer.changeGlobalLang(e.target.value) }}
-              />
-              <label className="custom-control-label" htmlFor="radioLangJa">{t('Japanese')}</label>
-            </div>
+            {
+              languages.map((lan)=>(
+                <div key={lan} className="custom-control custom-radio custom-control-inline">
+                  <input
+                    type="radio"
+                    id={'radioLang' + lan}
+                    className="custom-control-input"
+                    name="globalLang"
+                    value={lan}
+                    checked={adminAppContainer.state.globalLang === lan}
+                    onChange={(e) => {
+                      adminAppContainer.changeGlobalLang(e.target.value);
+                    }}
+                  />
+                  <label className="custom-control-label" htmlFor={'radioLang' + lan}>{locales[lan]["_conf"]["name"]}</label>
+                </div>
+              ))
+            }
           </div>
         </div>
 
         <div className="row form-group mb-5">
-          <label className="text-left text-md-right col-md-3 col-form-label">{t('admin:app_setting.file_uploading')}</label>
+          <label
+            className="text-left text-md-right col-md-3 col-form-label">{t('admin:app_setting.file_uploading')}</label>
           <div className="col-md-6">
             <div className="custom-control custom-checkbox custom-checkbox-info">
               <input
@@ -106,9 +107,12 @@ class AppSetting extends React.Component {
                 className="custom-control-input"
                 name="fileUpload"
                 checked={adminAppContainer.state.fileUpload}
-                onChange={(e) => { adminAppContainer.changeFileUpload(e.target.checked) }}
+                onChange={(e) => {
+                  adminAppContainer.changeFileUpload(e.target.checked);
+                }}
               />
-              <label className="custom-control-label" htmlFor="cbFileUpload">{t('admin:app_setting.enable_files_except_image')}</label>
+              <label className="custom-control-label"
+                     htmlFor="cbFileUpload">{t('admin:app_setting.enable_files_except_image')}</label>
             </div>
 
             <p className="form-text text-muted">
@@ -117,7 +121,7 @@ class AppSetting extends React.Component {
           </div>
         </div>
 
-        <AdminUpdateButtonRow onClick={this.submitHandler} disabled={adminAppContainer.state.retrieveError != null} />
+        <AdminUpdateButtonRow onClick={this.submitHandler} disabled={adminAppContainer.state.retrieveError != null}/>
       </React.Fragment>
     );
   }

+ 21 - 30
src/client/js/components/InstallerForm.jsx

@@ -44,6 +44,9 @@ class InstallerForm extends React.Component {
       : <span><i className="icon-fw icon-ban" />{ this.props.t('installer.unavaliable_user_id') }</span>;
 
     const checkedBtn = this.state.checkedBtn;
+    const {i18n}=this.props;
+    const locales = i18n.options.resources;
+    const languages = Object.keys(locales);
 
     return (
       <div className={`login-dialog p-3 mx-auto${hasErrorClass}`}>
@@ -58,36 +61,24 @@ class InstallerForm extends React.Component {
         <div className="row">
           <form role="form" action="/installer" method="post" id="register-form" className="col-md-12">
             <div className="form-group text-center">
-              <div className="custom-control custom-radio custom-control-inline">
-                <input
-                  type="radio"
-                  className="custom-control-input"
-                  id="register-form-check-en"
-                  name="registerForm[app:globalLang]"
-                  value="en-US"
-                  checked={checkedBtn === 'en-US'}
-                  inline
-                  onChange={(e) => { if (e.target.checked) { this.changeLanguage('en-US') } }}
-                />
-                <label className="custom-control-label" htmlFor="register-form-check-en">
-                  English
-                </label>
-              </div>
-              <div className="custom-control custom-radio custom-control-inline">
-                <input
-                  type="radio"
-                  className="custom-control-input"
-                  id="register-form-check-jp"
-                  name="registerForm[app:globalLang]"
-                  value="ja"
-                  checked={checkedBtn === 'ja'}
-                  inline
-                  onChange={(e) => { if (e.target.checked) { this.changeLanguage('ja') } }}
-                />
-                <label className="custom-control-label" htmlFor="register-form-check-jp">
-                  日本語
-                </label>
-              </div>
+              {
+                languages.map(lan=>(
+                  <div key={lan} className="custom-control custom-radio custom-control-inline">
+                    <input
+                      type="radio"
+                      className="custom-control-input"
+                      id={'register-form-check-'+lan}
+                      name="registerForm[app:globalLang]"
+                      value={lan}
+                      checked={checkedBtn === lan}
+                      onChange={(e) => { if (e.target.checked) { this.changeLanguage(lan) } }}
+                    />
+                    <label className="custom-control-label" htmlFor={'register-form-check-'+lan}>
+                      {locales[lan]["_conf"]["name"]}
+                    </label>
+                  </div>
+                ))
+              }
             </div>
 
             <div className={`input-group mb-3${hasErrorClass}`}>

+ 18 - 23
src/client/js/components/Me/BasicInfoSettings.jsx

@@ -39,8 +39,10 @@ class BasicInfoSettings extends React.Component {
   }
 
   render() {
-    const { t, personalContainer } = this.props;
+    const { t, personalContainer, appContainer } = this.props;
     const { registrationWhiteList } = personalContainer.state;
+    const locales = appContainer.locales;
+    const languages = Object.keys(locales);
 
     return (
       <Fragment>
@@ -110,28 +112,21 @@ class BasicInfoSettings extends React.Component {
         <div className="form-group row">
           <label className="text-left text-md-right col-md-3 col-form-label">{t('Language')}</label>
           <div className="col-md-6">
-            <div className="custom-control custom-radio custom-control-inline">
-              <input
-                type="radio"
-                id="radioLangEn"
-                className="custom-control-input"
-                name="userForm[lang]"
-                checked={personalContainer.state.lang === 'en-US'}
-                onChange={() => { personalContainer.changeLang('en-US') }}
-              />
-              <label className="custom-control-label" htmlFor="radioLangEn">{t('English')}</label>
-            </div>
-            <div className="custom-control custom-radio custom-control-inline">
-              <input
-                type="radio"
-                id="radioLangJa"
-                className="custom-control-input"
-                name="userForm[lang]"
-                checked={personalContainer.state.lang === 'ja'}
-                onChange={() => { personalContainer.changeLang('ja') }}
-              />
-              <label className="custom-control-label" htmlFor="radioLangJa">{t('Japanese')}</label>
-            </div>
+            {
+              languages.map(lan=>(
+                <div key={lan} className="custom-control custom-radio custom-control-inline">
+                  <input
+                    type="radio"
+                    id={'radioLang'+lan}
+                    className="custom-control-input"
+                    name="userForm[lang]"
+                    checked={personalContainer.state.lang === lan}
+                    onChange={() => { personalContainer.changeLang(lan) }}
+                  />
+                  <label className="custom-control-label" htmlFor={'radioLang'+lan}>{locales[lan]["_conf"]['name']}</label>
+                </div>
+              ))
+            }
           </div>
         </div>
 

+ 1 - 1
src/client/js/services/AppContainer.js

@@ -44,7 +44,7 @@ export default class AppContainer extends Container {
 
     const userlang = body.dataset.userlang;
     this.i18n = i18nFactory(userlang);
-
+    this.locales = this.i18n.options.resources;
     this.containerInstances = {};
     this.componentInstances = {};
     this.rendererInstances = {};

+ 5 - 0
src/server/crowi/index.js

@@ -8,6 +8,7 @@ const CdnResourcesService = require('@commons/service/cdn-resources-service');
 const Xss = require('@commons/service/xss');
 const { getMongoUri } = require('@commons/util/mongoose-utils');
 
+const fs = require('fs');
 const path = require('path');
 
 const sep = path.sep;
@@ -33,6 +34,10 @@ function Crowi(rootdir) {
   this.viewsDir = path.join(this.libDir, 'views') + sep;
   this.resourceDir = path.join(this.rootDir, 'resource') + sep;
   this.localeDir = path.join(this.resourceDir, 'locales') + sep;
+  this.locales = fs.readdirSync(this.localeDir)
+    .filter((filename) => {
+      return fs.statSync(path.join(this.localeDir, filename)).isDirectory();
+    });
   this.tmpDir = path.join(this.rootDir, 'tmp') + sep;
   this.cacheDir = path.join(this.tmpDir, 'cache');
 

+ 1 - 1
src/server/models/user.js

@@ -59,7 +59,7 @@ module.exports = function(crowi) {
     lang: {
       type: String,
       // eslint-disable-next-line no-eval
-      enum: Object.keys(getLanguageLabels()).map((k) => { return eval(k) }),
+      enum: crowi.locales,
       default: LANG_EN_US,
     },
     status: {

+ 1 - 1
src/server/routes/apiv3/app-settings.js

@@ -108,7 +108,7 @@ module.exports = (crowi) => {
     appSetting: [
       body('title').trim(),
       body('confidential'),
-      body('globalLang').isIn(['en-US', 'ja']),
+      body('globalLang').isIn(crowi.locales),
       body('fileUpload').isBoolean(),
     ],
     siteUrlSetting: [

+ 16 - 23
src/server/routes/apiv3/personal-setting.js

@@ -76,7 +76,7 @@ module.exports = (crowi) => {
     personal: [
       body('name').isString().not().isEmpty(),
       body('email').isEmail(),
-      body('lang').isString().isIn(['en-US', 'ja']),
+      body('lang').isString().isIn(crowi.locales),
       body('isEmailPublished').isBoolean(),
     ],
     imageType: [
@@ -122,7 +122,7 @@ module.exports = (crowi) => {
    *                      type: object
    *                      description: personal params
    */
-  router.get('/', accessTokenParser, loginRequiredStrictly, async(req, res) => {
+  router.get('/', accessTokenParser, loginRequiredStrictly, async (req, res) => {
     const currentUser = await User.findUserByUsername(req.user.username);
     return res.apiv3({ currentUser });
   });
@@ -153,7 +153,7 @@ module.exports = (crowi) => {
    *                      type: object
    *                      description: personal params
    */
-  router.put('/', accessTokenParser, loginRequiredStrictly, csrf, validator.personal, ApiV3FormValidator, async(req, res) => {
+  router.put('/', accessTokenParser, loginRequiredStrictly, csrf, validator.personal, ApiV3FormValidator, async (req, res) => {
 
     try {
       const user = await User.findOne({ _id: req.user.id });
@@ -165,8 +165,7 @@ module.exports = (crowi) => {
       const updatedUser = await user.save();
       req.i18n.changeLanguage(req.body.lang);
       return res.apiv3({ updatedUser });
-    }
-    catch (err) {
+    } catch (err) {
       logger.error(err);
       return res.apiv3Err('update-personal-settings-failed');
     }
@@ -193,14 +192,13 @@ module.exports = (crowi) => {
    *                      type: object
    *                      description: user data
    */
-  router.put('/image-type', accessTokenParser, loginRequiredStrictly, csrf, validator.imageType, ApiV3FormValidator, async(req, res) => {
+  router.put('/image-type', accessTokenParser, loginRequiredStrictly, csrf, validator.imageType, ApiV3FormValidator, async (req, res) => {
     const { isGravatarEnabled } = req.body;
 
     try {
       const userData = await req.user.updateIsGravatarEnabled(isGravatarEnabled);
       return res.apiv3({ userData });
-    }
-    catch (err) {
+    } catch (err) {
       logger.error(err);
       return res.apiv3Err('update-personal-settings-failed');
     }
@@ -226,14 +224,13 @@ module.exports = (crowi) => {
    *                      type: object
    *                      description: array of external accounts
    */
-  router.get('/external-accounts', accessTokenParser, loginRequiredStrictly, async(req, res) => {
+  router.get('/external-accounts', accessTokenParser, loginRequiredStrictly, async (req, res) => {
     const userData = req.user;
 
     try {
       const externalAccounts = await ExternalAccount.find({ user: userData });
       return res.apiv3({ externalAccounts });
-    }
-    catch (err) {
+    } catch (err) {
       logger.error(err);
       return res.apiv3Err('get-external-accounts-failed');
     }
@@ -266,7 +263,7 @@ module.exports = (crowi) => {
    *                      type: object
    *                      description: user data updated
    */
-  router.put('/password', accessTokenParser, loginRequiredStrictly, csrf, validator.password, ApiV3FormValidator, async(req, res) => {
+  router.put('/password', accessTokenParser, loginRequiredStrictly, csrf, validator.password, ApiV3FormValidator, async (req, res) => {
     const { body, user } = req;
     const { oldPassword, newPassword } = body;
 
@@ -276,8 +273,7 @@ module.exports = (crowi) => {
     try {
       const userData = await user.updatePassword(newPassword);
       return res.apiv3({ userData });
-    }
-    catch (err) {
+    } catch (err) {
       logger.error(err);
       return res.apiv3Err('update-password-failed');
     }
@@ -304,14 +300,13 @@ module.exports = (crowi) => {
    *                      type: object
    *                      description: user data
    */
-  router.put('/api-token', loginRequiredStrictly, csrf, async(req, res) => {
+  router.put('/api-token', loginRequiredStrictly, csrf, async (req, res) => {
     const { user } = req;
 
     try {
       const userData = await user.updateApiToken();
       return res.apiv3({ userData });
-    }
-    catch (err) {
+    } catch (err) {
       logger.error(err);
       return res.apiv3Err('update-api-token-failed');
     }
@@ -344,7 +339,7 @@ module.exports = (crowi) => {
    *                      type: object
    *                      description: Ldap account associate to me
    */
-  router.put('/associate-ldap', accessTokenParser, loginRequiredStrictly, csrf, validator.associateLdap, ApiV3FormValidator, async(req, res) => {
+  router.put('/associate-ldap', accessTokenParser, loginRequiredStrictly, csrf, validator.associateLdap, ApiV3FormValidator, async (req, res) => {
     const { passportService } = crowi;
     const { user, body } = req;
     const { username } = body;
@@ -358,8 +353,7 @@ module.exports = (crowi) => {
       await passport.authenticate('ldapauth');
       const associateUser = await ExternalAccount.associate('ldap', username, user);
       return res.apiv3({ associateUser });
-    }
-    catch (err) {
+    } catch (err) {
       logger.error(err);
       return res.apiv3Err('associate-ldap-account-failed');
     }
@@ -392,7 +386,7 @@ module.exports = (crowi) => {
    *                      type: object
    *                      description: Ldap account disassociate to me
    */
-  router.put('/disassociate-ldap', accessTokenParser, loginRequiredStrictly, csrf, validator.disassociateLdap, ApiV3FormValidator, async(req, res) => {
+  router.put('/disassociate-ldap', accessTokenParser, loginRequiredStrictly, csrf, validator.disassociateLdap, ApiV3FormValidator, async (req, res) => {
     const { user, body } = req;
     const { providerType, accountId } = body;
 
@@ -404,8 +398,7 @@ module.exports = (crowi) => {
       }
       const disassociateUser = await ExternalAccount.findOneAndRemove({ providerType, accountId, user });
       return res.apiv3({ disassociateUser });
-    }
-    catch (err) {
+    } catch (err) {
       logger.error(err);
       return res.apiv3Err('disassociate-ldap-account-failed');
     }

+ 72 - 3
yarn.lock

@@ -3979,6 +3979,16 @@ commander@^3.0.0:
   resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.1.tgz#4595aec3530525e671fb6f85fb173df8ff8bf57a"
   integrity sha512-UNgvDd+csKdc9GD4zjtkHKQbT8Aspt2jCBqNSPp53vAS0L1tS9sXB2TCEOPHJ7kt9bN/niWkYj8T3RQSoMXdSQ==
 
+commander@^4.0.0:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
+  integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
+
+commander@^5.1.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae"
+  integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==
+
 commondir@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
@@ -4648,6 +4658,16 @@ date-fns@^2.0.0:
   resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.0.0.tgz#52f05c6ae1fe0e395670082c72b690ab781682d0"
   integrity sha512-nGZDA64Ktq5uTWV4LEH3qX+foV4AguT5qxwRlJDzJtf57d4xLNwtwrfb7SzKCoikoae8Bvxf0zdaEG/xWssp/w==
 
+date-format@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/date-format/-/date-format-2.1.0.tgz#31d5b5ea211cf5fd764cd38baf9d033df7e125cf"
+  integrity sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==
+
+date-format@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/date-format/-/date-format-3.0.0.tgz#eb8780365c7d2b1511078fb491e6479780f3ad95"
+  integrity sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w==
+
 date-now@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
@@ -4977,7 +4997,7 @@ dotenv-expand@>=5.1.0:
   resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0"
   integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==
 
-dotenv@>=8.2.0:
+dotenv@>=8.2.0, dotenv@^8.2.0:
   version "8.2.0"
   resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a"
   integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==
@@ -5274,6 +5294,14 @@ env-cmd@^10.0.1:
     commander "^3.0.0"
     cross-spawn "^6.0.0"
 
+env-cmd@^10.1.0:
+  version "10.1.0"
+  resolved "https://registry.yarnpkg.com/env-cmd/-/env-cmd-10.1.0.tgz#c7f5d3b550c9519f137fdac4dd8fb6866a8c8c4b"
+  integrity sha512-mMdWTT9XKN7yNth/6N6g2GuKuJTsKMDHlQFUDacb/heQRRWOTIZ42t1rMHnQu4jYxU1ajdTeJM+9eEETlqToMA==
+  dependencies:
+    commander "^4.0.0"
+    cross-spawn "^7.0.0"
+
 errno@^0.1.3:
   version "0.1.6"
   resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.6.tgz#c386ce8a6283f14fc09563b71560908c9bf53026"
@@ -6203,6 +6231,11 @@ flatted@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.0.tgz#55122b6536ea496b4b44893ee2608141d10d9916"
 
+flatted@^2.0.1:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138"
+  integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==
+
 flatten@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782"
@@ -6340,7 +6373,7 @@ fs-extra@3.0.1:
     jsonfile "^3.0.0"
     universalify "^0.1.0"
 
-fs-extra@8.1.0:
+fs-extra@8.1.0, fs-extra@^8.1.0:
   version "8.1.0"
   resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
   integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==
@@ -8860,6 +8893,17 @@ log-symbols@^3.0.0:
   dependencies:
     chalk "^2.4.2"
 
+log4js@^6.3.0:
+  version "6.3.0"
+  resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.3.0.tgz#10dfafbb434351a3e30277a00b9879446f715bcb"
+  integrity sha512-Mc8jNuSFImQUIateBFwdOQcmC6Q5maU0VVvdC2R6XMb66/VnT+7WS4D/0EeNMZu1YODmJe5NIn2XftCzEocUgw==
+  dependencies:
+    date-format "^3.0.0"
+    debug "^4.1.1"
+    flatted "^2.0.1"
+    rfdc "^1.1.4"
+    streamroller "^2.2.4"
+
 lolex@^5.0.0:
   version "5.1.2"
   resolved "https://registry.yarnpkg.com/lolex/-/lolex-5.1.2.tgz#953694d098ce7c07bc5ed6d0e42bc6c0c6d5a367"
@@ -12407,7 +12451,7 @@ request@^2.74.0:
     tunnel-agent "^0.6.0"
     uuid "^3.1.0"
 
-request@^2.87.0, request@^2.88.0:
+request@^2.87.0, request@^2.88.0, request@^2.88.2:
   version "2.88.2"
   resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
   integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
@@ -12597,6 +12641,11 @@ reveal.js@^3.5.0:
   version "3.6.0"
   resolved "https://registry.yarnpkg.com/reveal.js/-/reveal.js-3.6.0.tgz#ce0e64f30cbebd6e5ce885c2f384085c5e5821e8"
 
+rfdc@^1.1.4:
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.4.tgz#ba72cc1367a0ccd9cf81a870b3b58bd3ad07f8c2"
+  integrity sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==
+
 rgb-regex@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1"
@@ -12654,6 +12703,17 @@ rndm@1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/rndm/-/rndm-1.2.0.tgz#f33fe9cfb52bbfd520aa18323bc65db110a1b76c"
 
+rs-i18n@^0.0.9:
+  version "0.0.9"
+  resolved "https://registry.yarnpkg.com/rs-i18n/-/rs-i18n-0.0.9.tgz#9975aa4ae8c3a7dee2e39b96d1a4b2929261ce4f"
+  integrity sha512-wPg9gaNHpRiuSJm5hJNO2zwqOmC9lRkdp3mr768DuCU3l+Ws+xk5Ut7+x2ekTpJJFe4WJuB/TwGWzZ0cmJ6jUQ==
+  dependencies:
+    commander "^5.1.0"
+    dotenv "^8.2.0"
+    env-cmd "^10.1.0"
+    log4js "^6.3.0"
+    request "^2.88.2"
+
 rsvp@^4.8.4:
   version "4.8.5"
   resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734"
@@ -13576,6 +13636,15 @@ stream-to-promise@^2.2.0:
     end-of-stream "~1.1.0"
     stream-to-array "~2.3.0"
 
+streamroller@^2.2.4:
+  version "2.2.4"
+  resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-2.2.4.tgz#c198ced42db94086a6193608187ce80a5f2b0e53"
+  integrity sha512-OG79qm3AujAM9ImoqgWEY1xG4HX+Lw+yY6qZj9R1K2mhF5bEmQ849wvrb+4vt4jLMLzwXttJlQbOdPOQVRv7DQ==
+  dependencies:
+    date-format "^2.1.0"
+    debug "^4.1.1"
+    fs-extra "^8.1.0"
+
 streamsearch@0.1.2:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"

Некоторые файлы не были показаны из-за большого количества измененных файлов