Browse Source

Add reading time for entries

Maxime Bailleul 5 years ago
parent
commit
ee5a8a05c9

+ 1 - 1
database/migration.go

@@ -12,7 +12,7 @@ import (
 	"miniflux.app/logger"
 )
 
-const schemaVersion = 32
+const schemaVersion = 33
 
 // Migrate executes database migrations.
 func Migrate(db *sql.DB) {

+ 2 - 0
database/sql.go

@@ -185,6 +185,7 @@ create index entries_user_feed_idx on entries (user_id, feed_id);
 	"schema_version_31": `alter table feeds add column ignore_http_cache bool default false;`,
 	"schema_version_32": `alter table users add column entries_per_page int default 100;
 `,
+	"schema_version_33": `alter table users add column show_reading_time boolean default 't';`,
 	"schema_version_4": `create type entry_sorting_direction as enum('asc', 'desc');
 alter table users add column entry_direction entry_sorting_direction default 'asc';
 `,
@@ -240,6 +241,7 @@ var SqlMapChecksums = map[string]string{
 	"schema_version_30": "3ec48a9b2e7a0fc32c85f31652f723565c34213f5f2d7e5e5076aad8f0b40d23",
 	"schema_version_31": "9290ef295731b03ddfe32dcaded0be70d41b63572420ad379cf2874a9b54581c",
 	"schema_version_32": "5b4de8dd2d7e3c6ae4150e0e3931df2ee989f2c667145bd67294e5a5f3fae456",
+	"schema_version_33": "bf38514efeb6c12511f41b1cc484f92722240b0a6ae874c32a958dfea3433d02",
 	"schema_version_4":  "216ea3a7d3e1704e40c797b5dc47456517c27dbb6ca98bf88812f4f63d74b5d9",
 	"schema_version_5":  "46397e2f5f2c82116786127e9f6a403e975b14d2ca7b652a48cd1ba843e6a27c",
 	"schema_version_6":  "9d05b4fb223f0e60efc716add5048b0ca9c37511cf2041721e20505d6d798ce4",

+ 1 - 0
database/sql/schema_version_33.sql

@@ -0,0 +1 @@
+alter table users add column show_reading_time boolean default 't';

+ 383 - 328
locale/translations.go

@@ -82,6 +82,10 @@ var translations = map[string]string{
     "entry.unshare.label": "Nicht teilen",
     "entry.shared_entry.title": "Öffnen Sie den öffentlichen Link",
     "entry.shared_entry.label": "Teilen",
+    "entry.estimated_reading_time": [
+        "%d Minute gelesen",
+        "%d Minuten gelesen"
+    ],
     "page.shared_entries.title": "Geteilte Artikel",
     "page.unread.title": "Ungelesen",
     "page.starred.title": "Lesezeichen",
@@ -264,6 +268,7 @@ var translations = map[string]string{
     "form.prefs.select.older_first": "Älteste Artikel zuerst",
     "form.prefs.select.recent_first": "Neueste Artikel zuerst",
     "form.prefs.label.keyboard_shortcuts": "Tastaturkürzel aktivieren",
+    "form.prefs.label.show_reading_time": "Geschätzte Lesezeit für Artikel anzeigen",
     "form.prefs.label.custom_css": "Benutzerdefiniertes CSS",
     "form.import.label.file": "OPML Datei",
     "form.import.label.url": "URL",
@@ -422,6 +427,10 @@ var translations = map[string]string{
     "entry.unshare.label": "Unshare",
     "entry.shared_entry.title": "Open the public link",
     "entry.shared_entry.label": "Share",
+    "entry.estimated_reading_time": [
+        "%d minute read",
+        "%d minutes read"
+    ],
     "page.shared_entries.title": "Shared Entries",
     "page.unread.title": "Unread",
     "page.starred.title": "Starred",
@@ -604,6 +613,7 @@ var translations = map[string]string{
     "form.prefs.select.older_first": "Older entries first",
     "form.prefs.select.recent_first": "Recent entries first",
     "form.prefs.label.keyboard_shortcuts": "Enable keyboard shortcuts",
+    "form.prefs.label.show_reading_time": "Show estimated reading time for articles",
     "form.prefs.label.custom_css": "Custom CSS",
     "form.import.label.file": "OPML file",
     "form.import.label.url": "URL",
@@ -742,6 +752,10 @@ var translations = map[string]string{
     "entry.unshare.label": "No compartir",
     "entry.shared_entry.title": "Abrir el enlace público",
     "entry.shared_entry.label": "Compartir",
+    "entry.estimated_reading_time": [
+        "%d minuto de lectura",
+        "%d minutos de lectura"
+    ],
     "page.shared_entries.title": "Entradas compartidas",
     "page.unread.title": "No leídos",
     "page.starred.title": "Marcadores",
@@ -924,6 +938,7 @@ var translations = map[string]string{
     "form.prefs.select.older_first": "Entradas más viejas primero",
     "form.prefs.select.recent_first": "Entradas recientes primero",
     "form.prefs.label.keyboard_shortcuts": "Habilitar atajos de teclado",
+    "form.prefs.label.show_reading_time": "Mostrar el tiempo estimado de lectura de los artículos",
     "form.prefs.label.custom_css": "CSS personalizado",
     "form.import.label.file": "Archivo OPML",
     "form.import.label.url": "URL",
@@ -1062,6 +1077,10 @@ var translations = map[string]string{
     "entry.unshare.label": "Enlever le partage",
     "entry.shared_entry.title": "Ouvrir le lien public",
     "entry.shared_entry.label": "Partage",
+    "entry.estimated_reading_time": [
+        "%d minute de lecture",
+        "%d minutes de lecture"
+    ],
     "page.shared_entries.title": "Articles partagés",
     "page.unread.title": "Non lus",
     "page.starred.title": "Favoris",
@@ -1244,6 +1263,7 @@ var translations = map[string]string{
     "form.prefs.select.older_first": "Ancien éléments en premier",
     "form.prefs.select.recent_first": "Éléments récents en premier",
     "form.prefs.label.keyboard_shortcuts": "Activer les raccourcis clavier",
+    "form.prefs.label.show_reading_time": "Afficher le temps de lecture estimé des articles",
     "form.prefs.label.custom_css": "CSS personnalisé",
     "form.import.label.file": "Fichier OPML",
     "form.import.label.url": "URL",
@@ -1402,6 +1422,10 @@ var translations = map[string]string{
     "entry.unshare.label": "Unshare",
     "entry.shared_entry.title": "Apri il link pubblico",
     "entry.shared_entry.label": "Condivisione",
+    "entry.estimated_reading_time": [
+        "%d minuto di lettura",
+        "%d minuti di lettura"
+    ],
     "page.shared_entries.title": "Voci condivise",
     "page.unread.title": "Da leggere",
     "page.starred.title": "Preferiti",
@@ -1584,6 +1608,7 @@ var translations = map[string]string{
     "form.prefs.select.older_first": "Prima i più vecchi",
     "form.prefs.select.recent_first": "Prima i più recenti",
     "form.prefs.label.keyboard_shortcuts": "Abilita le scorciatoie da tastiera",
+    "form.prefs.label.show_reading_time": "Mostra il tempo di lettura stimato per gli articoli",
     "form.prefs.label.custom_css": "CSS personalizzati",
     "form.import.label.file": "File OPML",
     "form.import.label.url": "URL",
@@ -1722,6 +1747,10 @@ var translations = map[string]string{
     "entry.unshare.label": "共有解除",
     "entry.shared_entry.title": "公開リンクを開く",
     "entry.shared_entry.label": "共有する",
+    "entry.estimated_reading_time": [
+        "%d分で読む",
+        "%d分で読む"
+    ],
     "page.shared_entries.title": "共有エントリ",
     "page.unread.title": "未読",
     "page.starred.title": "星付き",
@@ -1904,6 +1933,7 @@ var translations = map[string]string{
     "form.prefs.select.older_first": "古い記事を最初に",
     "form.prefs.select.recent_first": "新しい記事を最初に",
     "form.prefs.label.keyboard_shortcuts": "キーボード・ショートカットを有効にする",
+    "form.prefs.label.show_reading_time": "記事の推定読書時間を表示する",
     "form.prefs.label.custom_css": "カスタムCSS",
     "form.import.label.file": "OPML ファイル",
     "form.import.label.url": "URL",
@@ -2042,6 +2072,10 @@ var translations = map[string]string{
     "entry.unshare.label": "Delen ongedaan maken",
     "entry.shared_entry.title": "Open de openbare link",
     "entry.shared_entry.label": "Delen",
+    "entry.estimated_reading_time": [
+        "%d minuut gelezen",
+        "%d minuten gelezen"
+    ],
     "page.shared_entries.title": "Gedeelde vermeldingen",
     "page.unread.title": "Ongelezen",
     "page.starred.title": "Favorieten",
@@ -2224,6 +2258,7 @@ var translations = map[string]string{
     "form.prefs.select.older_first": "Oudere items eerst",
     "form.prefs.select.recent_first": "Recente items eerst",
     "form.prefs.label.keyboard_shortcuts": "Schakel sneltoetsen in",
+    "form.prefs.label.show_reading_time": "Toon geschatte leestijd voor artikelen",
     "form.prefs.label.custom_css": "Aangepaste CSS",
     "form.import.label.file": "OPML-bestand",
     "form.import.label.url": "URL",
@@ -2380,6 +2415,10 @@ var translations = map[string]string{
     "entry.unshare.label": "Unshare",
     "entry.shared_entry.title": "Otwórz publiczny link",
     "entry.shared_entry.label": "Udostępnianie",
+    "entry.estimated_reading_time": [
+        "%d minuta czytania",
+        "%d minut czytania"
+    ],
     "page.shared_entries.title": "Udostępnione wpisy",
     "page.unread.title": "Nieprzeczytane",
     "page.starred.title": "Oznaczone gwiazdką",
@@ -2563,6 +2602,7 @@ var translations = map[string]string{
     "form.prefs.label.entries_per_page": "Wpisy na stronie",
     "form.prefs.select.older_first": "Najstarsze wpisy jako pierwsze",
     "form.prefs.label.keyboard_shortcuts": "Włącz skróty klawiaturowe",
+    "form.prefs.label.show_reading_time": "Pokaż szacowany czas czytania artykułów",
     "form.prefs.select.recent_first": "Najnowsze wpisy jako pierwsze",
     "form.prefs.label.custom_css": "Niestandardowy CSS",
     "form.import.label.file": "Plik OPML",
@@ -2648,323 +2688,328 @@ var translations = map[string]string{
 }
 `,
 	"pt_BR": `{
-  "confirm.question": "Tem certeza?",
-  "confirm.yes": "Sim",
-  "confirm.no": "Não",
-  "confirm.loading": "Carregando...",
-  "action.subscribe": "Inscrever",
-  "action.save": "Salvar",
-  "action.or": "Ou",
-  "action.cancel": "Cancelar",
-  "action.remove": "Remover",
-  "action.remove_feed": "Remover fonte",
-  "action.update": "Atualizar",
-  "action.edit": "Editar",
-  "action.download": "Baixar",
-  "action.import": "Importar",
-  "action.login": "Iniciar sessão",
-  "action.home_screen": "Voltar para a tela inicial",
-  "tooltip.keyboard_shortcuts": "Atalho do teclado: %s",
-  "tooltip.logged_user": "Autenticado como %s",
-  "menu.unread": "Não lido",
-  "menu.starred": "Favoritos",
-  "menu.history": "Histórico",
-  "menu.feeds": "Fontes",
-  "menu.categories": "Categorias",
-  "menu.settings": "Configurações",
-  "menu.logout": "Encerrar sessão",
-  "menu.preferences": "Preferências",
-  "menu.integrations": "Integrações",
-  "menu.sessions": "Sessões",
-  "menu.users": "Usuários",
-  "menu.about": "Sobre",
-  "menu.export": "Exportar",
-  "menu.import": "Importar",
-  "menu.create_category": "Criar uma categoria",
-  "menu.mark_page_as_read": "Marcar essa página como lída",
-  "menu.mark_all_as_read": "Marcar todos como lido",
-  "menu.show_all_entries": "Mostrar todas os itens",
-  "menu.show_only_unread_entries": "Mostrar apenas itens não lidos",
-  "menu.refresh_feed": "Atualizar",
-  "menu.refresh_all_feeds": "Atualizar todas as fontes",
-  "menu.edit_feed": "Editar",
-  "menu.edit_category": "Editar",
-  "menu.add_feed": "Adicionar inscrição",
-  "menu.add_user": "Adicionar usuário",
-  "menu.flush_history": "Limpar histórico",
-  "menu.feed_entries": "Itens",
-  "menu.api_keys": "Chaves de API",
-  "menu.create_api_key": "Criar uma nova chave de API",
-  "menu.shared_entries": "Itens compartilhados",
-  "search.label": "Buscar",
-  "search.placeholder": "Buscar por...",
-  "pagination.next": "Próximo",
-  "pagination.previous": "Anterior",
-  "entry.status.unread": "Não lido",
-  "entry.status.read": "Lido",
-  "entry.status.toast.unread": "Marcado como não lido",
-  "entry.status.toast.read": "Marcado como lido",
-  "entry.status.title": "Modificar estado deste item",
-  "entry.bookmark.toggle.on": "Marcar",
-  "entry.bookmark.toggle.off": "Desmarcar",
-  "entry.bookmark.toast.on": "Favoritado",
-  "entry.bookmark.toast.off": "Desfavoritado",
-  "entry.state.saving": "Salvando...",
-  "entry.state.loading": "Carregando...",
-  "entry.save.label": "Salvar",
-  "entry.save.title": "Salvar esse item",
-  "entry.save.completed": "Feito!",
-  "entry.save.toast.completed": "Item guardado",
-  "entry.scraper.label": "Conteúdo completo",
-  "entry.scraper.title": "Obter conteúdo completo",
-  "entry.scraper.completed": "Feito!",
-  "entry.original.label": "Original",
-  "entry.comments.label": "Comentários",
-  "entry.comments.title": "Ver comentários",
-  "entry.share.label": "Compartilhar",
-  "entry.share.title": "Compartilhar esse item",
-  "entry.unshare.label": "Descompartilhar",
-  "entry.shared_entry.title": "Abrir link público",
-  "entry.shared_entry.label": "Compartilhar",
-  "page.shared_entries.title": "Itens compartilhados",
-  "page.unread.title": "Não lídos",
-  "page.starred.title": "Favoritos",
-  "page.categories.title": "Categorias",
-  "page.categories.no_feed": "Sem fonte.",
-  "page.categories.entries": "Itens",
-  "page.categories.feeds": "Inscrições",
-  "page.categories.feed_count": [
-      "Existe %d fonte.",
-      "Existem %d fontes."
-  ],
-  "page.new_category.title": "Nova categoria",
-  "page.new_user.title": "Novo usuário",
-  "page.edit_category.title": "Editar categoria: %s",
-  "page.edit_user.title": "Editar usuário: %s",
-  "page.feeds.title": "Fontes",
-  "page.feeds.last_check": "Última verificação:",
-  "page.feeds.unread_counter": "Numero de itens não lidos",
-  "page.feeds.read_counter": "Número de itens lidos",
-  "page.feeds.error_count": [
-      "%d erro",
-      "%d erros"
-  ],
-  "page.history.title": "Histórico",
-  "page.import.title": "Importar",
-  "page.search.title": "Resultados da busca",
-  "page.about.title": "Sobre",
-  "page.about.credits": "Créditos",
-  "page.about.version": "Versão:",
-  "page.about.build_date": "Compilado em:",
-  "page.about.author": "Autor:",
-  "page.about.license": "Licença:",
-  "page.add_feed.title": "Nova inscrição",
-  "page.add_feed.no_category": "Não existe uma categoria. Deve existir pelo menos uma categoria.",
-  "page.add_feed.label.url": "URL",
-  "page.add_feed.submit": "Buscar uma fonte",
-  "page.add_feed.legend.advanced_options": "Opções avançadas",
-  "page.add_feed.choose_feed": "Escolher uma fonte",
-  "page.edit_feed.title": "Editar fonte: %s",
-  "page.edit_feed.last_check": "Última verificação:",
-  "page.edit_feed.last_modified_header": "Cabeçalho 'LastModified':",
-  "page.edit_feed.etag_header": "Cabeçalho 'ETag':",
-  "page.edit_feed.no_header": "Sem cabeçalhos",
-  "page.edit_feed.last_parsing_error": "Último erro durante processamento",
-  "page.entry.attachments": "Anexos",
-  "page.keyboard_shortcuts.title": "Atalhos de teclado",
-  "page.keyboard_shortcuts.subtitle.sections": "Navegação de seções",
-  "page.keyboard_shortcuts.subtitle.items": "Navegação de itens",
-  "page.keyboard_shortcuts.subtitle.pages": "Navegação de páginas",
-  "page.keyboard_shortcuts.subtitle.actions": "Ações",
-  "page.keyboard_shortcuts.go_to_unread": "Ir aos não lidos",
-  "page.keyboard_shortcuts.go_to_starred": "Ir aos favoritos",
-  "page.keyboard_shortcuts.go_to_history": "Ir ao histórico",
-  "page.keyboard_shortcuts.go_to_feeds": "Ir as inscrições",
-  "page.keyboard_shortcuts.go_to_categories": "Ir as categorias",
-  "page.keyboard_shortcuts.go_to_settings": "Ir as configurações",
-  "page.keyboard_shortcuts.show_keyboard_shortcuts": "Mostrar atalhos de teclado",
-  "page.keyboard_shortcuts.go_to_previous_item": "Ir ao item anterior",
-  "page.keyboard_shortcuts.go_to_next_item": "Ir ao tem seguinte",
-  "page.keyboard_shortcuts.go_to_feed": "Ir a fonte",
-  "page.keyboard_shortcuts.go_to_previous_page": "Ir a página anterior",
-  "page.keyboard_shortcuts.go_to_next_page": "Ir a página seguinte",
-  "page.keyboard_shortcuts.open_item": "Abrir o item selecionado",
-  "page.keyboard_shortcuts.open_original": "Abrir o conteúdo original",
-  "page.keyboard_shortcuts.open_original_same_window": "Abrir o conteúdo original na janela atual",
-  "page.keyboard_shortcuts.open_comments": "Abrir os comentários",
-  "page.keyboard_shortcuts.open_comments_same_window": "Abrir os comentários na janela atual",
-  "page.keyboard_shortcuts.toggle_read_status": "Inverter estado de leitura do item",
-  "page.keyboard_shortcuts.refresh_all_feeds": "Atualizar todas as fontes",
-  "page.keyboard_shortcuts.mark_page_as_read": "Marcar página atual como lida",
-  "page.keyboard_shortcuts.download_content": "Buscar o conteúdo original",
-  "page.keyboard_shortcuts.toggle_bookmark_status": "Marcar ou desmarcar como favorito",
-  "page.keyboard_shortcuts.save_article": "Salvar item",
-  "page.keyboard_shortcuts.remove_feed": "Remover essa fonte",
-  "page.keyboard_shortcuts.go_to_search": "Ir para o campo de busca",
-  "page.keyboard_shortcuts.close_modal": "Fechar janela",
-  "page.users.title": "Usuários",
-  "page.users.username": "Nome de usuário",
-  "page.users.never_logged": "Nunca",
-  "page.users.admin.yes": "Sim",
-  "page.users.admin.no": "Não",
-  "page.users.actions": "Ações",
-  "page.users.last_login": "Último acesso",
-  "page.users.is_admin": "Administrador",
-  "page.settings.title": "Ajustes",
-  "page.settings.link_google_account": "Vincular minha conta do Google",
-  "page.settings.unlink_google_account": "Desvincular minha conta do Google",
-  "page.settings.link_oidc_account": "Vincular minha conta do OpenID Connect",
-  "page.settings.unlink_oidc_account": "Desvincular minha conta do OpenID Connect",
-  "page.login.title": "Iniciar Sessão",
-  "page.login.google_signin": "Iniciar Sessão com sua conta do Google",
-  "page.login.oidc_signin": "Iniciar Sessão com sua conta do OpenID Connect",
-  "page.integrations.title": "Integrações",
-  "page.integration.miniflux_api": "API do Miniflux",
-  "page.integration.miniflux_api_endpoint": "Endpoint da API",
-  "page.integration.miniflux_api_username": "Nome de usuário",
-  "page.integration.miniflux_api_password": "Senha",
-  "page.integration.miniflux_api_password_value": "Senha da sua Conta",
-  "page.integration.bookmarklet": "Bookmarklet",
-  "page.integration.bookmarklet.name": "Adicionar ao Miniflux",
-  "page.integration.bookmarklet.instructions": "Arrasta e solta esse link para os favoritos do teu navegador.",
-  "page.integration.bookmarklet.help": "Esse link especial permite você se inscrever a um site diretamente usando favorito do navegador.",
-  "page.sessions.title": "Sessões",
-  "page.sessions.table.date": "Data",
-  "page.sessions.table.ip": "Endereço IP",
-  "page.sessions.table.user_agent": "Agente de usuário",
-  "page.sessions.table.actions": "Ações",
-  "page.sessions.table.current_session": "Sessão Atual",
-  "page.api_keys.title": "Chaves de API",
-  "page.api_keys.table.description": "Descrição",
-  "page.api_keys.table.token": "Token",
-  "page.api_keys.table.last_used_at": "Ultima utilização",
-  "page.api_keys.table.created_at": "Data de criação",
-  "page.api_keys.table.actions": "Ações",
-  "page.api_keys.never_used": "Nunca usado",
-  "page.new_api_key.title": "Nova chave de API",
-  "alert.no_shared_entry": "Não há itens compartilhados.",
-  "alert.no_bookmark": "Não há favorito neste momento.",
-  "alert.no_category": "Não há categoria.",
-  "alert.no_category_entry": "Não há itens nesta categoria.",
-  "alert.no_feed_entry": "Não há itens nessa fonte.",
-  "alert.no_feed": "Não há inscrições.",
-  "alert.no_feed_in_category": "Não há inscrições nessa categoria.",
-  "alert.no_history": "Não há histórico nesse momento.",
-  "alert.feed_error": "Ocorreu um problema com esta fonte.",
-  "alert.no_search_result": "Não há resultados para essa busca.",
-  "alert.no_unread_entry": "Não há itens não lidos.",
-  "alert.no_user": "Você é o único usuário.",
-  "alert.account_unlinked": "Sua conta externa está desvinculada!",
-  "alert.account_linked": "Sua conta externa está vinculada!",
-  "alert.pocket_linked": "Sua conta do Pocket está vinculada!",
-  "alert.prefs_saved": "Suas preferências foram salvas!",
-  "error.unlink_account_without_password": "Você deve definir uma senha, senão não será possível efetuar a sessão novamente.",
-  "error.duplicate_linked_account": "Alguém já está vinculado a esse serviço!",
-  "error.duplicate_fever_username": "Alguém já está utilizando esse nome de usuário do Fever!",
-  "error.pocket_request_token": "Não foi possível obter um pedido de token no Pocket!",
-  "error.pocket_access_token": "Não foi possível obter um token de acesso no Pocket!",
-  "error.category_already_exists": "Esta categoria já existe.",
-  "error.unable_to_create_category": "Não foi possível criar essa categoria.",
-  "error.unable_to_update_category": "Não foi possível atualizar essa categoria.",
-  "error.user_already_exists": "Esse usuário já existe.",
-  "error.unable_to_create_user": "Não foi possível criar esse usuário.",
-  "error.unable_to_update_user": "Não foi possível atualizar esse usuário.",
-  "error.unable_to_update_feed": "Não foi possível atualizar essa fonte.",
-  "error.subscription_not_found": "Não foi possível encontrar uma inscrição.",
-  "error.empty_file": "Esse arquivo está vazio.",
-  "error.bad_credentials": "Usuário ou senha são inválidos.",
-  "error.fields_mandatory": "Todos os campos são obrigatórios.",
-  "error.title_required": "O título é obrigatório.",
-  "error.different_passwords": "As senhas não são iguais.",
-  "error.password_min_length": "A senha deve ter no mínimo 6 caracteres.",
-  "error.settings_mandatory_fields": "Os campos de nome de usuário, tema, idioma e fuso horário são obrigatórios.",
-  "error.entries_per_page_invalid": "O número de itens por página é inválido.",
-  "error.feed_mandatory_fields": "O campo de URL e categoria são obrigatórios.",
-  "error.user_mandatory_fields": "O nome de usuário é obrigatório.",
-  "error.api_key_already_exists": "Essa chave de API já existe.",
-  "error.unable_to_create_api_key": "Não foi possível criar uma chave de API.",
-  "form.feed.label.title": "Título",
-  "form.feed.label.site_url": "URL do site",
-  "form.feed.label.feed_url": "URL da fonte",
-  "form.feed.label.category": "Categoria",
-  "form.feed.label.crawler": "Obter conteúdo original",
-  "form.feed.label.feed_username": "Nome de usuário da fonte",
-  "form.feed.label.feed_password": "Senha da fonte",
-  "form.feed.label.user_agent": "Sobrescrever o agente de usuário (user-agent) padrão",
-  "form.feed.label.scraper_rules": "Regras do scraper",
-  "form.feed.label.rewrite_rules": "Regras para o Rewrite",
-  "form.feed.label.ignore_http_cache": "Ignorar cache HTTP",
-  "form.feed.label.disabled": "Não atualizar esta fonte",
-  "form.category.label.title": "Título",
-  "form.user.label.username": "Nome de usuário",
-  "form.user.label.password": "Senha",
-  "form.user.label.confirmation": "Confirmação de senha",
-  "form.user.label.admin": "Administrador",
-  "form.prefs.label.language": "Idioma",
-  "form.prefs.label.timezone": "Fuso horário",
-  "form.prefs.label.theme": "Tema",
-  "form.prefs.label.entry_sorting": "Ordenação dos itens",
-  "form.prefs.label.entries_per_page": "Itens por página",
-  "form.prefs.select.older_first": "Itens mais velhos primeiro",
-  "form.prefs.select.recent_first": "Itens mais recentes",
-  "form.prefs.label.keyboard_shortcuts": "Habilitar atalhos do teclado",
-  "form.prefs.label.custom_css": "CSS customizado",
-  "form.import.label.file": "Arquivo OPML",
-  "form.import.label.url": "URL",
-  "form.integration.fever_activate": "Ativar API do Fever",
-  "form.integration.fever_username": "Nome de usuário do Fever",
-  "form.integration.fever_password": "Senha do Fever",
-  "form.integration.fever_endpoint": "Endpoint da API do Fever:",
-  "form.integration.pinboard_activate": "Salvar itens no Pinboard",
-  "form.integration.pinboard_token": "Token de API do Pinboard",
-  "form.integration.pinboard_tags": "Etiquetas (tags) do Pinboard",
-  "form.integration.pinboard_bookmark": "Salvar marcador como não lído",
-  "form.integration.instapaper_activate": "Salvar itens no Instapaper",
-  "form.integration.instapaper_username": "Nome do usuário do Instapaper",
-  "form.integration.instapaper_password": "Senha do Instapaper",
-  "form.integration.pocket_activate": "Salvar itens no Pocket",
-  "form.integration.pocket_consumer_key": "Chave de consumo (Consumer Key) do Pocket",
-  "form.integration.pocket_access_token": "Token de acesso do Pocket",
-  "form.integration.pocket_connect_link": "Conectar a conta do Pocket",
-  "form.integration.wallabag_activate": "Salvar itens no Wallabag",
-  "form.integration.wallabag_endpoint": "Endpoint da API do Wallabag",
-  "form.integration.wallabag_client_id": "ID de cliente (Client ID) do Wallabag",
-  "form.integration.wallabag_client_secret": "Segredo do cliente (Client Secret) do Wallabag",
-  "form.integration.wallabag_username": "Nome de usuário do Wallabag",
-  "form.integration.wallabag_password": "Senha do Wallabag",
-  "form.integration.nunux_keeper_activate": "Salvar itens no Nunux Keeper",
-  "form.integration.nunux_keeper_endpoint": "Endpoint de API do Nunux Keeper",
-  "form.integration.nunux_keeper_api_key": "Chave de API do Nunux Keeper",
-  "form.api_key.label.description": "Etiqueta da chave de API",
-  "form.submit.loading": "Carregando...",
-  "form.submit.saving": "Salvando...",
-  "time_elapsed.not_yet": "ainda não",
-  "time_elapsed.yesterday": "ontem",
-  "time_elapsed.now": "agora mesmo",
-  "time_elapsed.minutes": [
-      "há %d minuto",
-      "há %d minutos"
-  ],
-  "time_elapsed.hours": [
-      "há %d hora",
-      "há %d horas"
-  ],
-  "time_elapsed.days": [
-      "há %d dia",
-      "há %d dias"
-  ],
-  "time_elapsed.weeks": [
-      "há %d semana",
-      "há %d semanas"
-  ],
-  "time_elapsed.months": [
-      "há %d mês",
-      "há %d meses"
-  ],
-  "time_elapsed.years": [
-      "há %d ano",
-      "há %d anos"
-  ]
+    "confirm.question": "Tem certeza?",
+    "confirm.yes": "Sim",
+    "confirm.no": "Não",
+    "confirm.loading": "Carregando...",
+    "action.subscribe": "Inscrever",
+    "action.save": "Salvar",
+    "action.or": "Ou",
+    "action.cancel": "Cancelar",
+    "action.remove": "Remover",
+    "action.remove_feed": "Remover fonte",
+    "action.update": "Atualizar",
+    "action.edit": "Editar",
+    "action.download": "Baixar",
+    "action.import": "Importar",
+    "action.login": "Iniciar sessão",
+    "action.home_screen": "Voltar para a tela inicial",
+    "tooltip.keyboard_shortcuts": "Atalho do teclado: %s",
+    "tooltip.logged_user": "Autenticado como %s",
+    "menu.unread": "Não lido",
+    "menu.starred": "Favoritos",
+    "menu.history": "Histórico",
+    "menu.feeds": "Fontes",
+    "menu.categories": "Categorias",
+    "menu.settings": "Configurações",
+    "menu.logout": "Encerrar sessão",
+    "menu.preferences": "Preferências",
+    "menu.integrations": "Integrações",
+    "menu.sessions": "Sessões",
+    "menu.users": "Usuários",
+    "menu.about": "Sobre",
+    "menu.export": "Exportar",
+    "menu.import": "Importar",
+    "menu.create_category": "Criar uma categoria",
+    "menu.mark_page_as_read": "Marcar essa página como lída",
+    "menu.mark_all_as_read": "Marcar todos como lido",
+    "menu.show_all_entries": "Mostrar todas os itens",
+    "menu.show_only_unread_entries": "Mostrar apenas itens não lidos",
+    "menu.refresh_feed": "Atualizar",
+    "menu.refresh_all_feeds": "Atualizar todas as fontes",
+    "menu.edit_feed": "Editar",
+    "menu.edit_category": "Editar",
+    "menu.add_feed": "Adicionar inscrição",
+    "menu.add_user": "Adicionar usuário",
+    "menu.flush_history": "Limpar histórico",
+    "menu.feed_entries": "Itens",
+    "menu.api_keys": "Chaves de API",
+    "menu.create_api_key": "Criar uma nova chave de API",
+    "menu.shared_entries": "Itens compartilhados",
+    "search.label": "Buscar",
+    "search.placeholder": "Buscar por...",
+    "pagination.next": "Próximo",
+    "pagination.previous": "Anterior",
+    "entry.status.unread": "Não lido",
+    "entry.status.read": "Lido",
+    "entry.status.toast.unread": "Marcado como não lido",
+    "entry.status.toast.read": "Marcado como lido",
+    "entry.status.title": "Modificar estado deste item",
+    "entry.bookmark.toggle.on": "Marcar",
+    "entry.bookmark.toggle.off": "Desmarcar",
+    "entry.bookmark.toast.on": "Favoritado",
+    "entry.bookmark.toast.off": "Desfavoritado",
+    "entry.state.saving": "Salvando...",
+    "entry.state.loading": "Carregando...",
+    "entry.save.label": "Salvar",
+    "entry.save.title": "Salvar esse item",
+    "entry.save.completed": "Feito!",
+    "entry.save.toast.completed": "Item guardado",
+    "entry.scraper.label": "Conteúdo completo",
+    "entry.scraper.title": "Obter conteúdo completo",
+    "entry.scraper.completed": "Feito!",
+    "entry.original.label": "Original",
+    "entry.comments.label": "Comentários",
+    "entry.comments.title": "Ver comentários",
+    "entry.share.label": "Compartilhar",
+    "entry.share.title": "Compartilhar esse item",
+    "entry.unshare.label": "Descompartilhar",
+    "entry.shared_entry.title": "Abrir link público",
+    "entry.shared_entry.label": "Compartilhar",
+    "entry.estimated_reading_time": [
+        "%d minuto lido",
+        "%d minutos lidos"
+    ],
+    "page.shared_entries.title": "Itens compartilhados",
+    "page.unread.title": "Não lídos",
+    "page.starred.title": "Favoritos",
+    "page.categories.title": "Categorias",
+    "page.categories.no_feed": "Sem fonte.",
+    "page.categories.entries": "Itens",
+    "page.categories.feeds": "Inscrições",
+    "page.categories.feed_count": [
+        "Existe %d fonte.",
+        "Existem %d fontes."
+    ],
+    "page.new_category.title": "Nova categoria",
+    "page.new_user.title": "Novo usuário",
+    "page.edit_category.title": "Editar categoria: %s",
+    "page.edit_user.title": "Editar usuário: %s",
+    "page.feeds.title": "Fontes",
+    "page.feeds.last_check": "Última verificação:",
+    "page.feeds.unread_counter": "Numero de itens não lidos",
+    "page.feeds.read_counter": "Número de itens lidos",
+    "page.feeds.error_count": [
+        "%d erro",
+        "%d erros"
+    ],
+    "page.history.title": "Histórico",
+    "page.import.title": "Importar",
+    "page.search.title": "Resultados da busca",
+    "page.about.title": "Sobre",
+    "page.about.credits": "Créditos",
+    "page.about.version": "Versão:",
+    "page.about.build_date": "Compilado em:",
+    "page.about.author": "Autor:",
+    "page.about.license": "Licença:",
+    "page.add_feed.title": "Nova inscrição",
+    "page.add_feed.no_category": "Não existe uma categoria. Deve existir pelo menos uma categoria.",
+    "page.add_feed.label.url": "URL",
+    "page.add_feed.submit": "Buscar uma fonte",
+    "page.add_feed.legend.advanced_options": "Opções avançadas",
+    "page.add_feed.choose_feed": "Escolher uma fonte",
+    "page.edit_feed.title": "Editar fonte: %s",
+    "page.edit_feed.last_check": "Última verificação:",
+    "page.edit_feed.last_modified_header": "Cabeçalho 'LastModified':",
+    "page.edit_feed.etag_header": "Cabeçalho 'ETag':",
+    "page.edit_feed.no_header": "Sem cabeçalhos",
+    "page.edit_feed.last_parsing_error": "Último erro durante processamento",
+    "page.entry.attachments": "Anexos",
+    "page.keyboard_shortcuts.title": "Atalhos de teclado",
+    "page.keyboard_shortcuts.subtitle.sections": "Navegação de seções",
+    "page.keyboard_shortcuts.subtitle.items": "Navegação de itens",
+    "page.keyboard_shortcuts.subtitle.pages": "Navegação de páginas",
+    "page.keyboard_shortcuts.subtitle.actions": "Ações",
+    "page.keyboard_shortcuts.go_to_unread": "Ir aos não lidos",
+    "page.keyboard_shortcuts.go_to_starred": "Ir aos favoritos",
+    "page.keyboard_shortcuts.go_to_history": "Ir ao histórico",
+    "page.keyboard_shortcuts.go_to_feeds": "Ir as inscrições",
+    "page.keyboard_shortcuts.go_to_categories": "Ir as categorias",
+    "page.keyboard_shortcuts.go_to_settings": "Ir as configurações",
+    "page.keyboard_shortcuts.show_keyboard_shortcuts": "Mostrar atalhos de teclado",
+    "page.keyboard_shortcuts.go_to_previous_item": "Ir ao item anterior",
+    "page.keyboard_shortcuts.go_to_next_item": "Ir ao tem seguinte",
+    "page.keyboard_shortcuts.go_to_feed": "Ir a fonte",
+    "page.keyboard_shortcuts.go_to_previous_page": "Ir a página anterior",
+    "page.keyboard_shortcuts.go_to_next_page": "Ir a página seguinte",
+    "page.keyboard_shortcuts.open_item": "Abrir o item selecionado",
+    "page.keyboard_shortcuts.open_original": "Abrir o conteúdo original",
+    "page.keyboard_shortcuts.open_original_same_window": "Abrir o conteúdo original na janela atual",
+    "page.keyboard_shortcuts.open_comments": "Abrir os comentários",
+    "page.keyboard_shortcuts.open_comments_same_window": "Abrir os comentários na janela atual",
+    "page.keyboard_shortcuts.toggle_read_status": "Inverter estado de leitura do item",
+    "page.keyboard_shortcuts.refresh_all_feeds": "Atualizar todas as fontes",
+    "page.keyboard_shortcuts.mark_page_as_read": "Marcar página atual como lida",
+    "page.keyboard_shortcuts.download_content": "Buscar o conteúdo original",
+    "page.keyboard_shortcuts.toggle_bookmark_status": "Marcar ou desmarcar como favorito",
+    "page.keyboard_shortcuts.save_article": "Salvar item",
+    "page.keyboard_shortcuts.remove_feed": "Remover essa fonte",
+    "page.keyboard_shortcuts.go_to_search": "Ir para o campo de busca",
+    "page.keyboard_shortcuts.close_modal": "Fechar janela",
+    "page.users.title": "Usuários",
+    "page.users.username": "Nome de usuário",
+    "page.users.never_logged": "Nunca",
+    "page.users.admin.yes": "Sim",
+    "page.users.admin.no": "Não",
+    "page.users.actions": "Ações",
+    "page.users.last_login": "Último acesso",
+    "page.users.is_admin": "Administrador",
+    "page.settings.title": "Ajustes",
+    "page.settings.link_google_account": "Vincular minha conta do Google",
+    "page.settings.unlink_google_account": "Desvincular minha conta do Google",
+    "page.settings.link_oidc_account": "Vincular minha conta do OpenID Connect",
+    "page.settings.unlink_oidc_account": "Desvincular minha conta do OpenID Connect",
+    "page.login.title": "Iniciar Sessão",
+    "page.login.google_signin": "Iniciar Sessão com sua conta do Google",
+    "page.login.oidc_signin": "Iniciar Sessão com sua conta do OpenID Connect",
+    "page.integrations.title": "Integrações",
+    "page.integration.miniflux_api": "API do Miniflux",
+    "page.integration.miniflux_api_endpoint": "Endpoint da API",
+    "page.integration.miniflux_api_username": "Nome de usuário",
+    "page.integration.miniflux_api_password": "Senha",
+    "page.integration.miniflux_api_password_value": "Senha da sua Conta",
+    "page.integration.bookmarklet": "Bookmarklet",
+    "page.integration.bookmarklet.name": "Adicionar ao Miniflux",
+    "page.integration.bookmarklet.instructions": "Arrasta e solta esse link para os favoritos do teu navegador.",
+    "page.integration.bookmarklet.help": "Esse link especial permite você se inscrever a um site diretamente usando favorito do navegador.",
+    "page.sessions.title": "Sessões",
+    "page.sessions.table.date": "Data",
+    "page.sessions.table.ip": "Endereço IP",
+    "page.sessions.table.user_agent": "Agente de usuário",
+    "page.sessions.table.actions": "Ações",
+    "page.sessions.table.current_session": "Sessão Atual",
+    "page.api_keys.title": "Chaves de API",
+    "page.api_keys.table.description": "Descrição",
+    "page.api_keys.table.token": "Token",
+    "page.api_keys.table.last_used_at": "Ultima utilização",
+    "page.api_keys.table.created_at": "Data de criação",
+    "page.api_keys.table.actions": "Ações",
+    "page.api_keys.never_used": "Nunca usado",
+    "page.new_api_key.title": "Nova chave de API",
+    "alert.no_shared_entry": "Não há itens compartilhados.",
+    "alert.no_bookmark": "Não há favorito neste momento.",
+    "alert.no_category": "Não há categoria.",
+    "alert.no_category_entry": "Não há itens nesta categoria.",
+    "alert.no_feed_entry": "Não há itens nessa fonte.",
+    "alert.no_feed": "Não há inscrições.",
+    "alert.no_feed_in_category": "Não há inscrições nessa categoria.",
+    "alert.no_history": "Não há histórico nesse momento.",
+    "alert.feed_error": "Ocorreu um problema com esta fonte.",
+    "alert.no_search_result": "Não há resultados para essa busca.",
+    "alert.no_unread_entry": "Não há itens não lidos.",
+    "alert.no_user": "Você é o único usuário.",
+    "alert.account_unlinked": "Sua conta externa está desvinculada!",
+    "alert.account_linked": "Sua conta externa está vinculada!",
+    "alert.pocket_linked": "Sua conta do Pocket está vinculada!",
+    "alert.prefs_saved": "Suas preferências foram salvas!",
+    "error.unlink_account_without_password": "Você deve definir uma senha, senão não será possível efetuar a sessão novamente.",
+    "error.duplicate_linked_account": "Alguém já está vinculado a esse serviço!",
+    "error.duplicate_fever_username": "Alguém já está utilizando esse nome de usuário do Fever!",
+    "error.pocket_request_token": "Não foi possível obter um pedido de token no Pocket!",
+    "error.pocket_access_token": "Não foi possível obter um token de acesso no Pocket!",
+    "error.category_already_exists": "Esta categoria já existe.",
+    "error.unable_to_create_category": "Não foi possível criar essa categoria.",
+    "error.unable_to_update_category": "Não foi possível atualizar essa categoria.",
+    "error.user_already_exists": "Esse usuário já existe.",
+    "error.unable_to_create_user": "Não foi possível criar esse usuário.",
+    "error.unable_to_update_user": "Não foi possível atualizar esse usuário.",
+    "error.unable_to_update_feed": "Não foi possível atualizar essa fonte.",
+    "error.subscription_not_found": "Não foi possível encontrar uma inscrição.",
+    "error.empty_file": "Esse arquivo está vazio.",
+    "error.bad_credentials": "Usuário ou senha são inválidos.",
+    "error.fields_mandatory": "Todos os campos são obrigatórios.",
+    "error.title_required": "O título é obrigatório.",
+    "error.different_passwords": "As senhas não são iguais.",
+    "error.password_min_length": "A senha deve ter no mínimo 6 caracteres.",
+    "error.settings_mandatory_fields": "Os campos de nome de usuário, tema, idioma e fuso horário são obrigatórios.",
+    "error.entries_per_page_invalid": "O número de itens por página é inválido.",
+    "error.feed_mandatory_fields": "O campo de URL e categoria são obrigatórios.",
+    "error.user_mandatory_fields": "O nome de usuário é obrigatório.",
+    "error.api_key_already_exists": "Essa chave de API já existe.",
+    "error.unable_to_create_api_key": "Não foi possível criar uma chave de API.",
+    "form.feed.label.title": "Título",
+    "form.feed.label.site_url": "URL do site",
+    "form.feed.label.feed_url": "URL da fonte",
+    "form.feed.label.category": "Categoria",
+    "form.feed.label.crawler": "Obter conteúdo original",
+    "form.feed.label.feed_username": "Nome de usuário da fonte",
+    "form.feed.label.feed_password": "Senha da fonte",
+    "form.feed.label.user_agent": "Sobrescrever o agente de usuário (user-agent) padrão",
+    "form.feed.label.scraper_rules": "Regras do scraper",
+    "form.feed.label.rewrite_rules": "Regras para o Rewrite",
+    "form.feed.label.ignore_http_cache": "Ignorar cache HTTP",
+    "form.feed.label.disabled": "Não atualizar esta fonte",
+    "form.category.label.title": "Título",
+    "form.user.label.username": "Nome de usuário",
+    "form.user.label.password": "Senha",
+    "form.user.label.confirmation": "Confirmação de senha",
+    "form.user.label.admin": "Administrador",
+    "form.prefs.label.language": "Idioma",
+    "form.prefs.label.timezone": "Fuso horário",
+    "form.prefs.label.theme": "Tema",
+    "form.prefs.label.entry_sorting": "Ordenação dos itens",
+    "form.prefs.label.entries_per_page": "Itens por página",
+    "form.prefs.select.older_first": "Itens mais velhos primeiro",
+    "form.prefs.select.recent_first": "Itens mais recentes",
+    "form.prefs.label.keyboard_shortcuts": "Habilitar atalhos do teclado",
+    "form.prefs.label.show_reading_time": "Mostrar tempo estimado de leitura de artigos",
+    "form.prefs.label.custom_css": "CSS customizado",
+    "form.import.label.file": "Arquivo OPML",
+    "form.import.label.url": "URL",
+    "form.integration.fever_activate": "Ativar API do Fever",
+    "form.integration.fever_username": "Nome de usuário do Fever",
+    "form.integration.fever_password": "Senha do Fever",
+    "form.integration.fever_endpoint": "Endpoint da API do Fever:",
+    "form.integration.pinboard_activate": "Salvar itens no Pinboard",
+    "form.integration.pinboard_token": "Token de API do Pinboard",
+    "form.integration.pinboard_tags": "Etiquetas (tags) do Pinboard",
+    "form.integration.pinboard_bookmark": "Salvar marcador como não lído",
+    "form.integration.instapaper_activate": "Salvar itens no Instapaper",
+    "form.integration.instapaper_username": "Nome do usuário do Instapaper",
+    "form.integration.instapaper_password": "Senha do Instapaper",
+    "form.integration.pocket_activate": "Salvar itens no Pocket",
+    "form.integration.pocket_consumer_key": "Chave de consumo (Consumer Key) do Pocket",
+    "form.integration.pocket_access_token": "Token de acesso do Pocket",
+    "form.integration.pocket_connect_link": "Conectar a conta do Pocket",
+    "form.integration.wallabag_activate": "Salvar itens no Wallabag",
+    "form.integration.wallabag_endpoint": "Endpoint da API do Wallabag",
+    "form.integration.wallabag_client_id": "ID de cliente (Client ID) do Wallabag",
+    "form.integration.wallabag_client_secret": "Segredo do cliente (Client Secret) do Wallabag",
+    "form.integration.wallabag_username": "Nome de usuário do Wallabag",
+    "form.integration.wallabag_password": "Senha do Wallabag",
+    "form.integration.nunux_keeper_activate": "Salvar itens no Nunux Keeper",
+    "form.integration.nunux_keeper_endpoint": "Endpoint de API do Nunux Keeper",
+    "form.integration.nunux_keeper_api_key": "Chave de API do Nunux Keeper",
+    "form.api_key.label.description": "Etiqueta da chave de API",
+    "form.submit.loading": "Carregando...",
+    "form.submit.saving": "Salvando...",
+    "time_elapsed.not_yet": "ainda não",
+    "time_elapsed.yesterday": "ontem",
+    "time_elapsed.now": "agora mesmo",
+    "time_elapsed.minutes": [
+        "há %d minuto",
+        "há %d minutos"
+    ],
+    "time_elapsed.hours": [
+        "há %d hora",
+        "há %d horas"
+    ],
+    "time_elapsed.days": [
+        "há %d dia",
+        "há %d dias"
+    ],
+    "time_elapsed.weeks": [
+        "há %d semana",
+        "há %d semanas"
+    ],
+    "time_elapsed.months": [
+        "há %d mês",
+        "há %d meses"
+    ],
+    "time_elapsed.years": [
+        "há %d ano",
+        "há %d anos"
+    ]
 }
 `,
 	"ru_RU": `{
@@ -3046,6 +3091,10 @@ var translations = map[string]string{
     "entry.unshare.label": "Удалить из общедоступных",
     "entry.shared_entry.title": "Открыть публичную ссылку",
     "entry.shared_entry.label": "Поделиться",
+    "entry.estimated_reading_time": [
+        "%d минута чтения",
+        "%d минут чтения"
+    ],
     "page.shared_entries.title": "Общедоступные записи",
     "page.unread.title": "Непрочитанное",
     "page.starred.title": "Избранное",
@@ -3230,6 +3279,7 @@ var translations = map[string]string{
     "form.prefs.select.older_first": "Сначала старые записи",
     "form.prefs.select.recent_first": "Сначала последние записи",
     "form.prefs.label.keyboard_shortcuts": "Включить сочетания клавиш",
+    "form.prefs.label.show_reading_time": "Показать примерное время чтения статей",
     "form.prefs.label.custom_css": "Пользовательские CSS",
     "form.import.label.file": "OPML файл",
     "form.import.label.url": "URL",
@@ -3374,6 +3424,10 @@ var translations = map[string]string{
     "entry.unshare.label": "取消分享",
     "entry.shared_entry.title": "打开公共链接",
     "entry.shared_entry.label": "分享分享",
+    "entry.estimated_reading_time": [
+        "%d分钟阅读",
+        "%d分钟阅读"
+    ],
     "page.shared_entries.title": "共享条目",
     "page.unread.title": "未读",
     "page.starred.title": "星标",
@@ -3554,6 +3608,7 @@ var translations = map[string]string{
     "form.prefs.select.older_first": "旧->新",
     "form.prefs.select.recent_first": "新->旧",
     "form.prefs.label.keyboard_shortcuts": "启用键盘快捷键",
+    "form.prefs.label.show_reading_time": "显示文章的预计阅读时间",
     "form.prefs.label.custom_css": "自定义CSS",
     "form.import.label.file": "OPML 文件",
     "form.import.label.url": "URL",
@@ -3629,15 +3684,15 @@ var translations = map[string]string{
 }
 
 var translationsChecksums = map[string]string{
-	"de_DE": "e986a40b1748968725ddede18ae6451e4d1ae270b9c4c033daa81ee50b1d306e",
-	"en_US": "b27169fc7767e51e6f7610ff1844708e8111e527c7931e3f888864a66826e293",
-	"es_ES": "20a713468ca6ce00e899a80354912e927ded61cf8a79ad9d976c78f515e242dd",
-	"fr_FR": "251eb14fe8521bde772d293fa748307ecd4cae4b0597da03aad39e745a382f11",
-	"it_IT": "8ab664ec8d826aa3702a4f5294c3a3e87193437e64b0ef4990a3a9609b782786",
-	"ja_JP": "7dc146dc5815a8d6dbae2f7f467deea598a85099bbee63e92bf3862d445519af",
-	"nl_NL": "fd106f08b2f8902712a68716a0e33b063bdce32a8440f7a2b296b4f822088403",
-	"pl_PL": "85de665d29e873f6099ef5ea40efe569a05ec3cbf08e4ca7741778bf3d5c8593",
-	"pt_BR": "193847c5691590926e684894ef971141f69c0e2aaf270457e10331d75a29e34b",
-	"ru_RU": "6e765e44e250469fe1c5666f8ff24e5e07e6b04098c1325c2663a1f722e0bfe9",
-	"zh_CN": "0dc8c5b86a03f0ce58f6d2633ab3011d9bc8004af18f922944a65d151e54beda",
+	"de_DE": "f3dd20afc5cbd9ac689bec2939c1d2fbe71372360c4e8f335e3b8cbce4228958",
+	"en_US": "30cbcb2170782f1e66f69066947bf053f68065d7b270eea879f2c573819dd52b",
+	"es_ES": "50dc7c8c2db7368bae133f5b455721470d314321153d41e4f27436a0f3f176e6",
+	"fr_FR": "373fd2db868961758bd1483c34f117b03aadea17080f268bc8bbd0acdfbc5eed",
+	"it_IT": "8d8f0bd75b4e7dec9370647c888dd9438b691130d9c41f839cdfff8cbc606cb5",
+	"ja_JP": "ec3a21c547e4625ad359624e43ba31b556fb8d8b8ff7fc7a20df089317db99b3",
+	"nl_NL": "20e180be2375f07ec02eb05f372a9102c13037a79e5651ce9bd41507fd2180d2",
+	"pl_PL": "b1526955641823708b4c1ca753b61e1e0561d0a3d33da3f62170540903031b0d",
+	"pt_BR": "cf8e131d39daac82d3157c6538c0643392a06358b7bc98be8579412ebd63f60e",
+	"ru_RU": "4056e4e94861835d44064273371adbbded7190e2b719769886eb99e6c9feaf82",
+	"zh_CN": "044abb0a34eee3d8d5597811d40166762311b8e4cd08b891796113790cc775f0",
 }

+ 5 - 0
locale/translations/de_DE.json

@@ -77,6 +77,10 @@
     "entry.unshare.label": "Nicht teilen",
     "entry.shared_entry.title": "Öffnen Sie den öffentlichen Link",
     "entry.shared_entry.label": "Teilen",
+    "entry.estimated_reading_time": [
+        "%d Minute gelesen",
+        "%d Minuten gelesen"
+    ],
     "page.shared_entries.title": "Geteilte Artikel",
     "page.unread.title": "Ungelesen",
     "page.starred.title": "Lesezeichen",
@@ -259,6 +263,7 @@
     "form.prefs.select.older_first": "Älteste Artikel zuerst",
     "form.prefs.select.recent_first": "Neueste Artikel zuerst",
     "form.prefs.label.keyboard_shortcuts": "Tastaturkürzel aktivieren",
+    "form.prefs.label.show_reading_time": "Geschätzte Lesezeit für Artikel anzeigen",
     "form.prefs.label.custom_css": "Benutzerdefiniertes CSS",
     "form.import.label.file": "OPML Datei",
     "form.import.label.url": "URL",

+ 5 - 0
locale/translations/en_US.json

@@ -77,6 +77,10 @@
     "entry.unshare.label": "Unshare",
     "entry.shared_entry.title": "Open the public link",
     "entry.shared_entry.label": "Share",
+    "entry.estimated_reading_time": [
+        "%d minute read",
+        "%d minutes read"
+    ],
     "page.shared_entries.title": "Shared Entries",
     "page.unread.title": "Unread",
     "page.starred.title": "Starred",
@@ -259,6 +263,7 @@
     "form.prefs.select.older_first": "Older entries first",
     "form.prefs.select.recent_first": "Recent entries first",
     "form.prefs.label.keyboard_shortcuts": "Enable keyboard shortcuts",
+    "form.prefs.label.show_reading_time": "Show estimated reading time for articles",
     "form.prefs.label.custom_css": "Custom CSS",
     "form.import.label.file": "OPML file",
     "form.import.label.url": "URL",

+ 5 - 0
locale/translations/es_ES.json

@@ -77,6 +77,10 @@
     "entry.unshare.label": "No compartir",
     "entry.shared_entry.title": "Abrir el enlace público",
     "entry.shared_entry.label": "Compartir",
+    "entry.estimated_reading_time": [
+        "%d minuto de lectura",
+        "%d minutos de lectura"
+    ],
     "page.shared_entries.title": "Entradas compartidas",
     "page.unread.title": "No leídos",
     "page.starred.title": "Marcadores",
@@ -259,6 +263,7 @@
     "form.prefs.select.older_first": "Entradas más viejas primero",
     "form.prefs.select.recent_first": "Entradas recientes primero",
     "form.prefs.label.keyboard_shortcuts": "Habilitar atajos de teclado",
+    "form.prefs.label.show_reading_time": "Mostrar el tiempo estimado de lectura de los artículos",
     "form.prefs.label.custom_css": "CSS personalizado",
     "form.import.label.file": "Archivo OPML",
     "form.import.label.url": "URL",

+ 5 - 0
locale/translations/fr_FR.json

@@ -77,6 +77,10 @@
     "entry.unshare.label": "Enlever le partage",
     "entry.shared_entry.title": "Ouvrir le lien public",
     "entry.shared_entry.label": "Partage",
+    "entry.estimated_reading_time": [
+        "%d minute de lecture",
+        "%d minutes de lecture"
+    ],
     "page.shared_entries.title": "Articles partagés",
     "page.unread.title": "Non lus",
     "page.starred.title": "Favoris",
@@ -259,6 +263,7 @@
     "form.prefs.select.older_first": "Ancien éléments en premier",
     "form.prefs.select.recent_first": "Éléments récents en premier",
     "form.prefs.label.keyboard_shortcuts": "Activer les raccourcis clavier",
+    "form.prefs.label.show_reading_time": "Afficher le temps de lecture estimé des articles",
     "form.prefs.label.custom_css": "CSS personnalisé",
     "form.import.label.file": "Fichier OPML",
     "form.import.label.url": "URL",

+ 5 - 0
locale/translations/it_IT.json

@@ -77,6 +77,10 @@
     "entry.unshare.label": "Unshare",
     "entry.shared_entry.title": "Apri il link pubblico",
     "entry.shared_entry.label": "Condivisione",
+    "entry.estimated_reading_time": [
+        "%d minuto di lettura",
+        "%d minuti di lettura"
+    ],
     "page.shared_entries.title": "Voci condivise",
     "page.unread.title": "Da leggere",
     "page.starred.title": "Preferiti",
@@ -259,6 +263,7 @@
     "form.prefs.select.older_first": "Prima i più vecchi",
     "form.prefs.select.recent_first": "Prima i più recenti",
     "form.prefs.label.keyboard_shortcuts": "Abilita le scorciatoie da tastiera",
+    "form.prefs.label.show_reading_time": "Mostra il tempo di lettura stimato per gli articoli",
     "form.prefs.label.custom_css": "CSS personalizzati",
     "form.import.label.file": "File OPML",
     "form.import.label.url": "URL",

+ 5 - 0
locale/translations/ja_JP.json

@@ -77,6 +77,10 @@
     "entry.unshare.label": "共有解除",
     "entry.shared_entry.title": "公開リンクを開く",
     "entry.shared_entry.label": "共有する",
+    "entry.estimated_reading_time": [
+        "%d分で読む",
+        "%d分で読む"
+    ],
     "page.shared_entries.title": "共有エントリ",
     "page.unread.title": "未読",
     "page.starred.title": "星付き",
@@ -259,6 +263,7 @@
     "form.prefs.select.older_first": "古い記事を最初に",
     "form.prefs.select.recent_first": "新しい記事を最初に",
     "form.prefs.label.keyboard_shortcuts": "キーボード・ショートカットを有効にする",
+    "form.prefs.label.show_reading_time": "記事の推定読書時間を表示する",
     "form.prefs.label.custom_css": "カスタムCSS",
     "form.import.label.file": "OPML ファイル",
     "form.import.label.url": "URL",

+ 5 - 0
locale/translations/nl_NL.json

@@ -77,6 +77,10 @@
     "entry.unshare.label": "Delen ongedaan maken",
     "entry.shared_entry.title": "Open de openbare link",
     "entry.shared_entry.label": "Delen",
+    "entry.estimated_reading_time": [
+        "%d minuut gelezen",
+        "%d minuten gelezen"
+    ],
     "page.shared_entries.title": "Gedeelde vermeldingen",
     "page.unread.title": "Ongelezen",
     "page.starred.title": "Favorieten",
@@ -259,6 +263,7 @@
     "form.prefs.select.older_first": "Oudere items eerst",
     "form.prefs.select.recent_first": "Recente items eerst",
     "form.prefs.label.keyboard_shortcuts": "Schakel sneltoetsen in",
+    "form.prefs.label.show_reading_time": "Toon geschatte leestijd voor artikelen",
     "form.prefs.label.custom_css": "Aangepaste CSS",
     "form.import.label.file": "OPML-bestand",
     "form.import.label.url": "URL",

+ 5 - 0
locale/translations/pl_PL.json

@@ -77,6 +77,10 @@
     "entry.unshare.label": "Unshare",
     "entry.shared_entry.title": "Otwórz publiczny link",
     "entry.shared_entry.label": "Udostępnianie",
+    "entry.estimated_reading_time": [
+        "%d minuta czytania",
+        "%d minut czytania"
+    ],
     "page.shared_entries.title": "Udostępnione wpisy",
     "page.unread.title": "Nieprzeczytane",
     "page.starred.title": "Oznaczone gwiazdką",
@@ -260,6 +264,7 @@
     "form.prefs.label.entries_per_page": "Wpisy na stronie",
     "form.prefs.select.older_first": "Najstarsze wpisy jako pierwsze",
     "form.prefs.label.keyboard_shortcuts": "Włącz skróty klawiaturowe",
+    "form.prefs.label.show_reading_time": "Pokaż szacowany czas czytania artykułów",
     "form.prefs.select.recent_first": "Najnowsze wpisy jako pierwsze",
     "form.prefs.label.custom_css": "Niestandardowy CSS",
     "form.import.label.file": "Plik OPML",

+ 322 - 317
locale/translations/pt_BR.json

@@ -1,319 +1,324 @@
 {
-  "confirm.question": "Tem certeza?",
-  "confirm.yes": "Sim",
-  "confirm.no": "Não",
-  "confirm.loading": "Carregando...",
-  "action.subscribe": "Inscrever",
-  "action.save": "Salvar",
-  "action.or": "Ou",
-  "action.cancel": "Cancelar",
-  "action.remove": "Remover",
-  "action.remove_feed": "Remover fonte",
-  "action.update": "Atualizar",
-  "action.edit": "Editar",
-  "action.download": "Baixar",
-  "action.import": "Importar",
-  "action.login": "Iniciar sessão",
-  "action.home_screen": "Voltar para a tela inicial",
-  "tooltip.keyboard_shortcuts": "Atalho do teclado: %s",
-  "tooltip.logged_user": "Autenticado como %s",
-  "menu.unread": "Não lido",
-  "menu.starred": "Favoritos",
-  "menu.history": "Histórico",
-  "menu.feeds": "Fontes",
-  "menu.categories": "Categorias",
-  "menu.settings": "Configurações",
-  "menu.logout": "Encerrar sessão",
-  "menu.preferences": "Preferências",
-  "menu.integrations": "Integrações",
-  "menu.sessions": "Sessões",
-  "menu.users": "Usuários",
-  "menu.about": "Sobre",
-  "menu.export": "Exportar",
-  "menu.import": "Importar",
-  "menu.create_category": "Criar uma categoria",
-  "menu.mark_page_as_read": "Marcar essa página como lída",
-  "menu.mark_all_as_read": "Marcar todos como lido",
-  "menu.show_all_entries": "Mostrar todas os itens",
-  "menu.show_only_unread_entries": "Mostrar apenas itens não lidos",
-  "menu.refresh_feed": "Atualizar",
-  "menu.refresh_all_feeds": "Atualizar todas as fontes",
-  "menu.edit_feed": "Editar",
-  "menu.edit_category": "Editar",
-  "menu.add_feed": "Adicionar inscrição",
-  "menu.add_user": "Adicionar usuário",
-  "menu.flush_history": "Limpar histórico",
-  "menu.feed_entries": "Itens",
-  "menu.api_keys": "Chaves de API",
-  "menu.create_api_key": "Criar uma nova chave de API",
-  "menu.shared_entries": "Itens compartilhados",
-  "search.label": "Buscar",
-  "search.placeholder": "Buscar por...",
-  "pagination.next": "Próximo",
-  "pagination.previous": "Anterior",
-  "entry.status.unread": "Não lido",
-  "entry.status.read": "Lido",
-  "entry.status.toast.unread": "Marcado como não lido",
-  "entry.status.toast.read": "Marcado como lido",
-  "entry.status.title": "Modificar estado deste item",
-  "entry.bookmark.toggle.on": "Marcar",
-  "entry.bookmark.toggle.off": "Desmarcar",
-  "entry.bookmark.toast.on": "Favoritado",
-  "entry.bookmark.toast.off": "Desfavoritado",
-  "entry.state.saving": "Salvando...",
-  "entry.state.loading": "Carregando...",
-  "entry.save.label": "Salvar",
-  "entry.save.title": "Salvar esse item",
-  "entry.save.completed": "Feito!",
-  "entry.save.toast.completed": "Item guardado",
-  "entry.scraper.label": "Conteúdo completo",
-  "entry.scraper.title": "Obter conteúdo completo",
-  "entry.scraper.completed": "Feito!",
-  "entry.original.label": "Original",
-  "entry.comments.label": "Comentários",
-  "entry.comments.title": "Ver comentários",
-  "entry.share.label": "Compartilhar",
-  "entry.share.title": "Compartilhar esse item",
-  "entry.unshare.label": "Descompartilhar",
-  "entry.shared_entry.title": "Abrir link público",
-  "entry.shared_entry.label": "Compartilhar",
-  "page.shared_entries.title": "Itens compartilhados",
-  "page.unread.title": "Não lídos",
-  "page.starred.title": "Favoritos",
-  "page.categories.title": "Categorias",
-  "page.categories.no_feed": "Sem fonte.",
-  "page.categories.entries": "Itens",
-  "page.categories.feeds": "Inscrições",
-  "page.categories.feed_count": [
-      "Existe %d fonte.",
-      "Existem %d fontes."
-  ],
-  "page.new_category.title": "Nova categoria",
-  "page.new_user.title": "Novo usuário",
-  "page.edit_category.title": "Editar categoria: %s",
-  "page.edit_user.title": "Editar usuário: %s",
-  "page.feeds.title": "Fontes",
-  "page.feeds.last_check": "Última verificação:",
-  "page.feeds.unread_counter": "Numero de itens não lidos",
-  "page.feeds.read_counter": "Número de itens lidos",
-  "page.feeds.error_count": [
-      "%d erro",
-      "%d erros"
-  ],
-  "page.history.title": "Histórico",
-  "page.import.title": "Importar",
-  "page.search.title": "Resultados da busca",
-  "page.about.title": "Sobre",
-  "page.about.credits": "Créditos",
-  "page.about.version": "Versão:",
-  "page.about.build_date": "Compilado em:",
-  "page.about.author": "Autor:",
-  "page.about.license": "Licença:",
-  "page.add_feed.title": "Nova inscrição",
-  "page.add_feed.no_category": "Não existe uma categoria. Deve existir pelo menos uma categoria.",
-  "page.add_feed.label.url": "URL",
-  "page.add_feed.submit": "Buscar uma fonte",
-  "page.add_feed.legend.advanced_options": "Opções avançadas",
-  "page.add_feed.choose_feed": "Escolher uma fonte",
-  "page.edit_feed.title": "Editar fonte: %s",
-  "page.edit_feed.last_check": "Última verificação:",
-  "page.edit_feed.last_modified_header": "Cabeçalho 'LastModified':",
-  "page.edit_feed.etag_header": "Cabeçalho 'ETag':",
-  "page.edit_feed.no_header": "Sem cabeçalhos",
-  "page.edit_feed.last_parsing_error": "Último erro durante processamento",
-  "page.entry.attachments": "Anexos",
-  "page.keyboard_shortcuts.title": "Atalhos de teclado",
-  "page.keyboard_shortcuts.subtitle.sections": "Navegação de seções",
-  "page.keyboard_shortcuts.subtitle.items": "Navegação de itens",
-  "page.keyboard_shortcuts.subtitle.pages": "Navegação de páginas",
-  "page.keyboard_shortcuts.subtitle.actions": "Ações",
-  "page.keyboard_shortcuts.go_to_unread": "Ir aos não lidos",
-  "page.keyboard_shortcuts.go_to_starred": "Ir aos favoritos",
-  "page.keyboard_shortcuts.go_to_history": "Ir ao histórico",
-  "page.keyboard_shortcuts.go_to_feeds": "Ir as inscrições",
-  "page.keyboard_shortcuts.go_to_categories": "Ir as categorias",
-  "page.keyboard_shortcuts.go_to_settings": "Ir as configurações",
-  "page.keyboard_shortcuts.show_keyboard_shortcuts": "Mostrar atalhos de teclado",
-  "page.keyboard_shortcuts.go_to_previous_item": "Ir ao item anterior",
-  "page.keyboard_shortcuts.go_to_next_item": "Ir ao tem seguinte",
-  "page.keyboard_shortcuts.go_to_feed": "Ir a fonte",
-  "page.keyboard_shortcuts.go_to_previous_page": "Ir a página anterior",
-  "page.keyboard_shortcuts.go_to_next_page": "Ir a página seguinte",
-  "page.keyboard_shortcuts.open_item": "Abrir o item selecionado",
-  "page.keyboard_shortcuts.open_original": "Abrir o conteúdo original",
-  "page.keyboard_shortcuts.open_original_same_window": "Abrir o conteúdo original na janela atual",
-  "page.keyboard_shortcuts.open_comments": "Abrir os comentários",
-  "page.keyboard_shortcuts.open_comments_same_window": "Abrir os comentários na janela atual",
-  "page.keyboard_shortcuts.toggle_read_status": "Inverter estado de leitura do item",
-  "page.keyboard_shortcuts.refresh_all_feeds": "Atualizar todas as fontes",
-  "page.keyboard_shortcuts.mark_page_as_read": "Marcar página atual como lida",
-  "page.keyboard_shortcuts.download_content": "Buscar o conteúdo original",
-  "page.keyboard_shortcuts.toggle_bookmark_status": "Marcar ou desmarcar como favorito",
-  "page.keyboard_shortcuts.save_article": "Salvar item",
-  "page.keyboard_shortcuts.remove_feed": "Remover essa fonte",
-  "page.keyboard_shortcuts.go_to_search": "Ir para o campo de busca",
-  "page.keyboard_shortcuts.close_modal": "Fechar janela",
-  "page.users.title": "Usuários",
-  "page.users.username": "Nome de usuário",
-  "page.users.never_logged": "Nunca",
-  "page.users.admin.yes": "Sim",
-  "page.users.admin.no": "Não",
-  "page.users.actions": "Ações",
-  "page.users.last_login": "Último acesso",
-  "page.users.is_admin": "Administrador",
-  "page.settings.title": "Ajustes",
-  "page.settings.link_google_account": "Vincular minha conta do Google",
-  "page.settings.unlink_google_account": "Desvincular minha conta do Google",
-  "page.settings.link_oidc_account": "Vincular minha conta do OpenID Connect",
-  "page.settings.unlink_oidc_account": "Desvincular minha conta do OpenID Connect",
-  "page.login.title": "Iniciar Sessão",
-  "page.login.google_signin": "Iniciar Sessão com sua conta do Google",
-  "page.login.oidc_signin": "Iniciar Sessão com sua conta do OpenID Connect",
-  "page.integrations.title": "Integrações",
-  "page.integration.miniflux_api": "API do Miniflux",
-  "page.integration.miniflux_api_endpoint": "Endpoint da API",
-  "page.integration.miniflux_api_username": "Nome de usuário",
-  "page.integration.miniflux_api_password": "Senha",
-  "page.integration.miniflux_api_password_value": "Senha da sua Conta",
-  "page.integration.bookmarklet": "Bookmarklet",
-  "page.integration.bookmarklet.name": "Adicionar ao Miniflux",
-  "page.integration.bookmarklet.instructions": "Arrasta e solta esse link para os favoritos do teu navegador.",
-  "page.integration.bookmarklet.help": "Esse link especial permite você se inscrever a um site diretamente usando favorito do navegador.",
-  "page.sessions.title": "Sessões",
-  "page.sessions.table.date": "Data",
-  "page.sessions.table.ip": "Endereço IP",
-  "page.sessions.table.user_agent": "Agente de usuário",
-  "page.sessions.table.actions": "Ações",
-  "page.sessions.table.current_session": "Sessão Atual",
-  "page.api_keys.title": "Chaves de API",
-  "page.api_keys.table.description": "Descrição",
-  "page.api_keys.table.token": "Token",
-  "page.api_keys.table.last_used_at": "Ultima utilização",
-  "page.api_keys.table.created_at": "Data de criação",
-  "page.api_keys.table.actions": "Ações",
-  "page.api_keys.never_used": "Nunca usado",
-  "page.new_api_key.title": "Nova chave de API",
-  "alert.no_shared_entry": "Não há itens compartilhados.",
-  "alert.no_bookmark": "Não há favorito neste momento.",
-  "alert.no_category": "Não há categoria.",
-  "alert.no_category_entry": "Não há itens nesta categoria.",
-  "alert.no_feed_entry": "Não há itens nessa fonte.",
-  "alert.no_feed": "Não há inscrições.",
-  "alert.no_feed_in_category": "Não há inscrições nessa categoria.",
-  "alert.no_history": "Não há histórico nesse momento.",
-  "alert.feed_error": "Ocorreu um problema com esta fonte.",
-  "alert.no_search_result": "Não há resultados para essa busca.",
-  "alert.no_unread_entry": "Não há itens não lidos.",
-  "alert.no_user": "Você é o único usuário.",
-  "alert.account_unlinked": "Sua conta externa está desvinculada!",
-  "alert.account_linked": "Sua conta externa está vinculada!",
-  "alert.pocket_linked": "Sua conta do Pocket está vinculada!",
-  "alert.prefs_saved": "Suas preferências foram salvas!",
-  "error.unlink_account_without_password": "Você deve definir uma senha, senão não será possível efetuar a sessão novamente.",
-  "error.duplicate_linked_account": "Alguém já está vinculado a esse serviço!",
-  "error.duplicate_fever_username": "Alguém já está utilizando esse nome de usuário do Fever!",
-  "error.pocket_request_token": "Não foi possível obter um pedido de token no Pocket!",
-  "error.pocket_access_token": "Não foi possível obter um token de acesso no Pocket!",
-  "error.category_already_exists": "Esta categoria já existe.",
-  "error.unable_to_create_category": "Não foi possível criar essa categoria.",
-  "error.unable_to_update_category": "Não foi possível atualizar essa categoria.",
-  "error.user_already_exists": "Esse usuário já existe.",
-  "error.unable_to_create_user": "Não foi possível criar esse usuário.",
-  "error.unable_to_update_user": "Não foi possível atualizar esse usuário.",
-  "error.unable_to_update_feed": "Não foi possível atualizar essa fonte.",
-  "error.subscription_not_found": "Não foi possível encontrar uma inscrição.",
-  "error.empty_file": "Esse arquivo está vazio.",
-  "error.bad_credentials": "Usuário ou senha são inválidos.",
-  "error.fields_mandatory": "Todos os campos são obrigatórios.",
-  "error.title_required": "O título é obrigatório.",
-  "error.different_passwords": "As senhas não são iguais.",
-  "error.password_min_length": "A senha deve ter no mínimo 6 caracteres.",
-  "error.settings_mandatory_fields": "Os campos de nome de usuário, tema, idioma e fuso horário são obrigatórios.",
-  "error.entries_per_page_invalid": "O número de itens por página é inválido.",
-  "error.feed_mandatory_fields": "O campo de URL e categoria são obrigatórios.",
-  "error.user_mandatory_fields": "O nome de usuário é obrigatório.",
-  "error.api_key_already_exists": "Essa chave de API já existe.",
-  "error.unable_to_create_api_key": "Não foi possível criar uma chave de API.",
-  "form.feed.label.title": "Título",
-  "form.feed.label.site_url": "URL do site",
-  "form.feed.label.feed_url": "URL da fonte",
-  "form.feed.label.category": "Categoria",
-  "form.feed.label.crawler": "Obter conteúdo original",
-  "form.feed.label.feed_username": "Nome de usuário da fonte",
-  "form.feed.label.feed_password": "Senha da fonte",
-  "form.feed.label.user_agent": "Sobrescrever o agente de usuário (user-agent) padrão",
-  "form.feed.label.scraper_rules": "Regras do scraper",
-  "form.feed.label.rewrite_rules": "Regras para o Rewrite",
-  "form.feed.label.ignore_http_cache": "Ignorar cache HTTP",
-  "form.feed.label.disabled": "Não atualizar esta fonte",
-  "form.category.label.title": "Título",
-  "form.user.label.username": "Nome de usuário",
-  "form.user.label.password": "Senha",
-  "form.user.label.confirmation": "Confirmação de senha",
-  "form.user.label.admin": "Administrador",
-  "form.prefs.label.language": "Idioma",
-  "form.prefs.label.timezone": "Fuso horário",
-  "form.prefs.label.theme": "Tema",
-  "form.prefs.label.entry_sorting": "Ordenação dos itens",
-  "form.prefs.label.entries_per_page": "Itens por página",
-  "form.prefs.select.older_first": "Itens mais velhos primeiro",
-  "form.prefs.select.recent_first": "Itens mais recentes",
-  "form.prefs.label.keyboard_shortcuts": "Habilitar atalhos do teclado",
-  "form.prefs.label.custom_css": "CSS customizado",
-  "form.import.label.file": "Arquivo OPML",
-  "form.import.label.url": "URL",
-  "form.integration.fever_activate": "Ativar API do Fever",
-  "form.integration.fever_username": "Nome de usuário do Fever",
-  "form.integration.fever_password": "Senha do Fever",
-  "form.integration.fever_endpoint": "Endpoint da API do Fever:",
-  "form.integration.pinboard_activate": "Salvar itens no Pinboard",
-  "form.integration.pinboard_token": "Token de API do Pinboard",
-  "form.integration.pinboard_tags": "Etiquetas (tags) do Pinboard",
-  "form.integration.pinboard_bookmark": "Salvar marcador como não lído",
-  "form.integration.instapaper_activate": "Salvar itens no Instapaper",
-  "form.integration.instapaper_username": "Nome do usuário do Instapaper",
-  "form.integration.instapaper_password": "Senha do Instapaper",
-  "form.integration.pocket_activate": "Salvar itens no Pocket",
-  "form.integration.pocket_consumer_key": "Chave de consumo (Consumer Key) do Pocket",
-  "form.integration.pocket_access_token": "Token de acesso do Pocket",
-  "form.integration.pocket_connect_link": "Conectar a conta do Pocket",
-  "form.integration.wallabag_activate": "Salvar itens no Wallabag",
-  "form.integration.wallabag_endpoint": "Endpoint da API do Wallabag",
-  "form.integration.wallabag_client_id": "ID de cliente (Client ID) do Wallabag",
-  "form.integration.wallabag_client_secret": "Segredo do cliente (Client Secret) do Wallabag",
-  "form.integration.wallabag_username": "Nome de usuário do Wallabag",
-  "form.integration.wallabag_password": "Senha do Wallabag",
-  "form.integration.nunux_keeper_activate": "Salvar itens no Nunux Keeper",
-  "form.integration.nunux_keeper_endpoint": "Endpoint de API do Nunux Keeper",
-  "form.integration.nunux_keeper_api_key": "Chave de API do Nunux Keeper",
-  "form.api_key.label.description": "Etiqueta da chave de API",
-  "form.submit.loading": "Carregando...",
-  "form.submit.saving": "Salvando...",
-  "time_elapsed.not_yet": "ainda não",
-  "time_elapsed.yesterday": "ontem",
-  "time_elapsed.now": "agora mesmo",
-  "time_elapsed.minutes": [
-      "há %d minuto",
-      "há %d minutos"
-  ],
-  "time_elapsed.hours": [
-      "há %d hora",
-      "há %d horas"
-  ],
-  "time_elapsed.days": [
-      "há %d dia",
-      "há %d dias"
-  ],
-  "time_elapsed.weeks": [
-      "há %d semana",
-      "há %d semanas"
-  ],
-  "time_elapsed.months": [
-      "há %d mês",
-      "há %d meses"
-  ],
-  "time_elapsed.years": [
-      "há %d ano",
-      "há %d anos"
-  ]
+    "confirm.question": "Tem certeza?",
+    "confirm.yes": "Sim",
+    "confirm.no": "Não",
+    "confirm.loading": "Carregando...",
+    "action.subscribe": "Inscrever",
+    "action.save": "Salvar",
+    "action.or": "Ou",
+    "action.cancel": "Cancelar",
+    "action.remove": "Remover",
+    "action.remove_feed": "Remover fonte",
+    "action.update": "Atualizar",
+    "action.edit": "Editar",
+    "action.download": "Baixar",
+    "action.import": "Importar",
+    "action.login": "Iniciar sessão",
+    "action.home_screen": "Voltar para a tela inicial",
+    "tooltip.keyboard_shortcuts": "Atalho do teclado: %s",
+    "tooltip.logged_user": "Autenticado como %s",
+    "menu.unread": "Não lido",
+    "menu.starred": "Favoritos",
+    "menu.history": "Histórico",
+    "menu.feeds": "Fontes",
+    "menu.categories": "Categorias",
+    "menu.settings": "Configurações",
+    "menu.logout": "Encerrar sessão",
+    "menu.preferences": "Preferências",
+    "menu.integrations": "Integrações",
+    "menu.sessions": "Sessões",
+    "menu.users": "Usuários",
+    "menu.about": "Sobre",
+    "menu.export": "Exportar",
+    "menu.import": "Importar",
+    "menu.create_category": "Criar uma categoria",
+    "menu.mark_page_as_read": "Marcar essa página como lída",
+    "menu.mark_all_as_read": "Marcar todos como lido",
+    "menu.show_all_entries": "Mostrar todas os itens",
+    "menu.show_only_unread_entries": "Mostrar apenas itens não lidos",
+    "menu.refresh_feed": "Atualizar",
+    "menu.refresh_all_feeds": "Atualizar todas as fontes",
+    "menu.edit_feed": "Editar",
+    "menu.edit_category": "Editar",
+    "menu.add_feed": "Adicionar inscrição",
+    "menu.add_user": "Adicionar usuário",
+    "menu.flush_history": "Limpar histórico",
+    "menu.feed_entries": "Itens",
+    "menu.api_keys": "Chaves de API",
+    "menu.create_api_key": "Criar uma nova chave de API",
+    "menu.shared_entries": "Itens compartilhados",
+    "search.label": "Buscar",
+    "search.placeholder": "Buscar por...",
+    "pagination.next": "Próximo",
+    "pagination.previous": "Anterior",
+    "entry.status.unread": "Não lido",
+    "entry.status.read": "Lido",
+    "entry.status.toast.unread": "Marcado como não lido",
+    "entry.status.toast.read": "Marcado como lido",
+    "entry.status.title": "Modificar estado deste item",
+    "entry.bookmark.toggle.on": "Marcar",
+    "entry.bookmark.toggle.off": "Desmarcar",
+    "entry.bookmark.toast.on": "Favoritado",
+    "entry.bookmark.toast.off": "Desfavoritado",
+    "entry.state.saving": "Salvando...",
+    "entry.state.loading": "Carregando...",
+    "entry.save.label": "Salvar",
+    "entry.save.title": "Salvar esse item",
+    "entry.save.completed": "Feito!",
+    "entry.save.toast.completed": "Item guardado",
+    "entry.scraper.label": "Conteúdo completo",
+    "entry.scraper.title": "Obter conteúdo completo",
+    "entry.scraper.completed": "Feito!",
+    "entry.original.label": "Original",
+    "entry.comments.label": "Comentários",
+    "entry.comments.title": "Ver comentários",
+    "entry.share.label": "Compartilhar",
+    "entry.share.title": "Compartilhar esse item",
+    "entry.unshare.label": "Descompartilhar",
+    "entry.shared_entry.title": "Abrir link público",
+    "entry.shared_entry.label": "Compartilhar",
+    "entry.estimated_reading_time": [
+        "%d minuto lido",
+        "%d minutos lidos"
+    ],
+    "page.shared_entries.title": "Itens compartilhados",
+    "page.unread.title": "Não lídos",
+    "page.starred.title": "Favoritos",
+    "page.categories.title": "Categorias",
+    "page.categories.no_feed": "Sem fonte.",
+    "page.categories.entries": "Itens",
+    "page.categories.feeds": "Inscrições",
+    "page.categories.feed_count": [
+        "Existe %d fonte.",
+        "Existem %d fontes."
+    ],
+    "page.new_category.title": "Nova categoria",
+    "page.new_user.title": "Novo usuário",
+    "page.edit_category.title": "Editar categoria: %s",
+    "page.edit_user.title": "Editar usuário: %s",
+    "page.feeds.title": "Fontes",
+    "page.feeds.last_check": "Última verificação:",
+    "page.feeds.unread_counter": "Numero de itens não lidos",
+    "page.feeds.read_counter": "Número de itens lidos",
+    "page.feeds.error_count": [
+        "%d erro",
+        "%d erros"
+    ],
+    "page.history.title": "Histórico",
+    "page.import.title": "Importar",
+    "page.search.title": "Resultados da busca",
+    "page.about.title": "Sobre",
+    "page.about.credits": "Créditos",
+    "page.about.version": "Versão:",
+    "page.about.build_date": "Compilado em:",
+    "page.about.author": "Autor:",
+    "page.about.license": "Licença:",
+    "page.add_feed.title": "Nova inscrição",
+    "page.add_feed.no_category": "Não existe uma categoria. Deve existir pelo menos uma categoria.",
+    "page.add_feed.label.url": "URL",
+    "page.add_feed.submit": "Buscar uma fonte",
+    "page.add_feed.legend.advanced_options": "Opções avançadas",
+    "page.add_feed.choose_feed": "Escolher uma fonte",
+    "page.edit_feed.title": "Editar fonte: %s",
+    "page.edit_feed.last_check": "Última verificação:",
+    "page.edit_feed.last_modified_header": "Cabeçalho 'LastModified':",
+    "page.edit_feed.etag_header": "Cabeçalho 'ETag':",
+    "page.edit_feed.no_header": "Sem cabeçalhos",
+    "page.edit_feed.last_parsing_error": "Último erro durante processamento",
+    "page.entry.attachments": "Anexos",
+    "page.keyboard_shortcuts.title": "Atalhos de teclado",
+    "page.keyboard_shortcuts.subtitle.sections": "Navegação de seções",
+    "page.keyboard_shortcuts.subtitle.items": "Navegação de itens",
+    "page.keyboard_shortcuts.subtitle.pages": "Navegação de páginas",
+    "page.keyboard_shortcuts.subtitle.actions": "Ações",
+    "page.keyboard_shortcuts.go_to_unread": "Ir aos não lidos",
+    "page.keyboard_shortcuts.go_to_starred": "Ir aos favoritos",
+    "page.keyboard_shortcuts.go_to_history": "Ir ao histórico",
+    "page.keyboard_shortcuts.go_to_feeds": "Ir as inscrições",
+    "page.keyboard_shortcuts.go_to_categories": "Ir as categorias",
+    "page.keyboard_shortcuts.go_to_settings": "Ir as configurações",
+    "page.keyboard_shortcuts.show_keyboard_shortcuts": "Mostrar atalhos de teclado",
+    "page.keyboard_shortcuts.go_to_previous_item": "Ir ao item anterior",
+    "page.keyboard_shortcuts.go_to_next_item": "Ir ao tem seguinte",
+    "page.keyboard_shortcuts.go_to_feed": "Ir a fonte",
+    "page.keyboard_shortcuts.go_to_previous_page": "Ir a página anterior",
+    "page.keyboard_shortcuts.go_to_next_page": "Ir a página seguinte",
+    "page.keyboard_shortcuts.open_item": "Abrir o item selecionado",
+    "page.keyboard_shortcuts.open_original": "Abrir o conteúdo original",
+    "page.keyboard_shortcuts.open_original_same_window": "Abrir o conteúdo original na janela atual",
+    "page.keyboard_shortcuts.open_comments": "Abrir os comentários",
+    "page.keyboard_shortcuts.open_comments_same_window": "Abrir os comentários na janela atual",
+    "page.keyboard_shortcuts.toggle_read_status": "Inverter estado de leitura do item",
+    "page.keyboard_shortcuts.refresh_all_feeds": "Atualizar todas as fontes",
+    "page.keyboard_shortcuts.mark_page_as_read": "Marcar página atual como lida",
+    "page.keyboard_shortcuts.download_content": "Buscar o conteúdo original",
+    "page.keyboard_shortcuts.toggle_bookmark_status": "Marcar ou desmarcar como favorito",
+    "page.keyboard_shortcuts.save_article": "Salvar item",
+    "page.keyboard_shortcuts.remove_feed": "Remover essa fonte",
+    "page.keyboard_shortcuts.go_to_search": "Ir para o campo de busca",
+    "page.keyboard_shortcuts.close_modal": "Fechar janela",
+    "page.users.title": "Usuários",
+    "page.users.username": "Nome de usuário",
+    "page.users.never_logged": "Nunca",
+    "page.users.admin.yes": "Sim",
+    "page.users.admin.no": "Não",
+    "page.users.actions": "Ações",
+    "page.users.last_login": "Último acesso",
+    "page.users.is_admin": "Administrador",
+    "page.settings.title": "Ajustes",
+    "page.settings.link_google_account": "Vincular minha conta do Google",
+    "page.settings.unlink_google_account": "Desvincular minha conta do Google",
+    "page.settings.link_oidc_account": "Vincular minha conta do OpenID Connect",
+    "page.settings.unlink_oidc_account": "Desvincular minha conta do OpenID Connect",
+    "page.login.title": "Iniciar Sessão",
+    "page.login.google_signin": "Iniciar Sessão com sua conta do Google",
+    "page.login.oidc_signin": "Iniciar Sessão com sua conta do OpenID Connect",
+    "page.integrations.title": "Integrações",
+    "page.integration.miniflux_api": "API do Miniflux",
+    "page.integration.miniflux_api_endpoint": "Endpoint da API",
+    "page.integration.miniflux_api_username": "Nome de usuário",
+    "page.integration.miniflux_api_password": "Senha",
+    "page.integration.miniflux_api_password_value": "Senha da sua Conta",
+    "page.integration.bookmarklet": "Bookmarklet",
+    "page.integration.bookmarklet.name": "Adicionar ao Miniflux",
+    "page.integration.bookmarklet.instructions": "Arrasta e solta esse link para os favoritos do teu navegador.",
+    "page.integration.bookmarklet.help": "Esse link especial permite você se inscrever a um site diretamente usando favorito do navegador.",
+    "page.sessions.title": "Sessões",
+    "page.sessions.table.date": "Data",
+    "page.sessions.table.ip": "Endereço IP",
+    "page.sessions.table.user_agent": "Agente de usuário",
+    "page.sessions.table.actions": "Ações",
+    "page.sessions.table.current_session": "Sessão Atual",
+    "page.api_keys.title": "Chaves de API",
+    "page.api_keys.table.description": "Descrição",
+    "page.api_keys.table.token": "Token",
+    "page.api_keys.table.last_used_at": "Ultima utilização",
+    "page.api_keys.table.created_at": "Data de criação",
+    "page.api_keys.table.actions": "Ações",
+    "page.api_keys.never_used": "Nunca usado",
+    "page.new_api_key.title": "Nova chave de API",
+    "alert.no_shared_entry": "Não há itens compartilhados.",
+    "alert.no_bookmark": "Não há favorito neste momento.",
+    "alert.no_category": "Não há categoria.",
+    "alert.no_category_entry": "Não há itens nesta categoria.",
+    "alert.no_feed_entry": "Não há itens nessa fonte.",
+    "alert.no_feed": "Não há inscrições.",
+    "alert.no_feed_in_category": "Não há inscrições nessa categoria.",
+    "alert.no_history": "Não há histórico nesse momento.",
+    "alert.feed_error": "Ocorreu um problema com esta fonte.",
+    "alert.no_search_result": "Não há resultados para essa busca.",
+    "alert.no_unread_entry": "Não há itens não lidos.",
+    "alert.no_user": "Você é o único usuário.",
+    "alert.account_unlinked": "Sua conta externa está desvinculada!",
+    "alert.account_linked": "Sua conta externa está vinculada!",
+    "alert.pocket_linked": "Sua conta do Pocket está vinculada!",
+    "alert.prefs_saved": "Suas preferências foram salvas!",
+    "error.unlink_account_without_password": "Você deve definir uma senha, senão não será possível efetuar a sessão novamente.",
+    "error.duplicate_linked_account": "Alguém já está vinculado a esse serviço!",
+    "error.duplicate_fever_username": "Alguém já está utilizando esse nome de usuário do Fever!",
+    "error.pocket_request_token": "Não foi possível obter um pedido de token no Pocket!",
+    "error.pocket_access_token": "Não foi possível obter um token de acesso no Pocket!",
+    "error.category_already_exists": "Esta categoria já existe.",
+    "error.unable_to_create_category": "Não foi possível criar essa categoria.",
+    "error.unable_to_update_category": "Não foi possível atualizar essa categoria.",
+    "error.user_already_exists": "Esse usuário já existe.",
+    "error.unable_to_create_user": "Não foi possível criar esse usuário.",
+    "error.unable_to_update_user": "Não foi possível atualizar esse usuário.",
+    "error.unable_to_update_feed": "Não foi possível atualizar essa fonte.",
+    "error.subscription_not_found": "Não foi possível encontrar uma inscrição.",
+    "error.empty_file": "Esse arquivo está vazio.",
+    "error.bad_credentials": "Usuário ou senha são inválidos.",
+    "error.fields_mandatory": "Todos os campos são obrigatórios.",
+    "error.title_required": "O título é obrigatório.",
+    "error.different_passwords": "As senhas não são iguais.",
+    "error.password_min_length": "A senha deve ter no mínimo 6 caracteres.",
+    "error.settings_mandatory_fields": "Os campos de nome de usuário, tema, idioma e fuso horário são obrigatórios.",
+    "error.entries_per_page_invalid": "O número de itens por página é inválido.",
+    "error.feed_mandatory_fields": "O campo de URL e categoria são obrigatórios.",
+    "error.user_mandatory_fields": "O nome de usuário é obrigatório.",
+    "error.api_key_already_exists": "Essa chave de API já existe.",
+    "error.unable_to_create_api_key": "Não foi possível criar uma chave de API.",
+    "form.feed.label.title": "Título",
+    "form.feed.label.site_url": "URL do site",
+    "form.feed.label.feed_url": "URL da fonte",
+    "form.feed.label.category": "Categoria",
+    "form.feed.label.crawler": "Obter conteúdo original",
+    "form.feed.label.feed_username": "Nome de usuário da fonte",
+    "form.feed.label.feed_password": "Senha da fonte",
+    "form.feed.label.user_agent": "Sobrescrever o agente de usuário (user-agent) padrão",
+    "form.feed.label.scraper_rules": "Regras do scraper",
+    "form.feed.label.rewrite_rules": "Regras para o Rewrite",
+    "form.feed.label.ignore_http_cache": "Ignorar cache HTTP",
+    "form.feed.label.disabled": "Não atualizar esta fonte",
+    "form.category.label.title": "Título",
+    "form.user.label.username": "Nome de usuário",
+    "form.user.label.password": "Senha",
+    "form.user.label.confirmation": "Confirmação de senha",
+    "form.user.label.admin": "Administrador",
+    "form.prefs.label.language": "Idioma",
+    "form.prefs.label.timezone": "Fuso horário",
+    "form.prefs.label.theme": "Tema",
+    "form.prefs.label.entry_sorting": "Ordenação dos itens",
+    "form.prefs.label.entries_per_page": "Itens por página",
+    "form.prefs.select.older_first": "Itens mais velhos primeiro",
+    "form.prefs.select.recent_first": "Itens mais recentes",
+    "form.prefs.label.keyboard_shortcuts": "Habilitar atalhos do teclado",
+    "form.prefs.label.show_reading_time": "Mostrar tempo estimado de leitura de artigos",
+    "form.prefs.label.custom_css": "CSS customizado",
+    "form.import.label.file": "Arquivo OPML",
+    "form.import.label.url": "URL",
+    "form.integration.fever_activate": "Ativar API do Fever",
+    "form.integration.fever_username": "Nome de usuário do Fever",
+    "form.integration.fever_password": "Senha do Fever",
+    "form.integration.fever_endpoint": "Endpoint da API do Fever:",
+    "form.integration.pinboard_activate": "Salvar itens no Pinboard",
+    "form.integration.pinboard_token": "Token de API do Pinboard",
+    "form.integration.pinboard_tags": "Etiquetas (tags) do Pinboard",
+    "form.integration.pinboard_bookmark": "Salvar marcador como não lído",
+    "form.integration.instapaper_activate": "Salvar itens no Instapaper",
+    "form.integration.instapaper_username": "Nome do usuário do Instapaper",
+    "form.integration.instapaper_password": "Senha do Instapaper",
+    "form.integration.pocket_activate": "Salvar itens no Pocket",
+    "form.integration.pocket_consumer_key": "Chave de consumo (Consumer Key) do Pocket",
+    "form.integration.pocket_access_token": "Token de acesso do Pocket",
+    "form.integration.pocket_connect_link": "Conectar a conta do Pocket",
+    "form.integration.wallabag_activate": "Salvar itens no Wallabag",
+    "form.integration.wallabag_endpoint": "Endpoint da API do Wallabag",
+    "form.integration.wallabag_client_id": "ID de cliente (Client ID) do Wallabag",
+    "form.integration.wallabag_client_secret": "Segredo do cliente (Client Secret) do Wallabag",
+    "form.integration.wallabag_username": "Nome de usuário do Wallabag",
+    "form.integration.wallabag_password": "Senha do Wallabag",
+    "form.integration.nunux_keeper_activate": "Salvar itens no Nunux Keeper",
+    "form.integration.nunux_keeper_endpoint": "Endpoint de API do Nunux Keeper",
+    "form.integration.nunux_keeper_api_key": "Chave de API do Nunux Keeper",
+    "form.api_key.label.description": "Etiqueta da chave de API",
+    "form.submit.loading": "Carregando...",
+    "form.submit.saving": "Salvando...",
+    "time_elapsed.not_yet": "ainda não",
+    "time_elapsed.yesterday": "ontem",
+    "time_elapsed.now": "agora mesmo",
+    "time_elapsed.minutes": [
+        "há %d minuto",
+        "há %d minutos"
+    ],
+    "time_elapsed.hours": [
+        "há %d hora",
+        "há %d horas"
+    ],
+    "time_elapsed.days": [
+        "há %d dia",
+        "há %d dias"
+    ],
+    "time_elapsed.weeks": [
+        "há %d semana",
+        "há %d semanas"
+    ],
+    "time_elapsed.months": [
+        "há %d mês",
+        "há %d meses"
+    ],
+    "time_elapsed.years": [
+        "há %d ano",
+        "há %d anos"
+    ]
 }

+ 5 - 0
locale/translations/ru_RU.json

@@ -77,6 +77,10 @@
     "entry.unshare.label": "Удалить из общедоступных",
     "entry.shared_entry.title": "Открыть публичную ссылку",
     "entry.shared_entry.label": "Поделиться",
+    "entry.estimated_reading_time": [
+        "%d минута чтения",
+        "%d минут чтения"
+    ],
     "page.shared_entries.title": "Общедоступные записи",
     "page.unread.title": "Непрочитанное",
     "page.starred.title": "Избранное",
@@ -261,6 +265,7 @@
     "form.prefs.select.older_first": "Сначала старые записи",
     "form.prefs.select.recent_first": "Сначала последние записи",
     "form.prefs.label.keyboard_shortcuts": "Включить сочетания клавиш",
+    "form.prefs.label.show_reading_time": "Показать примерное время чтения статей",
     "form.prefs.label.custom_css": "Пользовательские CSS",
     "form.import.label.file": "OPML файл",
     "form.import.label.url": "URL",

+ 5 - 0
locale/translations/zh_CN.json

@@ -77,6 +77,10 @@
     "entry.unshare.label": "取消分享",
     "entry.shared_entry.title": "打开公共链接",
     "entry.shared_entry.label": "分享分享",
+    "entry.estimated_reading_time": [
+        "%d分钟阅读",
+        "%d分钟阅读"
+    ],
     "page.shared_entries.title": "共享条目",
     "page.unread.title": "未读",
     "page.starred.title": "星标",
@@ -257,6 +261,7 @@
     "form.prefs.select.older_first": "旧->新",
     "form.prefs.select.recent_first": "新->旧",
     "form.prefs.label.keyboard_shortcuts": "启用键盘快捷键",
+    "form.prefs.label.show_reading_time": "显示文章的预计阅读时间",
     "form.prefs.label.custom_css": "自定义CSS",
     "form.import.label.file": "OPML 文件",
     "form.import.label.url": "URL",

+ 1 - 0
model/user.go

@@ -23,6 +23,7 @@ type User struct {
 	EntryDirection    string            `json:"entry_sorting_direction"`
 	EntriesPerPage    int               `json:"entries_per_page"`
 	KeyboardShortcuts bool              `json:"keyboard_shortcuts"`
+	ShowReadingTime	  bool              `json:"show_reading_time"`
 	LastLoginAt       *time.Time        `json:"last_login_at,omitempty"`
 	Extra             map[string]string `json:"extra"`
 }

+ 17 - 5
storage/user.go

@@ -64,7 +64,7 @@ func (s *Storage) CreateUser(user *model.User) (err error) {
 		VALUES
 			(LOWER($1), $2, $3, $4)
 		RETURNING
-			id, username, is_admin, language, theme, timezone, entry_direction, entries_per_page, keyboard_shortcuts
+			id, username, is_admin, language, theme, timezone, entry_direction, entries_per_page, keyboard_shortcuts, show_reading_time
 	`
 
 	err = s.db.QueryRow(query, user.Username, password, user.IsAdmin, extra).Scan(
@@ -77,6 +77,7 @@ func (s *Storage) CreateUser(user *model.User) (err error) {
 		&user.EntryDirection,
 		&user.EntriesPerPage,
 		&user.KeyboardShortcuts,
+		&user.ShowReadingTime,
 	)
 	if err != nil {
 		return fmt.Errorf(`store: unable to create user: %v`, err)
@@ -125,9 +126,10 @@ func (s *Storage) UpdateUser(user *model.User) error {
 				timezone=$6,
 				entry_direction=$7,
 				entries_per_page=$8,
-				keyboard_shortcuts=$9
+				keyboard_shortcuts=$9,
+				show_reading_time=$10
 			WHERE
-				id=$10
+				id=$11
 		`
 
 		_, err = s.db.Exec(
@@ -141,6 +143,7 @@ func (s *Storage) UpdateUser(user *model.User) error {
 			user.EntryDirection,
 			user.EntriesPerPage,
 			user.KeyboardShortcuts,
+			user.ShowReadingTime,
 			user.ID,
 		)
 		if err != nil {
@@ -156,9 +159,10 @@ func (s *Storage) UpdateUser(user *model.User) error {
 				timezone=$5,
 				entry_direction=$6,
 				entries_per_page=$7,
-				keyboard_shortcuts=$8
+				keyboard_shortcuts=$8,
+				show_reading_time=$9
 			WHERE
-				id=$9
+				id=$10
 		`
 
 		_, err := s.db.Exec(
@@ -171,6 +175,7 @@ func (s *Storage) UpdateUser(user *model.User) error {
 			user.EntryDirection,
 			user.EntriesPerPage,
 			user.KeyboardShortcuts,
+			user.ShowReadingTime,
 			user.ID,
 		)
 
@@ -209,6 +214,7 @@ func (s *Storage) UserByID(userID int64) (*model.User, error) {
 			entry_direction,
 			entries_per_page,
 			keyboard_shortcuts,
+			show_reading_time,
 			last_login_at,
 			extra
 		FROM
@@ -232,6 +238,7 @@ func (s *Storage) UserByUsername(username string) (*model.User, error) {
 			entry_direction,
 			entries_per_page,
 			keyboard_shortcuts,
+			show_reading_time,
 			last_login_at,
 			extra
 		FROM
@@ -255,6 +262,7 @@ func (s *Storage) UserByExtraField(field, value string) (*model.User, error) {
 			entry_direction,
 			entries_per_page,
 			keyboard_shortcuts,
+			show_reading_time,
 			last_login_at,
 			extra
 		FROM
@@ -278,6 +286,7 @@ func (s *Storage) UserByAPIKey(token string) (*model.User, error) {
 			u.entry_direction,
 			u.entries_per_page,
 			u.keyboard_shortcuts,
+			u.show_reading_time,
 			u.last_login_at,
 			u.extra
 		FROM
@@ -304,6 +313,7 @@ func (s *Storage) fetchUser(query string, args ...interface{}) (*model.User, err
 		&user.EntryDirection,
 		&user.EntriesPerPage,
 		&user.KeyboardShortcuts,
+		&user.ShowReadingTime,
 		&user.LastLoginAt,
 		&extra,
 	)
@@ -360,6 +370,7 @@ func (s *Storage) Users() (model.Users, error) {
 			entry_direction,
 			entries_per_page,
 			keyboard_shortcuts,
+			show_reading_time,
 			last_login_at,
 			extra
 		FROM
@@ -386,6 +397,7 @@ func (s *Storage) Users() (model.Users, error) {
 			&user.EntryDirection,
 			&user.EntriesPerPage,
 			&user.KeyboardShortcuts,
+			&user.ShowReadingTime,
 			&user.LastLoginAt,
 			&extra,
 		)

+ 8 - 1
template/common.go

@@ -237,6 +237,13 @@ SOFTWARE.
         <li>
             <time datetime="{{ isodate .entry.Date }}" title="{{ isodate .entry.Date }}">{{ elapsed .user.Timezone .entry.Date }}</time>
         </li>
+        {{ if .user.ShowReadingTime }}
+        <li>
+            <span>
+            {{ plural "entry.estimated_reading_time" (timeToRead .entry.Content) (timeToRead .entry.Content) }}
+            </span>
+        </li>
+        {{ end }}
     </ul>
     <ul class="item-meta-icons">
         {{ if .entry.ShareCode }}
@@ -510,7 +517,7 @@ var templateCommonMapChecksums = map[string]string{
 	"feed_list":        "30acc9ecc413811e73a1dad120b5d44e29564de3ba794fb07ee886b30addfb19",
 	"feed_menu":        "318d8662dda5ca9dfc75b909c8461e79c86fb5082df1428f67aaf856f19f4b50",
 	"icons":            "3dbe754a98f524a227111191d76b8c6944711b13613cc548ee9e9808fe0bffb4",
-	"item_meta":        "a5b07cc6597e5c8f3ca849ee486acb3f16f062d8a1eaa47d2fb402ae6825b7ef",
+	"item_meta":        "a5b686c7af6348fdbd9a147e3fcfacc4a0b442c3b9637f3dce026ccf367fc312",
 	"layout":           "91d2ab3f683a2ced5e9ce5cd04919e74b3e3f329a5eedcc60015b8d49ecb1b77",
 	"pagination":       "7b61288e86283c4cf0dc83bcbf8bf1c00c7cb29e60201c8c0b633b2450d2911f",
 	"settings_menu":    "e2b777630c0efdbc529800303c01d6744ed3af80ec505ac5a5b3f99c9b989156",

+ 3 - 0
template/engine.go

@@ -65,6 +65,9 @@ func (e *Engine) Render(name, language string, data interface{}) []byte {
 		"plural": func(key string, n int, args ...interface{}) string {
 			return printer.Plural(key, n, args...)
 		},
+		"timeToRead": func(content string) int {
+			return timeToRead(content)
+		},
 	})
 
 	var b bytes.Buffer

+ 9 - 0
template/functions.go

@@ -92,6 +92,9 @@ func (f *funcMap) Map() template.FuncMap {
 		"plural": func(key string, n int, args ...interface{}) string {
 			return ""
 		},
+		"timeToRead": func(content string) int {
+			return 0
+		},
 	}
 }
 
@@ -218,3 +221,9 @@ func formatFileSize(b int64) string {
 	return fmt.Sprintf("%.1f %ciB",
 		float64(b)/float64(div), "KMGTPE"[exp])
 }
+
+func timeToRead(content string) int {
+	nbOfWords := len(strings.Fields(content))
+
+	return int(math.Ceil(float64(nbOfWords) / 265))
+}

+ 7 - 0
template/html/common/item_meta.html

@@ -7,6 +7,13 @@
         <li>
             <time datetime="{{ isodate .entry.Date }}" title="{{ isodate .entry.Date }}">{{ elapsed .user.Timezone .entry.Date }}</time>
         </li>
+        {{ if .user.ShowReadingTime }}
+        <li>
+            <span>
+            {{ plural "entry.estimated_reading_time" (timeToRead .entry.Content) (timeToRead .entry.Content) }}
+            </span>
+        </li>
+        {{ end }}
     </ul>
     <ul class="item-meta-icons">
         {{ if .entry.ShareCode }}

+ 2 - 0
template/html/settings.html

@@ -53,6 +53,8 @@
     <input type="number" name="entries_per_page" id="form-entries-per-page" value="{{ .form.EntriesPerPage }}" min="1">
 
     <label><input type="checkbox" name="keyboard_shortcuts" value="1" {{ if .form.KeyboardShortcuts }}checked{{ end }}> {{ t "form.prefs.label.keyboard_shortcuts" }}</label>
+    
+    <label><input type="checkbox" name="show_reading_time" value="1" {{ if .form.ShowReadingTime }}checked{{ end }}> {{ t "form.prefs.label.show_reading_time" }}</label>
 
     <label>{{t "form.prefs.label.custom_css" }}</label><textarea name="custom_css" cols="40" rows="5">{{ .form.CustomCSS }}</textarea>
     <div class="buttons">

+ 3 - 1
template/views.go

@@ -1324,6 +1324,8 @@ var templateViewsMap = map[string]string{
     <input type="number" name="entries_per_page" id="form-entries-per-page" value="{{ .form.EntriesPerPage }}" min="1">
 
     <label><input type="checkbox" name="keyboard_shortcuts" value="1" {{ if .form.KeyboardShortcuts }}checked{{ end }}> {{ t "form.prefs.label.keyboard_shortcuts" }}</label>
+    
+    <label><input type="checkbox" name="show_reading_time" value="1" {{ if .form.ShowReadingTime }}checked{{ end }}> {{ t "form.prefs.label.show_reading_time" }}</label>
 
     <label>{{t "form.prefs.label.custom_css" }}</label><textarea name="custom_css" cols="40" rows="5">{{ .form.CustomCSS }}</textarea>
     <div class="buttons">
@@ -1568,7 +1570,7 @@ var templateViewsMapChecksums = map[string]string{
 	"login":               "79ff2ca488c0a19b37c8fa227a21f73e94472eb357a51a077197c852f7713f11",
 	"search_entries":      "c0786ddc6b17e865007b975eefb97417935cbc601f5917cca1ee0d3f584594bc",
 	"sessions":            "5d5c677bddbd027e0b0c9f7a0dd95b66d9d95b4e130959f31fb955b926c2201c",
-	"settings":            "3d6dd0d7fa0ca48cfd9a5edb43c055af8b816eb4460f16b71ae22db40ed9b754",
+	"settings":            "a4d3df17e6abc75881ec1ca5f92a9c2cfe24e3e08e5d844df848b4d6aaa1bbc0",
 	"shared_entries":      "1494d81e46f6af534a73cf6a91f8dfda1932a477bb3a70143513896ac0f0220b",
 	"unread_entries":      "e0080d0cf3583cda51d865422960137c8556c432853657086e43daf6bd5b73be",
 	"users":               "d7ff52efc582bbad10504f4a04fa3adcc12d15890e45dff51cac281e0c446e45",

+ 3 - 0
ui/form/settings.go

@@ -23,6 +23,7 @@ type SettingsForm struct {
 	EntryDirection    string
 	EntriesPerPage    int
 	KeyboardShortcuts bool
+	ShowReadingTime   bool
 	CustomCSS         string
 }
 
@@ -35,6 +36,7 @@ func (s *SettingsForm) Merge(user *model.User) *model.User {
 	user.EntryDirection = s.EntryDirection
 	user.EntriesPerPage = s.EntriesPerPage
 	user.KeyboardShortcuts = s.KeyboardShortcuts
+	user.ShowReadingTime = s.ShowReadingTime
 	user.Extra["custom_css"] = s.CustomCSS
 
 	if s.Password != "" {
@@ -88,6 +90,7 @@ func NewSettingsForm(r *http.Request) *SettingsForm {
 		EntryDirection:    r.FormValue("entry_direction"),
 		EntriesPerPage:    int(entriesPerPage),
 		KeyboardShortcuts: r.FormValue("keyboard_shortcuts") == "1",
+		ShowReadingTime:   r.FormValue("show_reading_time") == "1",
 		CustomCSS:         r.FormValue("custom_css"),
 	}
 }

+ 1 - 0
ui/settings_show.go

@@ -34,6 +34,7 @@ func (h *handler) showSettingsPage(w http.ResponseWriter, r *http.Request) {
 		EntryDirection:    user.EntryDirection,
 		EntriesPerPage:    user.EntriesPerPage,
 		KeyboardShortcuts: user.KeyboardShortcuts,
+		ShowReadingTime:   user.ShowReadingTime,
 		CustomCSS:         user.Extra["custom_css"],
 	}