From 03549455026119224c9d4c7d62f5678753570d99 Mon Sep 17 00:00:00 2001 From: Mikhail Chekan Date: Sun, 10 Oct 2021 11:51:34 +0800 Subject: [PATCH 01/27] Translate more error messages Featuring own fork of go-localize! It's not in dependencies though. --- l18n/l18n.go | 914 +++++++++++++++++++++++--------------------- l18n_src/en/ui.json | 19 + l18n_src/ru/ui.json | 19 + shroom/can.go | 35 +- shroom/delete.go | 5 +- shroom/rename.go | 15 +- shroom/unattach.go | 5 +- shroom/upload.go | 9 +- web/mutators.go | 22 +- 9 files changed, 561 insertions(+), 482 deletions(-) diff --git a/l18n/l18n.go b/l18n/l18n.go index caf228e..5ad1bd2 100644 --- a/l18n/l18n.go +++ b/l18n/l18n.go @@ -1,6 +1,4 @@ // Code generated by go-localize; DO NOT EDIT. -// This file was generated by robots at -// 2021-10-01 01:05:27.197649651 -0400 EDT m=+0.002749572 package l18n @@ -12,446 +10,482 @@ import ( ) var localizations = map[string]string{ - "en.admin.newuser_create": "Create", - "en.admin.newuser_title": "New user", - "en.admin.panel_about": "About this wiki", - "en.admin.panel_reindex": "Reindex hyphae", - "en.admin.panel_safe": "Safe things", - "en.admin.panel_shutdown": "Shutdown wiki", - "en.admin.panel_title": "Administrative functions", - "en.admin.panel_unsafe": "Dangerous things", - "en.admin.panel_updateheader": "Update header links", - "en.admin.panel_userlist": "User list", - "en.admin.panel_users": "Manage users", - "en.admin.user_delete": "Delete", - "en.admin.user_delete_heading": "Delete user", - "en.admin.user_delete_tip": "Remove the user from the database. Changes made by the user will be preserved. It will be possible to take this username later.", - "en.admin.user_delete_warn": "Are you sure you want to delete {{.name}} from the database? This action is irreversible.", - "en.admin.user_group_heading": "Change group", - "en.admin.user_title": "User %s", - "en.admin.user_update": "Update", - "en.admin.users_actions": "Actions", - "en.admin.users_create": "Create a new user", - "en.admin.users_edit": "Edit", - "en.admin.users_group": "Group", - "en.admin.users_name": "Name", - "en.admin.users_notime": "unknown", - "en.admin.users_password": "Password", - "en.admin.users_registered": "Registered at", - "en.admin.users_reindex": "Reindex users", - "en.admin.users_title": "Manage users", - "en.auth.cookie_tip": "By submitting this form you give this wiki a permission to store cookies in your browser. It lets the engine associate your edits with you. You will stay logged in until you log out.", - "en.auth.error_password": "Wrong password.", - "en.auth.error_telegram": "Could not authorize using Telegram.", - "en.auth.error_username": "Unknown username.", - "en.auth.go_back": "Go back", - "en.auth.go_home": "Go home", - "en.auth.go_login": "Go to the login page", - "en.auth.lock_title": "Locked", - "en.auth.login_button": "Log in", - "en.auth.login_header": "Log in to {{.name}}", - "en.auth.login_title": "Login", - "en.auth.logout_anon": "You cannot log out because you are not logged in.", - "en.auth.logout_button": "Confirm", - "en.auth.logout_header": "Log out?", - "en.auth.logout_title": "Logout?", - "en.auth.noauth": "Authentication is disabled. You can make edits anonymously.", - "en.auth.noregister": "Registrations are currently closed. Administrators can make an account for you by hand; contact them.", - "en.auth.password": "Password", - "en.auth.password_tip": "The server stores your password in an encrypted form; even administrators cannot read it.", - "en.auth.register_button": "Register", - "en.auth.register_header": "Register on {{.name}}", - "en.auth.register_title": "Register", - "en.auth.telegram_tip": "You can log in using Telegram. It only works if you have set your @username in Telegram and this username is free on this wiki.", - "en.auth.try_again": "Try again", - "en.auth.username": "Username", - "en.edit.actions": "Actions", - "en.edit.bold": "Bold", - "en.edit.bullets": "Bullet list", - "en.edit.code": "Code block", - "en.edit.date": "Insert current date", - "en.edit.heading": "Heading", - "en.edit.help": "{{.link}} about mycomarkup", - "en.edit.help_link": "Learn more", - "en.edit.highlight": "Highlight", - "en.edit.hr": "Horizontal bar", - "en.edit.italic": "Italic", - "en.edit.link": "Link", - "en.edit.link_title": "Title", - "en.edit.markup": "Markup", - "en.edit.mono": "Monospace", - "en.edit.new_hypha": "You are creating a new hypha.", - "en.edit.numbers": "Number list", - "en.edit.preview": "Preview", - "en.edit.preview_tip": "Note that the hypha hasn't been saved yet. Here's the preview:", - "en.edit.preview_title": "Preview of %s", - "en.edit.rocket": "Rocketlink", - "en.edit.save": "Save", - "en.edit.selflink": "Link yourself", - "en.edit.strike": "Strikethrough", - "en.edit.sub": "Subtext", - "en.edit.super": "Supertext", - "en.edit.tag": "Describe your changes:", - "en.edit.time": "Insert current time", - "en.edit.title": "Edit %s", - "en.edit.transclude": "Transclusion", - "en.edit.underline": "Underline", - "en.help.attachment": "Attachment", - "en.help.configuration": "Configuration (for administrators)", - "en.help.empty_error_line_1": "Try finding a different entry that would help you.", - "en.help.empty_error_line_2a": "If you want to write this entry by yourself, consider", - "en.help.empty_error_line_2b": "to Mycorrhiza Wiki directly.", - "en.help.empty_error_link": "contributing", - "en.help.empty_error_title": "This entry does not exist!", - "en.help.entry_not_found": "Entry not found", - "en.help.hypha": "Hypha", - "en.help.interface": "Interface", - "en.help.lock": "Lock", - "en.help.main": "Main", - "en.help.mycomarkup": "Mycomarkup", - "en.help.prevnext": "Previous/next", - "en.help.recent_changes": "Recent changes", - "en.help.sibling_hyphae": "Sibling hyphae", - "en.help.special_pages": "Special pages", - "en.help.telegram": "Telegram authorization", - "en.help.title": "Help", - "en.help.top_bar": "Top bar", - "en.help.topics": "Help topics", - "en.help.whitelist": "Whitelist", - "en.ui.about_admins": "Administrators:", - "en.ui.about_homepage": "Home page:", - "en.ui.about_hyphae": "See {{.link}} for information about hyphae on this wiki.", - "en.ui.about_noauth": "This wiki does not use authorization", - "en.ui.about_title": "About {{.name}}", - "en.ui.about_usercount": "User count:", - "en.ui.about_version": "{{.pre}}Mycorrhiza Wiki{{.post}} version:", - "en.ui.admin_panel": "Admin panel", - "en.ui.ask_delete": "Delete %s?", - "en.ui.ask_delete_tip": "In this version of Mycorrhiza Wiki you cannot undelete a deleted hypha but the history can still be accessed.", - "en.ui.ask_delete_verb": "delete", - "en.ui.ask_really": "Do you really want to {{.verb}} hypha {{.name}}?", - "en.ui.ask_rename": "Rename %s", - "en.ui.ask_unattach": "Unattach %s?", - "en.ui.ask_unattach_verb": "unattach", - "en.ui.attach_empty": "This hypha has no attachment, you can upload it here.", - "en.ui.attach_include": "Include", - "en.ui.attach_include_tip": "This attachment is an image. To include it in a hypha, use a syntax like this:", - "en.ui.attach_link": "What are attachments?", - "en.ui.attach_new": "Attach", - "en.ui.attach_new_tip": "You can upload a new attachment. Please do not upload too big pictures unless you need to because may not want to wait for big pictures to load.", - "en.ui.attach_remove": "Unattach", - "en.ui.attach_remove_button": "Unattach", - "en.ui.attach_remove_tip": "Please note that you don't have to unattach before uploading a new attachment.", - "en.ui.attach_size_value": "{{.n}} byte%s", - "en.ui.attach_size_value+one": "", + "en.admin.newuser_create": "Create", + "en.admin.newuser_title": "New user", + "en.admin.panel_about": "About this wiki", + "en.admin.panel_reindex": "Reindex hyphae", + "en.admin.panel_safe": "Safe things", + "en.admin.panel_shutdown": "Shutdown wiki", + "en.admin.panel_title": "Administrative functions", + "en.admin.panel_unsafe": "Dangerous things", + "en.admin.panel_updateheader": "Update header links", + "en.admin.panel_userlist": "User list", + "en.admin.panel_users": "Manage users", + "en.admin.user_delete": "Delete", + "en.admin.user_delete_heading": "Delete user", + "en.admin.user_delete_tip": "Remove the user from the database. Changes made by the user will be preserved. It will be possible to take this username later.", + "en.admin.user_delete_warn": "Are you sure you want to delete {{.name}} from the database? This action is irreversible.", + "en.admin.user_group_heading": "Change group", + "en.admin.user_title": "User %s", + "en.admin.user_update": "Update", + "en.admin.users_actions": "Actions", + "en.admin.users_create": "Create a new user", + "en.admin.users_edit": "Edit", + "en.admin.users_group": "Group", + "en.admin.users_name": "Name", + "en.admin.users_notime": "unknown", + "en.admin.users_password": "Password", + "en.admin.users_registered": "Registered at", + "en.admin.users_reindex": "Reindex users", + "en.admin.users_title": "Manage users", + "en.auth.cookie_tip": "By submitting this form you give this wiki a permission to store cookies in your browser. It lets the engine associate your edits with you. You will stay logged in until you log out.", + "en.auth.error_password": "Wrong password.", + "en.auth.error_telegram": "Could not authorize using Telegram.", + "en.auth.error_username": "Unknown username.", + "en.auth.go_back": "Go back", + "en.auth.go_home": "Go home", + "en.auth.go_login": "Go to the login page", + "en.auth.lock_title": "Locked", + "en.auth.login_button": "Log in", + "en.auth.login_header": "Log in to {{.name}}", + "en.auth.login_title": "Login", + "en.auth.logout_anon": "You cannot log out because you are not logged in.", + "en.auth.logout_button": "Confirm", + "en.auth.logout_header": "Log out?", + "en.auth.logout_title": "Logout?", + "en.auth.noauth": "Authentication is disabled. You can make edits anonymously.", + "en.auth.noregister": "Registrations are currently closed. Administrators can make an account for you by hand; contact them.", + "en.auth.password": "Password", + "en.auth.password_tip": "The server stores your password in an encrypted form; even administrators cannot read it.", + "en.auth.register_button": "Register", + "en.auth.register_header": "Register on {{.name}}", + "en.auth.register_title": "Register", + "en.auth.telegram_tip": "You can log in using Telegram. It only works if you have set your @username in Telegram and this username is free on this wiki.", + "en.auth.try_again": "Try again", + "en.auth.username": "Username", + "en.edit.actions": "Actions", + "en.edit.bold": "Bold", + "en.edit.bullets": "Bullet list", + "en.edit.code": "Code block", + "en.edit.date": "Insert current date", + "en.edit.heading": "Heading", + "en.edit.help": "{{.link}} about mycomarkup", + "en.edit.help_link": "Learn more", + "en.edit.highlight": "Highlight", + "en.edit.hr": "Horizontal bar", + "en.edit.italic": "Italic", + "en.edit.link": "Link", + "en.edit.link_title": "Title", + "en.edit.markup": "Markup", + "en.edit.mono": "Monospace", + "en.edit.new_hypha": "You are creating a new hypha.", + "en.edit.numbers": "Number list", + "en.edit.preview": "Preview", + "en.edit.preview_tip": "Note that the hypha hasn't been saved yet. Here's the preview:", + "en.edit.preview_title": "Preview of %s", + "en.edit.rocket": "Rocketlink", + "en.edit.save": "Save", + "en.edit.selflink": "Link yourself", + "en.edit.strike": "Strikethrough", + "en.edit.sub": "Subtext", + "en.edit.super": "Supertext", + "en.edit.tag": "Describe your changes:", + "en.edit.time": "Insert current time", + "en.edit.title": "Edit %s", + "en.edit.transclude": "Transclusion", + "en.edit.underline": "Underline", + "en.help.attachment": "Attachment", + "en.help.configuration": "Configuration (for administrators)", + "en.help.empty_error_line_1": "Try finding a different entry that would help you.", + "en.help.empty_error_line_2a": "If you want to write this entry by yourself, consider", + "en.help.empty_error_line_2b": "to Mycorrhiza Wiki directly.", + "en.help.empty_error_link": "contributing", + "en.help.empty_error_title": "This entry does not exist!", + "en.help.entry_not_found": "Entry not found", + "en.help.hypha": "Hypha", + "en.help.interface": "Interface", + "en.help.lock": "Lock", + "en.help.main": "Main", + "en.help.mycomarkup": "Mycomarkup", + "en.help.prevnext": "Previous/next", + "en.help.recent_changes": "Recent changes", + "en.help.sibling_hyphae": "Sibling hyphae", + "en.help.special_pages": "Special pages", + "en.help.telegram": "Telegram authorization", + "en.help.title": "Help", + "en.help.top_bar": "Top bar", + "en.help.topics": "Help topics", + "en.help.whitelist": "Whitelist", + "en.ui.about_admins": "Administrators:", + "en.ui.about_homepage": "Home page:", + "en.ui.about_hyphae": "See {{.link}} for information about hyphae on this wiki.", + "en.ui.about_noauth": "This wiki does not use authorization", + "en.ui.about_title": "About {{.name}}", + "en.ui.about_usercount": "User count:", + "en.ui.about_version": "{{.pre}}Mycorrhiza Wiki{{.post}} version:", + "en.ui.act_noattachment": "No attachment", + "en.ui.act_noattachment_tip": "Cannot unattach this hypha because it has no attachment", + "en.ui.act_norights": "Not enough rights", + "en.ui.act_norights_attach": "You must be an editor to attach a hypha", + "en.ui.act_norights_delete": "Not enough rights to delete, you must be a moderator", + "en.ui.act_norights_edit": "You must be an editor to edit a hypha", + "en.ui.act_norights_rename": "Not enough rights to rename, you must be a trusted editor", + "en.ui.act_norights_unattach": "Not enough rights to unattach, you must be a trusted editor", + "en.ui.act_notexist": "Does not exist", + "en.ui.act_notexist_delete": "Cannot delete this hypha because it does not exist", + "en.ui.act_notexist_rename": "Cannot rename this hypha because it does not exist", + "en.ui.act_notexist_unattach": "Cannot unattach this hypha because it does not exist", + "en.ui.admin_panel": "Admin panel", + "en.ui.ask_delete": "Delete %s?", + "en.ui.ask_delete_tip": "In this version of Mycorrhiza Wiki you cannot undelete a deleted hypha but the history can still be accessed.", + "en.ui.ask_delete_verb": "delete", + "en.ui.ask_really": "Do you really want to {{.verb}} hypha {{.name}}?", + "en.ui.ask_rename": "Rename %s", + "en.ui.ask_unattach": "Unattach %s?", + "en.ui.ask_unattach_verb": "unattach", + "en.ui.attach_empty": "This hypha has no attachment, you can upload it here.", + "en.ui.attach_include": "Include", + "en.ui.attach_include_tip": "This attachment is an image. To include it in a hypha, use a syntax like this:", + "en.ui.attach_link": "What are attachments?", + "en.ui.attach_new": "Attach", + "en.ui.attach_new_tip": "You can upload a new attachment. Please do not upload too big pictures unless you need to because may not want to wait for big pictures to load.", + "en.ui.attach_remove": "Unattach", + "en.ui.attach_remove_button": "Unattach", + "en.ui.attach_remove_tip": "Please note that you don't have to unattach before uploading a new attachment.", + "en.ui.attach_size_value": "{{.n}} byte%s", + "en.ui.attach_size_value+one": "", "en.ui.attach_size_value+other": "s", - "en.ui.attach_stat": "Stat", - "en.ui.attach_stat_mime": "MIME type:", - "en.ui.attach_stat_size": "File size:", - "en.ui.attach_tip": "You can manage the hypha's attachment on this page.", - "en.ui.attach_title": "Attachment of {{.name}}", - "en.ui.attach_upload": "Upload", - "en.ui.attachment_link": "Manage attachment", - "en.ui.backlinks_desc": "Hyphae which have a link to the selected hypha are listed below.", - "en.ui.backlinks_link": "{{.n}} backlink%s", - "en.ui.backlinks_link+one": "", - "en.ui.backlinks_link+other": "s", - "en.ui.backlinks_query": "Backlinks to ‘{{.query}}’", - "en.ui.backlinks_title": "Backlinks to {{.query}}", - "en.ui.cancel": "Cancel", - "en.ui.close_dialog": "Close this dialog", - "en.ui.confirm": "Confirm", - "en.ui.delete_link": "Delete", - "en.ui.diff_title": "Diff of {{.name}} at {{.rev}}", - "en.ui.edit_link": "Edit text", - "en.ui.error": "Error", - "en.ui.error_go_back": "Go back to the hypha.", - "en.ui.error_text_fetch": "Could not fetch text data", - "en.ui.error_try_again": "Try again", - "en.ui.header_no_rights": "You must be a moderator to update header links.", - "en.ui.history_link": "View history", - "en.ui.history_title": "History of %s", - "en.ui.list_desc": "This wiki has {{.n}} %s.", - "en.ui.list_desc+one": "hypha", - "en.ui.list_desc+other": "hyphae", - "en.ui.list_heading": "List of hyphae", - "en.ui.list_title": "List of pages", - "en.ui.login": "Login", - "en.ui.media_download": "Download media", - "en.ui.media_noaudio": "Your browser does not support audio.", - "en.ui.media_noaudio_link": "Download audio", - "en.ui.media_novideo": "Your browser does not support video.", - "en.ui.media_novideo_link": "Download video", - "en.ui.no_rights": "Not enough rights", - "en.ui.notexist_heading": "This hypha does not exist", - "en.ui.notexist_login": "Log in to your account, if you have one", - "en.ui.notexist_media": "Upload a media", - "en.ui.notexist_media_tip1": "Upload a picture, a video or an audio. Most common formats can be accessed from the browser, others can be only downloaded afterwards. You can write a description for the media later.", - "en.ui.notexist_norights": "You are not authorized to create new hyphae. Here is what you can do:", - "en.ui.notexist_register": "Register a new account", - "en.ui.notexist_write": "Write a text", - "en.ui.notexist_write_button": "Create", - "en.ui.notexist_write_myco": "Mycomarkup", - "en.ui.notexist_write_tip1": "Write a note, a diary, an article, a story or anything textual using {{.myco}}. Full history of edits to the document will be saved.", - "en.ui.notexist_write_tip2": "Make sure to follow this wiki's writing conventions if there are any.", - "en.ui.random_no_hyphae": "There are no hyphae", - "en.ui.random_no_hyphae_tip": "It is impossible to display a random hypha because the wiki does not contain any hyphae", - "en.ui.recent_count_post": "recent changes", - "en.ui.recent_count_pre": "See", - "en.ui.recent_empty": "Could not find any recent changes.", - "en.ui.recent_heading": "Recent Changes", - "en.ui.recent_subscribe": "Subscribe via {{.rss}}, {{.atom}} or {{.json}}", - "en.ui.recent_subscribe_json": "JSON feed", - "en.ui.recent_title": "{{.n}} recent change%s", - "en.ui.recent_title+one": "", - "en.ui.recent_title+other": "s", - "en.ui.register": "Register", - "en.ui.reindex_no_rights": "You must be an admin to reindex hyphae.", - "en.ui.rename_link": "Rename", - "en.ui.rename_recurse": "Rename subhyphae too", - "en.ui.rename_tip": "If you rename this hypha, all incoming links and all relative outcoming links will break. You will also lose all history for the new name. Rename carefully.", - "en.ui.rename_to": "New name", - "en.ui.revision_link": "Get Mycomarkup source of this revision", - "en.ui.revision_no_text": "This hypha had no text at this revision.", - "en.ui.revision_title": "{{.name}} at {{.rev}}", - "en.ui.revision_warning": "Please note that viewing attachments of hyphae is not supported in history for now.", - "en.ui.search_results_desc": "Every hypha name has been compared with the query. Hyphae that have matched the query are listed below.", - "en.ui.search_results_query": "Search results for ‘{{.query}}’", - "en.ui.search_results_title": "Search: {{.query}}", - "en.ui.sibling_hyphae": "Sibling hyphae", - "en.ui.subhyphae": "Subhyphae", - "en.ui.text_link": "View markup", - "en.ui.title_search": "Search by title", - "en.ui.users_admins": "Admins", - "en.ui.users_editors": "Editors", - "en.ui.users_heading": "List of users", - "en.ui.users_moderators": "Moderators", - "en.ui.users_title": "User list", - "ru.admin.newuser_create": "Создать", - "ru.admin.newuser_title": "Новый пользователь", - "ru.admin.panel_about": "Об этой вики", - "ru.admin.panel_reindex": "Переиндексировать гифы", - "ru.admin.panel_safe": "Безопасные действия", - "ru.admin.panel_shutdown": "Отключить вики", - "ru.admin.panel_title": "Панель администратора", - "ru.admin.panel_unsafe": "Опасные действия", - "ru.admin.panel_updateheader": "Обновить ссылки верхней панели", - "ru.admin.panel_userlist": "Список пользователей", - "ru.admin.panel_users": "Менеджер пользователей", - "ru.admin.user_delete": "Удалить", - "ru.admin.user_delete_heading": "Удалить пользователя", - "ru.admin.user_delete_tip": "Удаляет пользователя из базы данных. Правки пользователя будут сохранены. Имя пользователя освободится для повторной регистрации.", - "ru.admin.user_delete_warn": "Вы уверены, что хотите удалить {{.name}} из базы данных? Это действие нельзя отменить.", - "ru.admin.user_group_heading": "Изменить группу", - "ru.admin.user_title": "Пользователь %s", - "ru.admin.user_update": "Обновить", - "ru.admin.users_actions": "Действия", - "ru.admin.users_create": "Создать пользователя", - "ru.admin.users_edit": "Изменить", - "ru.admin.users_group": "Группа", - "ru.admin.users_name": "Имя", - "ru.admin.users_notime": "неизвестно", - "ru.admin.users_password": "Пароль", - "ru.admin.users_registered": "Время создания", - "ru.admin.users_reindex": "Переиндексировать пользователей", - "ru.admin.users_title": "Менеджер пользователей", - "ru.auth.cookie_tip": "Отправляя эту форму, вы разрешаете вики хранить cookie в вашем браузере. Это позволит движку связывать ваши правки с вашей учётной записью. Вы будете авторизованы, пока не выйдете из учётной записи.", - "ru.auth.error_password": "Неверный пароль.", - "ru.auth.error_telegram": "Не удалось авторизоваться через Телеграм.", - "ru.auth.error_username": "Неизвестное имя пользователя.", - "ru.auth.go_back": "Назад", - "ru.auth.go_home": "Домой", - "ru.auth.go_login": "На страницу входа", - "ru.auth.lock_title": "Доступ закрыт", - "ru.auth.login_button": "Войти", - "ru.auth.login_header": "Вход в «{{.name}}»", - "ru.auth.login_title": "Вход", - "ru.auth.logout_anon": "Вы не можете выйти, потому что ещё не вошли.", - "ru.auth.logout_button": "Подтвердить", - "ru.auth.logout_header": "Выйти?", - "ru.auth.logout_title": "Выйти?", - "ru.auth.noauth": "Аутентификация отключена. Вы можете делать правки анонимно.", - "ru.auth.noregister": "Регистрация в текущее время недоступна. Администраторы могут вручную создать вам учётную запись, свяжитесь с ними.", - "ru.auth.password": "Пароль", - "ru.auth.password_tip": "Сервер хранит ваш пароль в зашифрованном виде, даже администраторы не смогут его прочесть.", - "ru.auth.register_button": "Зарегистрироваться", - "ru.auth.register_header": "Регистрация на «{{.name}}»", - "ru.auth.register_title": "Регистрация", - "ru.auth.telegram_tip": "Вы можете войти с помощью Телеграм. Это сработает, если у вашего профиля есть @имя, и оно не занято в этой вики.", - "ru.auth.try_again": "Ещё раз", - "ru.auth.username": "Логин", - "ru.edit.actions": "Действия", - "ru.edit.bold": "Жирный", - "ru.edit.bullets": "Маркир. список", - "ru.edit.code": "Код-блок", - "ru.edit.date": "Текущая дата", - "ru.edit.heading": "Заголовок", - "ru.edit.help": "{{.link}} о микоразметке", - "ru.edit.help_link": "Подробнее", - "ru.edit.highlight": "Выделение", - "ru.edit.hr": "Гориз. черта", - "ru.edit.italic": "Курсив", - "ru.edit.link": "Ссылка", - "ru.edit.link_title": "Текст", - "ru.edit.markup": "Разметка", - "ru.edit.mono": "Моноширинный", - "ru.edit.new_hypha": "Вы создаёте новую гифу.", - "ru.edit.numbers": "Нумер. список", - "ru.edit.preview": "Предпросмотр", - "ru.edit.preview_tip": "Заметьте, эта гифа ещё не сохранена. Вот её предпросмотр:", - "ru.edit.preview_title": "Предпросмотр «%s»", - "ru.edit.rocket": "Ссылка-ракета", - "ru.edit.save": "Сохранить", - "ru.edit.selflink": "Ссылка на вас", - "ru.edit.strike": "Зачёркнутый", - "ru.edit.sub": "Подстрочный", - "ru.edit.super": "Надстрочный", - "ru.edit.tag": "Опишите ваши правки:", - "ru.edit.time": "Текущее время", - "ru.edit.title": "Редактирование «%s»", - "ru.edit.transclude": "Трансклюзия", - "ru.edit.underline": "Подчеркивание", - "ru.help.attachment": "Вложение", - "ru.help.configuration": "Конфигурация (для администраторов)", - "ru.help.empty_error_line_1": "Попробуйте поискать другую страницу, способную вам помочь.", - "ru.help.empty_error_line_2a": "Если вы хотите написать эту страницу сами, будем рады вашим правкам в ", - "ru.help.empty_error_line_2b": "Микоризы.", - "ru.help.empty_error_link": "репозитории", - "ru.help.empty_error_title": "Этой страницы не существует!", - "ru.help.entry_not_found": "Запись не найдена", - "ru.help.hypha": "Гифа", - "ru.help.interface": "Интерфейс", - "ru.help.lock": "Блокировка", - "ru.help.main": "Введение", - "ru.help.mycomarkup": "Микоразметка", - "ru.help.prevnext": "Назад/далее", - "ru.help.recent_changes": "Недавние изменения", - "ru.help.sibling_hyphae": "Гифы-сиблинги", - "ru.help.special_pages": "Специальные страницы", - "ru.help.telegram": "Авторизация через Телеграм", - "ru.help.title": "Справка", - "ru.help.top_bar": "Верхняя панель", - "ru.help.topics": "Темы справки", - "ru.help.whitelist": "Белый список", - "ru.ui.about_admins": "Администраторы:", - "ru.ui.about_homepage": "Домашняя гифа:", - "ru.ui.about_hyphae": "См. {{.link}}, чтобы узнать о гифах в этой вики.", - "ru.ui.about_noauth": "В этой вики нет авторизации", - "ru.ui.about_title": "О вики «{{.name}}»", - "ru.ui.about_usercount": "Число пользователей:", - "ru.ui.about_version": "Версия {{.pre}}Микоризы{{.post}}:", - "ru.ui.admin_panel": "Администрирование", - "ru.ui.ask_delete": "Удалить «%s»?", - "ru.ui.ask_delete_tip": "В этой версии Микоризы нельзя отменить удаление гифы, но её история останется доступной.", - "ru.ui.ask_delete_verb": "удалить", - "ru.ui.ask_really": "Вы действительно хотите {{.verb}} гифу «{{.name}}»?", - "ru.ui.ask_rename": "Переименовать «%s»", - "ru.ui.ask_unattach": "Открепить «%s»?", - "ru.ui.ask_unattach_verb": "открепить", - "ru.ui.attach_empty": "Эта гифа не имеет вложения, здесь вы можете его загрузить.", - "ru.ui.attach_include": "Добавление", - "ru.ui.attach_include_tip": "Это вложение – изображение. Чтобы добавить его в текст гифы, используйте синтаксис ниже:", - "ru.ui.attach_link": "Что такое вложение?", - "ru.ui.attach_new": "Прикрепить", - "ru.ui.attach_new_tip": "Вы можете загрузить новое вложение. Пожалуйста, не загружайте слишком большие изображения без необходимости, чтобы впоследствии не ждать её долгую загрузку.", - "ru.ui.attach_remove": "Открепить", - "ru.ui.attach_remove_button": "Открепить", - "ru.ui.attach_remove_tip": "Заметьте, чтобы заменить вложение, вам не нужно его перед этим откреплять.", - "ru.ui.attach_size_value": "{{.n}} %s", - "ru.ui.attach_size_value+few": "байта", - "ru.ui.attach_size_value+many": "байт", - "ru.ui.attach_size_value+one": "байт", - "ru.ui.attach_stat": "Свойства", - "ru.ui.attach_stat_mime": "MIME-тип:", - "ru.ui.attach_stat_size": "Размер файла:", - "ru.ui.attach_tip": "На этой странице вы можете управлять вложением.", - "ru.ui.attach_title": "Вложение «{{.name}}»", - "ru.ui.attach_upload": "Загрузить", - "ru.ui.attachment_link": "Вложение", - "ru.ui.backlinks_desc": "Ниже перечислены гифы, содержащие ссылку на выбранную гифу.", - "ru.ui.backlinks_link": "{{.n}} %s сюда", - "ru.ui.backlinks_link+few": "ссылки", - "ru.ui.backlinks_link+many": "ссылок", - "ru.ui.backlinks_link+one": "ссылка", - "ru.ui.backlinks_query": "Обратные ссылки на «{{.query}}»", - "ru.ui.backlinks_title": "Обратные ссылки на {{.query}}", - "ru.ui.cancel": "Отмена", - "ru.ui.close_dialog": "Закрыть этот диалог", - "ru.ui.confirm": "Применить", - "ru.ui.delete_link": "Удалить", - "ru.ui.diff_title": "Разница для «{{.name}}» из {{.rev}}", - "ru.ui.edit_link": "Редактировать", - "ru.ui.error": "Ошибка", - "ru.ui.error_go_back": "Вернуться к гифе.", - "ru.ui.error_text_fetch": "Не удалось получить текстовые данные", - "ru.ui.error_try_again": "Попробуйте ещё раз", - "ru.ui.header_no_rights": "Вы должны быть модератором, чтобы обновить ссылки в заголовке.", - "ru.ui.history_link": "История", - "ru.ui.history_title": "История «%s»", - "ru.ui.list_desc": "В этой вики {{.n}} %s.", - "ru.ui.list_desc+few": "гифы", - "ru.ui.list_desc+many": "гиф", - "ru.ui.list_desc+one": "гифа", - "ru.ui.list_heading": "Список гиф", - "ru.ui.list_title": "Список страниц", - "ru.ui.login": "Войти", - "ru.ui.media_download": "Скачать медиа", - "ru.ui.media_noaudio": "Ваш браузер не поддерживает аудио.", - "ru.ui.media_noaudio_link": "Скачать аудио", - "ru.ui.media_novideo": "Ваш браузер не поддерживает видео.", - "ru.ui.media_novideo_link": "Скачать видео", - "ru.ui.no_rights": "Недостаточно прав", - "ru.ui.notexist_heading": "Эта гифа не существует", - "ru.ui.notexist_login": "Войти в свою учётную запись, если она у вас есть", - "ru.ui.notexist_media": "Загрузить медиа", - "ru.ui.notexist_media_tip1": "Загрузите изображение, видео или аудио. Распространённые форматы можно просматривать из браузера, остальные – просто скачать. Позже вы можете дописать пояснение к этому медиа.", - "ru.ui.notexist_norights": "У вас нет прав для создания новых гиф. Вы можете:", - "ru.ui.notexist_register": "Создать новую учётную запись", - "ru.ui.notexist_write": "Написать текст", - "ru.ui.notexist_write_button": "Создать", - "ru.ui.notexist_write_myco": "микоразметки", - "ru.ui.notexist_write_tip1": "Напишите заметку, дневник, статью, рассказ или иной текст с помощью {{.myco}}. Сохраняется полная история правок документа.", - "ru.ui.notexist_write_tip2": "Не забывайте следовать правилам оформления этой вики, если они имеются.", - "ru.ui.random_no_hyphae": "В этой вики нет гиф", - "ru.ui.random_no_hyphae_tip": "Невозможно отобразить случайную гифу, потому что вики не содержит ни одной гифы", - "ru.ui.recent_count_post": "недавних изменений", - "ru.ui.recent_count_pre": "Отобразить", - "ru.ui.recent_empty": "Не удалось найти последние изменения.", - "ru.ui.recent_heading": "Недавние изменения", - "ru.ui.recent_subscribe": "Подписаться через {{.rss}}, {{.atom}} или {{.json}}", - "ru.ui.recent_subscribe_json": "JSON-ленту", - "ru.ui.recent_title": "{{.n}} %s", - "ru.ui.recent_title+few": "недавних изменения", - "ru.ui.recent_title+many": "недавних изменений", - "ru.ui.recent_title+one": "недавнее изменение", - "ru.ui.register": "Регистрация", - "ru.ui.reindex_no_rights": "Вы должны быть администратором, чтобы переиндексировать гифы.", - "ru.ui.rename_link": "Переименовать", - "ru.ui.rename_recurse": "Также переименовать подгифы", - "ru.ui.rename_tip": "Если вы переименуете эту гифу, сломаются все ссылки, ведущие на неё, а также исходящие относительные ссылки. Также вы потеряете всю текущую историю для нового названия. Переименовывайте аккуратно.", - "ru.ui.rename_to": "Новое название", - "ru.ui.revision_link": "Посмотреть код микоразметки для этой ревизии", - "ru.ui.revision_no_text": "В этой ревизии гифы не было текста.", - "ru.ui.revision_title": "{{.name}} из {{.rev}}", - "ru.ui.revision_warning": "Обратите внимание, просмотр вложений в истории гифы пока что недоступен.", - "ru.ui.search_results_desc": "Название каждой из существующих гиф сопоставлено с запросом. Подходящие гифы приведены ниже.", - "ru.ui.search_results_query": "Результаты поиска для «{{.query}}»", - "ru.ui.search_results_title": "Поиск: {{.query}}", - "ru.ui.sibling_hyphae": "Гифы-сиблинги", - "ru.ui.subhyphae": "Подгифы", - "ru.ui.text_link": "Посмотреть разметку", - "ru.ui.title_search": "Поиск по названию", - "ru.ui.users_admins": "Администраторы", - "ru.ui.users_editors": "Редакторы", - "ru.ui.users_heading": "Список пользователей", - "ru.ui.users_moderators": "Модераторы", - "ru.ui.users_title": "Список пользователей", + "en.ui.attach_stat": "Stat", + "en.ui.attach_stat_mime": "MIME type:", + "en.ui.attach_stat_size": "File size:", + "en.ui.attach_tip": "You can manage the hypha's attachment on this page.", + "en.ui.attach_title": "Attachment of {{.name}}", + "en.ui.attach_upload": "Upload", + "en.ui.attachment_link": "Manage attachment", + "en.ui.backlinks_desc": "Hyphae which have a link to the selected hypha are listed below.", + "en.ui.backlinks_link": "{{.n}} backlink%s", + "en.ui.backlinks_link+one": "", + "en.ui.backlinks_link+other": "s", + "en.ui.backlinks_query": "Backlinks to ‘{{.query}}’", + "en.ui.backlinks_title": "Backlinks to {{.query}}", + "en.ui.cancel": "Cancel", + "en.ui.close_dialog": "Close this dialog", + "en.ui.confirm": "Confirm", + "en.ui.delete_link": "Delete", + "en.ui.diff_title": "Diff of {{.name}} at {{.rev}}", + "en.ui.edit_link": "Edit text", + "en.ui.error": "Error", + "en.ui.error_go_back": "Go back to the hypha.", + "en.ui.error_text_fetch": "Could not fetch text data", + "en.ui.error_try_again": "Try again", + "en.ui.header_no_rights": "You must be a moderator to update header links.", + "en.ui.history_link": "View history", + "en.ui.history_title": "History of %s", + "en.ui.list_desc": "This wiki has {{.n}} %s.", + "en.ui.list_desc+one": "hypha", + "en.ui.list_desc+other": "hyphae", + "en.ui.list_heading": "List of hyphae", + "en.ui.list_title": "List of pages", + "en.ui.login": "Login", + "en.ui.media_download": "Download media", + "en.ui.media_noaudio": "Your browser does not support audio.", + "en.ui.media_noaudio_link": "Download audio", + "en.ui.media_novideo": "Your browser does not support video.", + "en.ui.media_novideo_link": "Download video", + "en.ui.no_rights": "Not enough rights", + "en.ui.notexist_heading": "This hypha does not exist", + "en.ui.notexist_login": "Log in to your account, if you have one", + "en.ui.notexist_media": "Upload a media", + "en.ui.notexist_media_tip1": "Upload a picture, a video or an audio. Most common formats can be accessed from the browser, others can be only downloaded afterwards. You can write a description for the media later.", + "en.ui.notexist_norights": "You are not authorized to create new hyphae. Here is what you can do:", + "en.ui.notexist_register": "Register a new account", + "en.ui.notexist_write": "Write a text", + "en.ui.notexist_write_button": "Create", + "en.ui.notexist_write_myco": "Mycomarkup", + "en.ui.notexist_write_tip1": "Write a note, a diary, an article, a story or anything textual using {{.myco}}. Full history of edits to the document will be saved.", + "en.ui.notexist_write_tip2": "Make sure to follow this wiki's writing conventions if there are any.", + "en.ui.random_no_hyphae": "There are no hyphae", + "en.ui.random_no_hyphae_tip": "It is impossible to display a random hypha because the wiki does not contain any hyphae", + "en.ui.recent_count_post": "recent changes", + "en.ui.recent_count_pre": "See", + "en.ui.recent_empty": "Could not find any recent changes.", + "en.ui.recent_heading": "Recent Changes", + "en.ui.recent_subscribe": "Subscribe via {{.rss}}, {{.atom}} or {{.json}}", + "en.ui.recent_subscribe_json": "JSON feed", + "en.ui.recent_title": "{{.n}} recent change%s", + "en.ui.recent_title+one": "", + "en.ui.recent_title+other": "s", + "en.ui.register": "Register", + "en.ui.reindex_no_rights": "You must be an admin to reindex hyphae.", + "en.ui.rename_badname": "Invalid name", + "en.ui.rename_badname_tip": "Invalid new name. Names cannot contain characters {{.chars}}.", + "en.ui.rename_link": "Rename", + "en.ui.rename_noname": "No name given", + "en.ui.rename_noname_tip": "No new name is given", + "en.ui.rename_recurse": "Rename subhyphae too", + "en.ui.rename_taken": "Name taken", + "en.ui.rename_taken_tip": "Hypha named {{.name}} already exists, cannot rename", + "en.ui.rename_tip": "If you rename this hypha, all incoming links and all relative outcoming links will break. You will also lose all history for the new name. Rename carefully.", + "en.ui.rename_to": "New name", + "en.ui.revision_link": "Get Mycomarkup source of this revision", + "en.ui.revision_no_text": "This hypha had no text at this revision.", + "en.ui.revision_title": "{{.name}} at {{.rev}}", + "en.ui.revision_warning": "Please note that viewing attachments of hyphae is not supported in history for now.", + "en.ui.search_results_desc": "Every hypha name has been compared with the query. Hyphae that have matched the query are listed below.", + "en.ui.search_results_query": "Search results for ‘{{.query}}’", + "en.ui.search_results_title": "Search: {{.query}}", + "en.ui.sibling_hyphae": "Sibling hyphae", + "en.ui.subhyphae": "Subhyphae", + "en.ui.text_link": "View markup", + "en.ui.title_search": "Search by title", + "en.ui.users_admins": "Admins", + "en.ui.users_editors": "Editors", + "en.ui.users_heading": "List of users", + "en.ui.users_moderators": "Moderators", + "en.ui.users_title": "User list", + "ru.admin.newuser_create": "Создать", + "ru.admin.newuser_title": "Новый пользователь", + "ru.admin.panel_about": "Об этой вики", + "ru.admin.panel_reindex": "Переиндексировать гифы", + "ru.admin.panel_safe": "Безопасные действия", + "ru.admin.panel_shutdown": "Отключить вики", + "ru.admin.panel_title": "Панель администратора", + "ru.admin.panel_unsafe": "Опасные действия", + "ru.admin.panel_updateheader": "Обновить ссылки верхней панели", + "ru.admin.panel_userlist": "Список пользователей", + "ru.admin.panel_users": "Менеджер пользователей", + "ru.admin.user_delete": "Удалить", + "ru.admin.user_delete_heading": "Удалить пользователя", + "ru.admin.user_delete_tip": "Удаляет пользователя из базы данных. Правки пользователя будут сохранены. Имя пользователя освободится для повторной регистрации.", + "ru.admin.user_delete_warn": "Вы уверены, что хотите удалить {{.name}} из базы данных? Это действие нельзя отменить.", + "ru.admin.user_group_heading": "Изменить группу", + "ru.admin.user_title": "Пользователь %s", + "ru.admin.user_update": "Обновить", + "ru.admin.users_actions": "Действия", + "ru.admin.users_create": "Создать пользователя", + "ru.admin.users_edit": "Изменить", + "ru.admin.users_group": "Группа", + "ru.admin.users_name": "Имя", + "ru.admin.users_notime": "неизвестно", + "ru.admin.users_password": "Пароль", + "ru.admin.users_registered": "Время создания", + "ru.admin.users_reindex": "Переиндексировать пользователей", + "ru.admin.users_title": "Менеджер пользователей", + "ru.auth.cookie_tip": "Отправляя эту форму, вы разрешаете вики хранить cookie в вашем браузере. Это позволит движку связывать ваши правки с вашей учётной записью. Вы будете авторизованы, пока не выйдете из учётной записи.", + "ru.auth.error_password": "Неверный пароль.", + "ru.auth.error_telegram": "Не удалось авторизоваться через Телеграм.", + "ru.auth.error_username": "Неизвестное имя пользователя.", + "ru.auth.go_back": "Назад", + "ru.auth.go_home": "Домой", + "ru.auth.go_login": "На страницу входа", + "ru.auth.lock_title": "Доступ закрыт", + "ru.auth.login_button": "Войти", + "ru.auth.login_header": "Вход в «{{.name}}»", + "ru.auth.login_title": "Вход", + "ru.auth.logout_anon": "Вы не можете выйти, потому что ещё не вошли.", + "ru.auth.logout_button": "Подтвердить", + "ru.auth.logout_header": "Выйти?", + "ru.auth.logout_title": "Выйти?", + "ru.auth.noauth": "Аутентификация отключена. Вы можете делать правки анонимно.", + "ru.auth.noregister": "Регистрация в текущее время недоступна. Администраторы могут вручную создать вам учётную запись, свяжитесь с ними.", + "ru.auth.password": "Пароль", + "ru.auth.password_tip": "Сервер хранит ваш пароль в зашифрованном виде, даже администраторы не смогут его прочесть.", + "ru.auth.register_button": "Зарегистрироваться", + "ru.auth.register_header": "Регистрация на «{{.name}}»", + "ru.auth.register_title": "Регистрация", + "ru.auth.telegram_tip": "Вы можете войти с помощью Телеграм. Это сработает, если у вашего профиля есть @имя, и оно не занято в этой вики.", + "ru.auth.try_again": "Ещё раз", + "ru.auth.username": "Логин", + "ru.edit.actions": "Действия", + "ru.edit.bold": "Жирный", + "ru.edit.bullets": "Маркир. список", + "ru.edit.code": "Код-блок", + "ru.edit.date": "Текущая дата", + "ru.edit.heading": "Заголовок", + "ru.edit.help": "{{.link}} о микоразметке", + "ru.edit.help_link": "Подробнее", + "ru.edit.highlight": "Выделение", + "ru.edit.hr": "Гориз. черта", + "ru.edit.italic": "Курсив", + "ru.edit.link": "Ссылка", + "ru.edit.link_title": "Текст", + "ru.edit.markup": "Разметка", + "ru.edit.mono": "Моноширинный", + "ru.edit.new_hypha": "Вы создаёте новую гифу.", + "ru.edit.numbers": "Нумер. список", + "ru.edit.preview": "Предпросмотр", + "ru.edit.preview_tip": "Заметьте, эта гифа ещё не сохранена. Вот её предпросмотр:", + "ru.edit.preview_title": "Предпросмотр «%s»", + "ru.edit.rocket": "Ссылка-ракета", + "ru.edit.save": "Сохранить", + "ru.edit.selflink": "Ссылка на вас", + "ru.edit.strike": "Зачёркнутый", + "ru.edit.sub": "Подстрочный", + "ru.edit.super": "Надстрочный", + "ru.edit.tag": "Опишите ваши правки:", + "ru.edit.time": "Текущее время", + "ru.edit.title": "Редактирование «%s»", + "ru.edit.transclude": "Трансклюзия", + "ru.edit.underline": "Подчеркивание", + "ru.help.attachment": "Вложение", + "ru.help.configuration": "Конфигурация (для администраторов)", + "ru.help.empty_error_line_1": "Попробуйте поискать другую страницу, способную вам помочь.", + "ru.help.empty_error_line_2a": "Если вы хотите написать эту страницу сами, будем рады вашим правкам в ", + "ru.help.empty_error_line_2b": "Микоризы.", + "ru.help.empty_error_link": "репозитории", + "ru.help.empty_error_title": "Этой страницы не существует!", + "ru.help.entry_not_found": "Запись не найдена", + "ru.help.hypha": "Гифа", + "ru.help.interface": "Интерфейс", + "ru.help.lock": "Блокировка", + "ru.help.main": "Введение", + "ru.help.mycomarkup": "Микоразметка", + "ru.help.prevnext": "Назад/далее", + "ru.help.recent_changes": "Недавние изменения", + "ru.help.sibling_hyphae": "Гифы-сиблинги", + "ru.help.special_pages": "Специальные страницы", + "ru.help.telegram": "Авторизация через Телеграм", + "ru.help.title": "Справка", + "ru.help.top_bar": "Верхняя панель", + "ru.help.topics": "Темы справки", + "ru.help.whitelist": "Белый список", + "ru.ui.about_admins": "Администраторы:", + "ru.ui.about_homepage": "Домашняя гифа:", + "ru.ui.about_hyphae": "См. {{.link}}, чтобы узнать о гифах в этой вики.", + "ru.ui.about_noauth": "В этой вики нет авторизации", + "ru.ui.about_title": "О вики «{{.name}}»", + "ru.ui.about_usercount": "Число пользователей:", + "ru.ui.about_version": "Версия {{.pre}}Микоризы{{.post}}:", + "ru.ui.act_noattachment": "Нет вложения", + "ru.ui.act_noattachment_tip": "Не могу открепить гифу, потому что в ней нет вложения", + "ru.ui.act_norights": "Недостаточно прав", + "ru.ui.act_norights_attach": "Вы должны быть редактором, чтобы прикреплять гифы", + "ru.ui.act_norights_delete": "Недостаточно прав для удаления, вы должны быть модератором", + "ru.ui.act_norights_edit": "Вы должны быть редактором, чтобы редактировать гифы", + "ru.ui.act_norights_rename": "Недостаточно прав для переименования, вы должны быть доверенным редактором", + "ru.ui.act_norights_unattach": "Недостаточно прав для открепления, вы должны быть доверенным редактором", + "ru.ui.act_notexist": "Не существует", + "ru.ui.act_notexist_delete": "Нельзя удалить эту гифу, потому что она не существует", + "ru.ui.act_notexist_rename": "Нельзя переименовать эту гифу, потому что она не существует", + "ru.ui.act_notexist_unattach": "Нельзя открепить эту гифу, потому что она не существует", + "ru.ui.admin_panel": "Администрирование", + "ru.ui.ask_delete": "Удалить «%s»?", + "ru.ui.ask_delete_tip": "В этой версии Микоризы нельзя отменить удаление гифы, но её история останется доступной.", + "ru.ui.ask_delete_verb": "удалить", + "ru.ui.ask_really": "Вы действительно хотите {{.verb}} гифу «{{.name}}»?", + "ru.ui.ask_rename": "Переименовать «%s»", + "ru.ui.ask_unattach": "Открепить «%s»?", + "ru.ui.ask_unattach_verb": "открепить", + "ru.ui.attach_empty": "Эта гифа не имеет вложения, здесь вы можете его загрузить.", + "ru.ui.attach_include": "Добавление", + "ru.ui.attach_include_tip": "Это вложение – изображение. Чтобы добавить его в текст гифы, используйте синтаксис ниже:", + "ru.ui.attach_link": "Что такое вложение?", + "ru.ui.attach_new": "Прикрепить", + "ru.ui.attach_new_tip": "Вы можете загрузить новое вложение. Пожалуйста, не загружайте слишком большие изображения без необходимости, чтобы впоследствии не ждать её долгую загрузку.", + "ru.ui.attach_remove": "Открепить", + "ru.ui.attach_remove_button": "Открепить", + "ru.ui.attach_remove_tip": "Заметьте, чтобы заменить вложение, вам не нужно его перед этим откреплять.", + "ru.ui.attach_size_value": "{{.n}} %s", + "ru.ui.attach_size_value+few": "байта", + "ru.ui.attach_size_value+many": "байт", + "ru.ui.attach_size_value+one": "байт", + "ru.ui.attach_stat": "Свойства", + "ru.ui.attach_stat_mime": "MIME-тип:", + "ru.ui.attach_stat_size": "Размер файла:", + "ru.ui.attach_tip": "На этой странице вы можете управлять вложением.", + "ru.ui.attach_title": "Вложение «{{.name}}»", + "ru.ui.attach_upload": "Загрузить", + "ru.ui.attachment_link": "Вложение", + "ru.ui.backlinks_desc": "Ниже перечислены гифы, содержащие ссылку на выбранную гифу.", + "ru.ui.backlinks_link": "{{.n}} %s сюда", + "ru.ui.backlinks_link+few": "ссылки", + "ru.ui.backlinks_link+many": "ссылок", + "ru.ui.backlinks_link+one": "ссылка", + "ru.ui.backlinks_query": "Обратные ссылки на «{{.query}}»", + "ru.ui.backlinks_title": "Обратные ссылки на {{.query}}", + "ru.ui.cancel": "Отмена", + "ru.ui.close_dialog": "Закрыть этот диалог", + "ru.ui.confirm": "Применить", + "ru.ui.delete_link": "Удалить", + "ru.ui.diff_title": "Разница для «{{.name}}» из {{.rev}}", + "ru.ui.edit_link": "Редактировать", + "ru.ui.error": "Ошибка", + "ru.ui.error_go_back": "Вернуться к гифе.", + "ru.ui.error_text_fetch": "Не удалось получить текстовые данные", + "ru.ui.error_try_again": "Попробуйте ещё раз", + "ru.ui.header_no_rights": "Вы должны быть модератором, чтобы обновить ссылки в заголовке.", + "ru.ui.history_link": "История", + "ru.ui.history_title": "История «%s»", + "ru.ui.list_desc": "В этой вики {{.n}} %s.", + "ru.ui.list_desc+few": "гифы", + "ru.ui.list_desc+many": "гиф", + "ru.ui.list_desc+one": "гифа", + "ru.ui.list_heading": "Список гиф", + "ru.ui.list_title": "Список страниц", + "ru.ui.login": "Войти", + "ru.ui.media_download": "Скачать медиа", + "ru.ui.media_noaudio": "Ваш браузер не поддерживает аудио.", + "ru.ui.media_noaudio_link": "Скачать аудио", + "ru.ui.media_novideo": "Ваш браузер не поддерживает видео.", + "ru.ui.media_novideo_link": "Скачать видео", + "ru.ui.no_rights": "Недостаточно прав", + "ru.ui.notexist_heading": "Эта гифа не существует", + "ru.ui.notexist_login": "Войти в свою учётную запись, если она у вас есть", + "ru.ui.notexist_media": "Загрузить медиа", + "ru.ui.notexist_media_tip1": "Загрузите изображение, видео или аудио. Распространённые форматы можно просматривать из браузера, остальные – просто скачать. Позже вы можете дописать пояснение к этому медиа.", + "ru.ui.notexist_norights": "У вас нет прав для создания новых гиф. Вы можете:", + "ru.ui.notexist_register": "Создать новую учётную запись", + "ru.ui.notexist_write": "Написать текст", + "ru.ui.notexist_write_button": "Создать", + "ru.ui.notexist_write_myco": "микоразметки", + "ru.ui.notexist_write_tip1": "Напишите заметку, дневник, статью, рассказ или иной текст с помощью {{.myco}}. Сохраняется полная история правок документа.", + "ru.ui.notexist_write_tip2": "Не забывайте следовать правилам оформления этой вики, если они имеются.", + "ru.ui.random_no_hyphae": "В этой вики нет гиф", + "ru.ui.random_no_hyphae_tip": "Невозможно отобразить случайную гифу, потому что вики не содержит ни одной гифы", + "ru.ui.recent_count_post": "недавних изменений", + "ru.ui.recent_count_pre": "Отобразить", + "ru.ui.recent_empty": "Не удалось найти последние изменения.", + "ru.ui.recent_heading": "Недавние изменения", + "ru.ui.recent_subscribe": "Подписаться через {{.rss}}, {{.atom}} или {{.json}}", + "ru.ui.recent_subscribe_json": "JSON-ленту", + "ru.ui.recent_title": "{{.n}} %s", + "ru.ui.recent_title+few": "недавних изменения", + "ru.ui.recent_title+many": "недавних изменений", + "ru.ui.recent_title+one": "недавнее изменение", + "ru.ui.register": "Регистрация", + "ru.ui.reindex_no_rights": "Вы должны быть администратором, чтобы переиндексировать гифы.", + "ru.ui.rename_badname": "Некорректное название", + "ru.ui.rename_badname_tip": "Новое название некорректно. Названия не могут содержать символы {{.chars}}.", + "ru.ui.rename_link": "Переименовать", + "ru.ui.rename_noname": "Нет названия", + "ru.ui.rename_noname_tip": "Не задано новое название", + "ru.ui.rename_recurse": "Также переименовать подгифы", + "ru.ui.rename_taken": "Название занято", + "ru.ui.rename_taken_tip": "Гифа {{.name}} уже существует, не могу переименовать", + "ru.ui.rename_tip": "Если вы переименуете эту гифу, сломаются все ссылки, ведущие на неё, а также исходящие относительные ссылки. Также вы потеряете всю текущую историю для нового названия. Переименовывайте аккуратно.", + "ru.ui.rename_to": "Новое название", + "ru.ui.revision_link": "Посмотреть код микоразметки для этой ревизии", + "ru.ui.revision_no_text": "В этой ревизии гифы не было текста.", + "ru.ui.revision_title": "{{.name}} из {{.rev}}", + "ru.ui.revision_warning": "Обратите внимание, просмотр вложений в истории гифы пока что недоступен.", + "ru.ui.search_results_desc": "Название каждой из существующих гиф сопоставлено с запросом. Подходящие гифы приведены ниже.", + "ru.ui.search_results_query": "Результаты поиска для «{{.query}}»", + "ru.ui.search_results_title": "Поиск: {{.query}}", + "ru.ui.sibling_hyphae": "Гифы-сиблинги", + "ru.ui.subhyphae": "Подгифы", + "ru.ui.text_link": "Посмотреть разметку", + "ru.ui.title_search": "Поиск по названию", + "ru.ui.users_admins": "Администраторы", + "ru.ui.users_editors": "Редакторы", + "ru.ui.users_heading": "Список пользователей", + "ru.ui.users_moderators": "Модераторы", + "ru.ui.users_title": "Список пользователей", } type Replacements map[string]interface{} type Localizer struct { - Locale string + Locale string FallbackLocale string Localizations map[string]string } @@ -487,11 +521,11 @@ func (t Localizer) GetWithLocale(locale, key string, replacements ...*Replacemen } } - // If the str doesn't have any substitutions, no need to - // template.Execute. + // If the str doesn't have any substitutions, no need to + // template.Execute. if strings.Index(str, "}}") == -1 { - return str - } + return str + } return t.replace(str, replacements...) } diff --git a/l18n_src/en/ui.json b/l18n_src/en/ui.json index 7126c98..ac59aff 100644 --- a/l18n_src/en/ui.json +++ b/l18n_src/en/ui.json @@ -43,6 +43,25 @@ "rename_to": "New name", "rename_recurse": "Rename subhyphae too", "rename_tip": "If you rename this hypha, all incoming links and all relative outcoming links will break. You will also lose all history for the new name. Rename carefully.", + "rename_taken": "Name taken", + "rename_taken_tip": "Hypha named {{.name}} already exists, cannot rename", + "rename_noname": "No name given", + "rename_noname_tip": "No new name is given", + "rename_badname": "Invalid name", + "rename_badname_tip": "Invalid new name. Names cannot contain characters {{.chars}}.", + + "act_noattachment": "No attachment", + "act_noattachment_tip": "Cannot unattach this hypha because it has no attachment", + "act_norights": "Not enough rights", + "act_notexist": "Does not exist", + "act_norights_delete": "Not enough rights to delete, you must be a moderator", + "act_notexist_delete": "Cannot delete this hypha because it does not exist", + "act_norights_rename": "Not enough rights to rename, you must be a trusted editor", + "act_notexist_rename": "Cannot rename this hypha because it does not exist", + "act_norights_unattach": "Not enough rights to unattach, you must be a trusted editor", + "act_notexist_unattach": "Cannot unattach this hypha because it does not exist", + "act_norights_edit": "You must be an editor to edit a hypha", + "act_norights_attach": "You must be an editor to attach a hypha", "ask_delete": "Delete %s?", "ask_delete_tip": "In this version of Mycorrhiza Wiki you cannot undelete a deleted hypha but the history can still be accessed.", diff --git a/l18n_src/ru/ui.json b/l18n_src/ru/ui.json index 949e2e5..27a4555 100644 --- a/l18n_src/ru/ui.json +++ b/l18n_src/ru/ui.json @@ -45,6 +45,25 @@ "rename_to": "Новое название", "rename_recurse": "Также переименовать подгифы", "rename_tip": "Если вы переименуете эту гифу, сломаются все ссылки, ведущие на неё, а также исходящие относительные ссылки. Также вы потеряете всю текущую историю для нового названия. Переименовывайте аккуратно.", + "rename_taken": "Название занято", + "rename_taken_tip": "Гифа {{.name}} уже существует, не могу переименовать", + "rename_noname": "Нет названия", + "rename_noname_tip": "Не задано новое название", + "rename_badname": "Некорректное название", + "rename_badname_tip": "Новое название некорректно. Названия не могут содержать символы {{.chars}}.", + + "act_noattachment": "Нет вложения", + "act_noattachment_tip": "Не могу открепить гифу, потому что в ней нет вложения", + "act_norights": "Недостаточно прав", + "act_notexist": "Не существует", + "act_norights_delete": "Недостаточно прав для удаления, вы должны быть модератором", + "act_norights_rename": "Недостаточно прав для переименования, вы должны быть доверенным редактором", + "act_norights_unattach": "Недостаточно прав для открепления, вы должны быть доверенным редактором", + "act_norights_edit": "Вы должны быть редактором, чтобы редактировать гифы", + "act_norights_attach": "Вы должны быть редактором, чтобы прикреплять гифы", + "act_notexist_delete": "Нельзя удалить эту гифу, потому что она не существует", + "act_notexist_rename": "Нельзя переименовать эту гифу, потому что она не существует", + "act_notexist_unattach": "Нельзя открепить эту гифу, потому что она не существует", "ask_delete": "Удалить «%s»?", "ask_delete_tip": "В этой версии Микоризы нельзя отменить удаление гифы, но её история останется доступной.", diff --git a/shroom/can.go b/shroom/can.go index 0cf61db..114dc7c 100644 --- a/shroom/can.go +++ b/shroom/can.go @@ -4,32 +4,33 @@ import ( "errors" "github.com/bouncepaw/mycorrhiza/hyphae" + "github.com/bouncepaw/mycorrhiza/l18n" "github.com/bouncepaw/mycorrhiza/user" ) func canFactory( rejectLogger func(*hyphae.Hypha, *user.User, string), action string, - dispatcher func(*hyphae.Hypha, *user.User) (string, string), + dispatcher func(*hyphae.Hypha, *user.User, *l18n.Localizer) (string, string), noRightsMsg string, notExistsMsg string, careAboutExistence bool, -) func(*user.User, *hyphae.Hypha) (string, error) { - return func(u *user.User, h *hyphae.Hypha) (string, error) { +) func(*user.User, *hyphae.Hypha, *l18n.Localizer) (string, error) { + return func(u *user.User, h *hyphae.Hypha, lc *l18n.Localizer) (string, error) { if !u.CanProceed(action) { rejectLogger(h, u, "no rights") - return "Not enough rights", errors.New(noRightsMsg) + return lc.Get("ui.act_no_rights"), errors.New(lc.Get(noRightsMsg)) } if careAboutExistence && !h.Exists { rejectLogger(h, u, "does not exist") - return "Does not exist", errors.New(notExistsMsg) + return lc.Get("ui.act_notexist"), errors.New(lc.Get(notExistsMsg)) } if dispatcher == nil { return "", nil } - errmsg, errtitle := dispatcher(h, u) + errmsg, errtitle := dispatcher(h, u, lc) if errtitle == "" { return "", nil } @@ -43,8 +44,8 @@ var ( rejectDeleteLog, "delete-confirm", nil, - "Not enough rights to delete, you must be a moderator", - "Cannot delete this hypha because it does not exist", + "ui.act_norights_delete", + "ui.act_notexist_delete", true, ) @@ -52,24 +53,24 @@ var ( rejectRenameLog, "rename-confirm", nil, - "Not enough rights to rename, you must be a trusted editor", - "Cannot rename this hypha because it does not exist", + "ui.act_norights_rename", + "ui.act_notexist_rename", true, ) CanUnattach = canFactory( rejectUnattachLog, "unattach-confirm", - func(h *hyphae.Hypha, u *user.User) (errmsg, errtitle string) { + func(h *hyphae.Hypha, u *user.User, lc *l18n.Localizer) (errmsg, errtitle string) { if h.BinaryPath == "" { rejectUnattachLog(h, u, "no amnt") - return "Cannot unattach this hypha because it has no attachment", "No attachment" + return lc.Get("ui.act_noattachment_tip"), lc.Get("ui.act_noattachment") } return "", "" }, - "Not enough rights to unattach, you must be a trusted editor", - "Cannot unattach this hypha because it does not exist", + "ui.act_norights_unattach", + "ui.act_notexist_unattach", true, ) @@ -77,7 +78,7 @@ var ( rejectEditLog, "upload-text", nil, - "You must be an editor to edit a hypha", + "ui.act_norights_edit", "You cannot edit a hypha that does not exist", false, ) @@ -86,8 +87,10 @@ var ( rejectAttachLog, "upload-binary", nil, - "You must be an editor to attach a hypha", + "ui.act_norights_attach", "You cannot attach a hypha that does not exist", false, ) ) + +/* I've left 'not exists' messages for edit and attach out of translation as they are not used -- chekoopa */ diff --git a/shroom/delete.go b/shroom/delete.go index 05c5c68..fcacb7a 100644 --- a/shroom/delete.go +++ b/shroom/delete.go @@ -5,14 +5,15 @@ import ( "github.com/bouncepaw/mycorrhiza/history" "github.com/bouncepaw/mycorrhiza/hyphae" + "github.com/bouncepaw/mycorrhiza/l18n" "github.com/bouncepaw/mycorrhiza/user" ) // DeleteHypha deletes hypha and makes a history record about that. -func DeleteHypha(u *user.User, h *hyphae.Hypha) (hop *history.Op, errtitle string) { +func DeleteHypha(u *user.User, h *hyphae.Hypha, lc *l18n.Localizer) (hop *history.Op, errtitle string) { hop = history.Operation(history.TypeDeleteHypha) - if errtitle, err := CanDelete(u, h); errtitle != "" { + if errtitle, err := CanDelete(u, h, lc); errtitle != "" { hop.WithErrAbort(err) return hop, errtitle } diff --git a/shroom/rename.go b/shroom/rename.go index 823ca99..e7e792c 100644 --- a/shroom/rename.go +++ b/shroom/rename.go @@ -7,40 +7,41 @@ import ( "github.com/bouncepaw/mycorrhiza/history" "github.com/bouncepaw/mycorrhiza/hyphae" + "github.com/bouncepaw/mycorrhiza/l18n" "github.com/bouncepaw/mycorrhiza/user" "github.com/bouncepaw/mycorrhiza/util" ) -func canRenameThisToThat(oh *hyphae.Hypha, nh *hyphae.Hypha, u *user.User) (errtitle string, err error) { +func canRenameThisToThat(oh *hyphae.Hypha, nh *hyphae.Hypha, u *user.User, lc *l18n.Localizer) (errtitle string, err error) { if nh.Exists { rejectRenameLog(oh, u, fmt.Sprintf("name ‘%s’ taken already", nh.Name)) - return "Name taken", fmt.Errorf("Hypha named %[1]s already exists, cannot rename", nh.Name) + return lc.Get("ui.rename_taken"), fmt.Errorf(lc.Get("ui.rename_taken_tip", &l18n.Replacements{"name": "%[1]s"}), nh.Name) } if nh.Name == "" { rejectRenameLog(oh, u, "no new name given") - return "No name given", errors.New("No new name is given") + return lc.Get("ui.rename_noname"), errors.New(lc.Get("ui.rename_noname_tip")) } if !hyphae.HyphaPattern.MatchString(nh.Name) { rejectRenameLog(oh, u, fmt.Sprintf("new name ‘%s’ invalid", nh.Name)) - return "Invalid name", errors.New("Invalid new name. Names cannot contain characters ^?!:#@><*|\"\\'&%") + return lc.Get("ui.rename_badname"), errors.New(lc.Get("ui.rename_badname_tip", &l18n.Replacements{"chars": "^?!:#@><*|\"\\'&%"})) } return "", nil } // RenameHypha renames hypha from old name `hyphaName` to `newName` and makes a history record about that. If `recursive` is `true`, its subhyphae will be renamed the same way. -func RenameHypha(h *hyphae.Hypha, newHypha *hyphae.Hypha, recursive bool, u *user.User) (hop *history.Op, errtitle string) { +func RenameHypha(h *hyphae.Hypha, newHypha *hyphae.Hypha, recursive bool, u *user.User, lc *l18n.Localizer) (hop *history.Op, errtitle string) { newHypha.Lock() defer newHypha.Unlock() hop = history.Operation(history.TypeRenameHypha) - if errtitle, err := CanRename(u, h); errtitle != "" { + if errtitle, err := CanRename(u, h, lc); errtitle != "" { hop.WithErrAbort(err) return hop, errtitle } - if errtitle, err := canRenameThisToThat(h, newHypha, u); errtitle != "" { + if errtitle, err := canRenameThisToThat(h, newHypha, u, lc); errtitle != "" { hop.WithErrAbort(err) return hop, errtitle } diff --git a/shroom/unattach.go b/shroom/unattach.go index 4514e9b..02bd9a7 100644 --- a/shroom/unattach.go +++ b/shroom/unattach.go @@ -5,14 +5,15 @@ import ( "github.com/bouncepaw/mycorrhiza/history" "github.com/bouncepaw/mycorrhiza/hyphae" + "github.com/bouncepaw/mycorrhiza/l18n" "github.com/bouncepaw/mycorrhiza/user" ) // UnattachHypha unattaches hypha and makes a history record about that. -func UnattachHypha(u *user.User, h *hyphae.Hypha) (hop *history.Op, errtitle string) { +func UnattachHypha(u *user.User, h *hyphae.Hypha, lc *l18n.Localizer) (hop *history.Op, errtitle string) { hop = history.Operation(history.TypeUnattachHypha) - if errtitle, err := CanUnattach(u, h); errtitle != "" { + if errtitle, err := CanUnattach(u, h, lc); errtitle != "" { hop.WithErrAbort(err) return hop, errtitle } diff --git a/shroom/upload.go b/shroom/upload.go index ed8b4f5..6e4ffe2 100644 --- a/shroom/upload.go +++ b/shroom/upload.go @@ -14,12 +14,13 @@ import ( "github.com/bouncepaw/mycorrhiza/files" "github.com/bouncepaw/mycorrhiza/history" "github.com/bouncepaw/mycorrhiza/hyphae" + "github.com/bouncepaw/mycorrhiza/l18n" "github.com/bouncepaw/mycorrhiza/mimetype" "github.com/bouncepaw/mycorrhiza/user" ) // UploadText edits a hypha' text part and makes a history record about that. -func UploadText(h *hyphae.Hypha, data []byte, message string, u *user.User) (hop *history.Op, errtitle string) { +func UploadText(h *hyphae.Hypha, data []byte, message string, u *user.User, lc *l18n.Localizer) (hop *history.Op, errtitle string) { hop = history.Operation(history.TypeEditText) var action string if h.Exists { @@ -34,7 +35,7 @@ func UploadText(h *hyphae.Hypha, data []byte, message string, u *user.User) (hop hop.WithMsg(fmt.Sprintf("%s ‘%s’: %s", action, h.Name, message)) } - if errtitle, err := CanEdit(u, h); err != nil { + if errtitle, err := CanEdit(u, h, lc); err != nil { return hop.WithErrAbort(err), errtitle } if len(bytes.TrimSpace(data)) == 0 && h.BinaryPath == "" { @@ -45,7 +46,7 @@ func UploadText(h *hyphae.Hypha, data []byte, message string, u *user.User) (hop } // UploadBinary edits a hypha' attachment and makes a history record about that. -func UploadBinary(h *hyphae.Hypha, mime string, file multipart.File, u *user.User) (*history.Op, string) { +func UploadBinary(h *hyphae.Hypha, mime string, file multipart.File, u *user.User, lc *l18n.Localizer) (*history.Op, string) { var ( hop = history.Operation(history.TypeEditBinary).WithMsg(fmt.Sprintf("Upload attachment for ‘%s’ with type ‘%s’", h.Name, mime)) data, err = io.ReadAll(file) @@ -54,7 +55,7 @@ func UploadBinary(h *hyphae.Hypha, mime string, file multipart.File, u *user.Use if err != nil { return hop.WithErrAbort(err), err.Error() } - if errtitle, err := CanAttach(u, h); err != nil { + if errtitle, err := CanAttach(u, h, lc); err != nil { return hop.WithErrAbort(err), errtitle } if len(data) == 0 { diff --git a/web/mutators.go b/web/mutators.go index bbfb0cb..0015225 100644 --- a/web/mutators.go +++ b/web/mutators.go @@ -35,7 +35,7 @@ func initMutators(r *mux.Router) { func factoryHandlerAsker( actionPath string, - asker func(*user.User, *hyphae.Hypha) (string, error), + asker func(*user.User, *hyphae.Hypha, *l18n.Localizer) (string, error), succTitleKey string, succPageTemplate func(*http.Request, string, bool) string, ) func(http.ResponseWriter, *http.Request) { @@ -47,7 +47,7 @@ func factoryHandlerAsker( u = user.FromRequest(rq) lc = l18n.FromRequest(rq) ) - if errtitle, err := asker(u, h); err != nil { + if errtitle, err := asker(u, h, lc); err != nil { httpErr( w, lc, @@ -112,15 +112,15 @@ func factoryHandlerConfirmer( var handlerUnattachConfirm = factoryHandlerConfirmer( "unattach-confirm", - func(h *hyphae.Hypha, u *user.User, _ *http.Request) (*history.Op, string) { - return shroom.UnattachHypha(u, h) + func(h *hyphae.Hypha, u *user.User, rq *http.Request) (*history.Op, string) { + return shroom.UnattachHypha(u, h, l18n.FromRequest(rq)) }, ) var handlerDeleteConfirm = factoryHandlerConfirmer( "delete-confirm", - func(h *hyphae.Hypha, u *user.User, _ *http.Request) (*history.Op, string) { - return shroom.DeleteHypha(u, h) + func(h *hyphae.Hypha, u *user.User, rq *http.Request) (*history.Op, string) { + return shroom.DeleteHypha(u, h, l18n.FromRequest(rq)) }, ) @@ -136,7 +136,7 @@ func handlerRenameConfirm(w http.ResponseWriter, rq *http.Request) { newHypha = hyphae.ByName(newName) recursive = rq.PostFormValue("recursive") == "true" ) - hop, errtitle := shroom.RenameHypha(oldHypha, newHypha, recursive, u) + hop, errtitle := shroom.RenameHypha(oldHypha, newHypha, recursive, u, lc) if hop.HasErrors() { httpErr(w, lc, http.StatusInternalServerError, hyphaName, errtitle, @@ -158,7 +158,7 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) { u = user.FromRequest(rq) lc = l18n.FromRequest(rq) ) - if errtitle, err := shroom.CanEdit(u, h); err != nil { + if errtitle, err := shroom.CanEdit(u, h, lc); err != nil { httpErr(w, lc, http.StatusInternalServerError, hyphaName, errtitle, err.Error()) @@ -201,7 +201,7 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) { ) if action != "Preview" { - hop, errtitle = shroom.UploadText(h, []byte(textData), message, u) + hop, errtitle = shroom.UploadText(h, []byte(textData), message, u, lc) if hop.HasErrors() { httpErr(w, lc, http.StatusForbidden, hyphaName, errtitle, @@ -247,7 +247,7 @@ func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) { lc.Get("ui.error"), err.Error()) } - if errtitle, err := shroom.CanAttach(u, h); err != nil { + if errtitle, err := shroom.CanAttach(u, h, lc); err != nil { httpErr(w, lc, http.StatusInternalServerError, hyphaName, errtitle, err.Error()) @@ -264,7 +264,7 @@ func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) { } var ( mime = handler.Header.Get("Content-Type") - hop, errtitle = shroom.UploadBinary(h, mime, file, u) + hop, errtitle = shroom.UploadBinary(h, mime, file, u, lc) ) if hop.HasErrors() { From 835529947b55ebdbac9c7995a4035c9fd0a51edd Mon Sep 17 00:00:00 2001 From: Elias Bomberger Date: Sat, 16 Oct 2021 22:48:35 -0400 Subject: [PATCH 02/27] fix an error that occured when one subhypha's name was the prefix of another figureOutChildren("root/a") would detect "root/ab" as a descendant then figureOutChildren("root/ab") would detect "root/ab" as a descendant and panic trying to find the subpath --- tree/tree.go | 85 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 50 insertions(+), 35 deletions(-) diff --git a/tree/tree.go b/tree/tree.go index 244d808..9977a79 100644 --- a/tree/tree.go +++ b/tree/tree.go @@ -11,7 +11,7 @@ import ( "github.com/bouncepaw/mycorrhiza/util" ) -func findSiblingsAndDescendants(hyphaName string) ([]*sibling, map[string]bool) { +func findSiblings(hyphaName string) []*sibling { hyphaDir := "" if hyphaRawDir := path.Dir(hyphaName); hyphaRawDir != "." { hyphaDir = hyphaRawDir @@ -37,20 +37,11 @@ func findSiblingsAndDescendants(hyphaName string) ([]*sibling, map[string]bool) return hyphae.CheckContinue } - descendantsPool = make(map[string]bool, 0) - descendantCheck = func(h *hyphae.Hypha) hyphae.CheckResult { - if strings.HasPrefix(h.Name, hyphaName+"/") { - descendantsPool[h.Name] = true - } - return hyphae.CheckContinue - } - i7n = hyphae.NewIteration() ) siblingsMap[hyphaName] = true i7n.AddCheck(siblingCheck) - i7n.AddCheck(descendantCheck) i7n.Ignite() siblings := make([]*sibling, len(siblingsMap)) @@ -62,7 +53,7 @@ func findSiblingsAndDescendants(hyphaName string) ([]*sibling, map[string]bool) sort.Slice(siblings, func(i, j int) bool { return siblings[i].name < siblings[j].name }) - return siblings, descendantsPool + return siblings } func countSubhyphae(siblings []*sibling) { @@ -90,21 +81,22 @@ func Tree(hyphaName string) (siblingsHTML, childrenHTML, prev, next string) { children := make([]child, 0) I := 0 // The tree is generated in two iterations of hyphae storage: - // 1. Find all siblings (sorted) and descendants' names + // 1. Find all siblings (sorted) // 2. Count how many subhyphae siblings have // // We also have to figure out what is going on with the descendants: who is a child of whom. We do that in parallel with (2) because we can. // One of the siblings is the hypha with name `hyphaName` - siblings, descendantsPool := findSiblingsAndDescendants(hyphaName) + var siblings []*sibling wg := sync.WaitGroup{} wg.Add(2) go func() { + siblings = findSiblings(hyphaName) countSubhyphae(siblings) wg.Done() }() go func() { - children = figureOutChildren(hyphaName, descendantsPool, true).children + children = figureOutChildren(hyphaName, true).children wg.Done() }() wg.Wait() @@ -132,32 +124,55 @@ type child struct { children []child } -func figureOutChildren(hyphaName string, subhyphaePool map[string]bool, exists bool) child { +func figureOutChildren(hyphaName string, exists bool) child { var ( - nestLevel = strings.Count(hyphaName, "/") - adopted = make([]child, 0) + descPrefix = hyphaName + "/" + child = child{hyphaName, true, make([]child, 0)} ) - for subhyphaName := range subhyphaePool { - subnestLevel := strings.Count(subhyphaName, "/") - if subnestLevel-1 == nestLevel && path.Dir(subhyphaName) == hyphaName { - delete(subhyphaePool, subhyphaName) - adopted = append(adopted, figureOutChildren(subhyphaName, subhyphaePool, true)) - } - } - for descName := range subhyphaePool { - if strings.HasPrefix(descName, hyphaName) { - var ( - rawSubPath = strings.TrimPrefix(descName, hyphaName)[1:] - slashIdx = strings.IndexRune(rawSubPath, '/') - ) - if slashIdx > -1 { - var sibPath = descName[:slashIdx+len(hyphaName)+1] - adopted = append(adopted, figureOutChildren(sibPath, subhyphaePool, false)) - } // `else` never happens? + + for desc := range hyphae.YieldExistingHyphae() { + var descName = desc.Name + if strings.HasPrefix(descName, descPrefix) { + var subPath = strings.TrimPrefix(descName, descPrefix) + addHyphaToChild(descName, subPath, &child) } } - return child{hyphaName, exists, adopted} + return child +} + +func addHyphaToChild(hyphaName, subPath string, child *child) { + // when hyphaName = "root/a/b", subPath = "a/b", and child.name = "root" + // addHyphaToChild("root/a/b", "b", child{"root/a"}) + // when hyphaName = "root/a/b", subPath = "b", and child.name = "root/a" + // set .exists=true for "root/a/b", and create it if it isn't there already + var exists = !strings.Contains(subPath, "/") + if exists { + var subchild = findOrCreateSubchild(subPath, child) + subchild.exists = true + } else { + var ( + firstSlash = strings.IndexRune(subPath, '/') + firstDir = subPath[:firstSlash] + restOfPath = subPath[firstSlash + 1:] + subchild = findOrCreateSubchild(firstDir, child) + ) + addHyphaToChild(hyphaName, restOfPath, subchild) + } +} + +func findOrCreateSubchild(name string, baseChild *child) *child { + // when name = "a", and baseChild.name = "root" + // if baseChild.children contains "root/a", return it + // else create it and return that + var fullName = baseChild.name + "/" + name + for i := range baseChild.children { + if baseChild.children[i].name == fullName { + return &baseChild.children[i] + } + } + baseChild.children = append(baseChild.children, child{fullName, false, make([]child, 0)}) + return &baseChild.children[len(baseChild.children) - 1] } type sibling struct { From e4cd5e4a5f82fcd63fa1d1e2cfdff0a2fce30396 Mon Sep 17 00:00:00 2001 From: Elias Bomberger Date: Sun, 17 Oct 2021 14:30:19 -0400 Subject: [PATCH 03/27] oops, missed a spot before run gofmt, remove an unused bit --- tree/tree.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tree/tree.go b/tree/tree.go index 9977a79..8c567ae 100644 --- a/tree/tree.go +++ b/tree/tree.go @@ -96,7 +96,7 @@ func Tree(hyphaName string) (siblingsHTML, childrenHTML, prev, next string) { wg.Done() }() go func() { - children = figureOutChildren(hyphaName, true).children + children = figureOutChildren(hyphaName).children wg.Done() }() wg.Wait() @@ -124,10 +124,10 @@ type child struct { children []child } -func figureOutChildren(hyphaName string, exists bool) child { +func figureOutChildren(hyphaName string) child { var ( descPrefix = hyphaName + "/" - child = child{hyphaName, true, make([]child, 0)} + child = child{hyphaName, true, make([]child, 0)} ) for desc := range hyphae.YieldExistingHyphae() { @@ -153,9 +153,9 @@ func addHyphaToChild(hyphaName, subPath string, child *child) { } else { var ( firstSlash = strings.IndexRune(subPath, '/') - firstDir = subPath[:firstSlash] - restOfPath = subPath[firstSlash + 1:] - subchild = findOrCreateSubchild(firstDir, child) + firstDir = subPath[:firstSlash] + restOfPath = subPath[firstSlash+1:] + subchild = findOrCreateSubchild(firstDir, child) ) addHyphaToChild(hyphaName, restOfPath, subchild) } @@ -172,7 +172,7 @@ func findOrCreateSubchild(name string, baseChild *child) *child { } } baseChild.children = append(baseChild.children, child{fullName, false, make([]child, 0)}) - return &baseChild.children[len(baseChild.children) - 1] + return &baseChild.children[len(baseChild.children)-1] } type sibling struct { From 924b011e06de56f63265f477c8718a9d6a7111c5 Mon Sep 17 00:00:00 2001 From: Elias Bomberger Date: Fri, 22 Oct 2021 22:00:44 -0400 Subject: [PATCH 04/27] reorganize the history package (and rewrite some parts with qtpl) start work on grouping edits in feeds --- history/feed.go | 84 +++++++++++ history/history.go | 139 ------------------ history/information.go | 199 ------------------------- history/operations.go | 4 +- history/revision.go | 255 ++++++++++++++++++++++++++++++++ history/view.qtpl | 55 +++++++ history/view.qtpl.go | 326 +++++++++++++++++++++++++++++++++++++++++ main.go | 1 + 8 files changed, 722 insertions(+), 341 deletions(-) create mode 100644 history/feed.go delete mode 100644 history/information.go create mode 100644 history/revision.go create mode 100644 history/view.qtpl create mode 100644 history/view.qtpl.go diff --git a/history/feed.go b/history/feed.go new file mode 100644 index 0000000..b3a0bd4 --- /dev/null +++ b/history/feed.go @@ -0,0 +1,84 @@ +package history + +import ( + "fmt" + "strings" + "time" + + "github.com/bouncepaw/mycorrhiza/cfg" + + "github.com/gorilla/feeds" +) + +var groupPeriod, _ = time.ParseDuration("30m") + +func recentChangesFeed() *feeds.Feed { + feed := &feeds.Feed{ + Title: "Recent changes", + Link: &feeds.Link{Href: cfg.URL}, + Description: "List of 30 recent changes on the wiki", + Author: &feeds.Author{Name: "Wikimind", Email: "wikimind@mycorrhiza"}, + Updated: time.Now(), + } + revs := RecentChanges(30) + groups := groupRevisionsByPeriod(revs, groupPeriod) + for _, grp := range groups { + item := grp.feedItem() + feed.Add(&item) + } + return feed +} + +// RecentChangesRSS creates recent changes feed in RSS format. +func RecentChangesRSS() (string, error) { + return recentChangesFeed().ToRss() +} + +// RecentChangesAtom creates recent changes feed in Atom format. +func RecentChangesAtom() (string, error) { + return recentChangesFeed().ToAtom() +} + +// RecentChangesJSON creates recent changes feed in JSON format. +func RecentChangesJSON() (string, error) { + return recentChangesFeed().ToJSON() +} + +func (grp revisionGroup) feedItem() feeds.Item { + return feeds.Item{ + Title: grp.title(), + Author: grp.author(), + Id: grp[0].Hash, + Description: grp.descriptionForFeed(), + Created: grp[len(grp)-1].Time, // earliest revision + Updated: grp[0].Time, // latest revision + Link: &feeds.Link{Href: cfg.URL + grp[0].bestLink()}, + } +} + +func (grp revisionGroup) title() string { + if len(grp) == 1 { + return grp[0].Message + } else { + return fmt.Sprintf("%d edits (%s, ...)", len(grp), grp[0].Message) + } +} + +func (grp revisionGroup) author() *feeds.Author { + author := grp[0].Username + for _, rev := range grp[1:] { + // if they don't all have the same author, return nil + if rev.Username != author { + return nil + } + } + return &feeds.Author{Name: author} +} + +func (grp revisionGroup) descriptionForFeed() string { + builder := strings.Builder{} + for _, rev := range grp { + builder.WriteString(rev.descriptionForFeed()) + } + return builder.String() +} diff --git a/history/history.go b/history/history.go index 37ce429..559d9d5 100644 --- a/history/history.go +++ b/history/history.go @@ -4,14 +4,10 @@ package history import ( "bytes" "fmt" - "html" "log" "os/exec" "path/filepath" "regexp" - "strconv" - "strings" - "time" "github.com/bouncepaw/mycorrhiza/files" "github.com/bouncepaw/mycorrhiza/util" @@ -54,131 +50,6 @@ func InitGitRepo() { } } -// Revision represents a revision, duh. Hash is usually short. Username is extracted from email. -type Revision struct { - Hash string - Username string - Time time.Time - Message string - filesAffectedBuf []string - hyphaeAffectedBuf []string -} - -// filesAffected tells what files have been affected by the revision. -func (rev *Revision) filesAffected() (filenames []string) { - if nil != rev.filesAffectedBuf { - return rev.filesAffectedBuf - } - // List of files affected by this revision, one per line. - out, err := silentGitsh("diff-tree", "--no-commit-id", "--name-only", "-r", rev.Hash) - // There's an error? Well, whatever, let's just assign an empty slice, who cares. - if err != nil { - rev.filesAffectedBuf = []string{} - } else { - rev.filesAffectedBuf = strings.Split(out.String(), "\n") - } - return rev.filesAffectedBuf -} - -// determine what hyphae were affected by this revision -func (rev *Revision) hyphaeAffected() (hyphae []string) { - if nil != rev.hyphaeAffectedBuf { - return rev.hyphaeAffectedBuf - } - hyphae = make([]string, 0) - var ( - // set is used to determine if a certain hypha has been already noted (hyphae are stored in 2 files at most currently). - set = make(map[string]bool) - isNewName = func(hyphaName string) bool { - if _, present := set[hyphaName]; present { - return false - } - set[hyphaName] = true - return true - } - filesAffected = rev.filesAffected() - ) - for _, filename := range filesAffected { - if strings.IndexRune(filename, '.') >= 0 { - dotPos := strings.LastIndexByte(filename, '.') - hyphaName := string([]byte(filename)[0:dotPos]) // is it safe? - if isNewName(hyphaName) { - hyphae = append(hyphae, hyphaName) - } - } - } - rev.hyphaeAffectedBuf = hyphae - return hyphae -} - -// TimeString returns a human readable time representation. -func (rev Revision) TimeString() string { - return rev.Time.Format(time.RFC822) -} - -// HyphaeLinksHTML returns a comma-separated list of hyphae that were affected by this revision as HTML string. -func (rev Revision) HyphaeLinksHTML() (html string) { - hyphae := rev.hyphaeAffected() - for i, hyphaName := range hyphae { - if i > 0 { - html += `` - } - html += fmt.Sprintf(`%[1]s`, hyphaName) - } - return html -} - -// descriptionForFeed generates a good enough HTML contents for a web feed. -func (rev *Revision) descriptionForFeed() (htmlDesc string) { - return fmt.Sprintf( - `

%s

-

Hyphae affected: %s

-
%s
`, rev.Message, rev.HyphaeLinksHTML(), html.EscapeString(rev.textDiff())) -} - -// textDiff generates a good enough diff to display in a web feed. It is not html-escaped. -func (rev *Revision) textDiff() (diff string) { - filenames, ok := rev.mycoFiles() - if !ok { - return "No text changes" - } - for _, filename := range filenames { - text, err := PrimitiveDiffAtRevision(filename, rev.Hash) - if err != nil { - diff += "\nAn error has occurred with " + filename + "\n" - } - diff += text + "\n" - } - return diff -} - -// mycoFiles returns filenames of .myco file. It is not ok if there are no myco files. -func (rev *Revision) mycoFiles() (filenames []string, ok bool) { - filenames = []string{} - for _, filename := range rev.filesAffected() { - if strings.HasSuffix(filename, ".myco") { - filenames = append(filenames, filename) - } - } - return filenames, len(filenames) > 0 -} - -// Try and guess what link is the most important by looking at the message. -func (rev *Revision) bestLink() string { - var ( - revs = rev.hyphaeAffected() - renameRes = renameMsgPattern.FindStringSubmatch(rev.Message) - ) - switch { - case renameRes != nil: - return "/hypha/" + renameRes[1] - case len(revs) == 0: - return "" - default: - return "/hypha/" + revs[0] - } -} - // I pronounce it as [gɪt͡ʃ]. // gitsh is async-safe, therefore all other git-related functions in this module are too. func gitsh(args ...string) (out bytes.Buffer, err error) { @@ -204,16 +75,6 @@ func silentGitsh(args ...string) (out bytes.Buffer, err error) { return *bytes.NewBuffer(b), err } -// Convert a UNIX timestamp as string into a time. If nil is returned, it means that the timestamp could not be converted. -func unixTimestampAsTime(ts string) *time.Time { - i, err := strconv.ParseInt(ts, 10, 64) - if err != nil { - return nil - } - tm := time.Unix(i, 0) - return &tm -} - // Rename renames from `from` to `to` using `git mv`. func Rename(from, to string) error { log.Println(util.ShorterPath(from), util.ShorterPath(to)) diff --git a/history/information.go b/history/information.go deleted file mode 100644 index 8237c1c..0000000 --- a/history/information.go +++ /dev/null @@ -1,199 +0,0 @@ -package history - -// information.go -// Things related to gathering existing information. -import ( - "fmt" - "log" - "regexp" - "strconv" - "strings" - "time" - - "github.com/bouncepaw/mycorrhiza/cfg" - "github.com/bouncepaw/mycorrhiza/files" - - "github.com/gorilla/feeds" -) - -func recentChangesFeed() *feeds.Feed { - feed := &feeds.Feed{ - Title: "Recent changes", - Link: &feeds.Link{Href: cfg.URL}, - Description: "List of 30 recent changes on the wiki", - Author: &feeds.Author{Name: "Wikimind", Email: "wikimind@mycorrhiza"}, - Updated: time.Now(), - } - var ( - out, err = silentGitsh( - "log", "--oneline", "--no-merges", - "--pretty=format:\"%h\t%ae\t%at\t%s\"", - "--max-count=30", - ) - revs []Revision - ) - if err == nil { - for _, line := range strings.Split(out.String(), "\n") { - revs = append(revs, parseRevisionLine(line)) - } - } - log.Printf("Found %d recent changes", len(revs)) - for _, rev := range revs { - feed.Add(&feeds.Item{ - Title: rev.Message, - Author: &feeds.Author{Name: rev.Username}, - Id: rev.Hash, - Description: rev.descriptionForFeed(), - Created: rev.Time, - Updated: rev.Time, - Link: &feeds.Link{Href: cfg.URL + rev.bestLink()}, - }) - } - return feed -} - -// RecentChangesRSS creates recent changes feed in RSS format. -func RecentChangesRSS() (string, error) { - return recentChangesFeed().ToRss() -} - -// RecentChangesAtom creates recent changes feed in Atom format. -func RecentChangesAtom() (string, error) { - return recentChangesFeed().ToAtom() -} - -// RecentChangesJSON creates recent changes feed in JSON format. -func RecentChangesJSON() (string, error) { - return recentChangesFeed().ToJSON() -} - -// RecentChanges gathers an arbitrary number of latest changes in form of revisions slice. -func RecentChanges(n int) []Revision { - var ( - out, err = silentGitsh( - "log", "--oneline", "--no-merges", - "--pretty=format:\"%h\t%ae\t%at\t%s\"", - "--max-count="+strconv.Itoa(n), - ) - revs []Revision - ) - if err == nil { - for _, line := range strings.Split(out.String(), "\n") { - revs = append(revs, parseRevisionLine(line)) - } - } - log.Printf("Found %d recent changes", len(revs)) - return revs -} - -// FileChanged tells you if the file has been changed. -func FileChanged(path string) bool { - _, err := gitsh("diff", "--exit-code", path) - return err != nil -} - -// Revisions returns slice of revisions for the given hypha name. -func Revisions(hyphaName string) ([]Revision, error) { - var ( - out, err = silentGitsh( - "log", "--oneline", "--no-merges", - // Hash, author email, author time, commit msg separated by tab - "--pretty=format:\"%h\t%ae\t%at\t%s\"", - "--", hyphaName+".*", - ) - revs []Revision - ) - if err == nil { - for _, line := range strings.Split(out.String(), "\n") { - if line != "" { - revs = append(revs, parseRevisionLine(line)) - } - } - } - log.Printf("Found %d revisions for ‘%s’\n", len(revs), hyphaName) - return revs, err -} - -// WithRevisions returns an html representation of `revs` that is meant to be inserted in a history page. -func WithRevisions(hyphaName string, revs []Revision) (html string) { - var ( - currentYear int - currentMonth time.Month - ) - for i, rev := range revs { - if rev.Time.Month() != currentMonth || rev.Time.Year() != currentYear { - currentYear = rev.Time.Year() - currentMonth = rev.Time.Month() - if i != 0 { - html += ` - -` - } - html += fmt.Sprintf(` -
- -

%[3]s

-
-