4 Commits

Author SHA1 Message Date
nkanaev
76c2b9a475 add russian 2026-05-01 23:48:27 +01:00
nkanaev
14d5a6b52b ui tweaks / fixes 2026-05-01 23:47:17 +01:00
nkanaev
6069330e92 i18n in UI 2026-05-01 23:35:14 +01:00
nkanaev
552ebb7ad5 i18n class 2026-05-01 22:46:52 +01:00
4 changed files with 463 additions and 65 deletions

View File

@@ -27,43 +27,43 @@
<button class="toolbar-item ml-1"
:class="{active: filterSelected == 'unread'}"
:aria-pressed="filterSelected == 'unread'"
title="Unread"
:title="$t('unread')"
@click="filterSelected = 'unread'">
<span class="icon">{% inline "circle-full.svg" %}</span>
</button>
<button class="toolbar-item mx-1"
:class="{active: filterSelected == 'starred'}"
:aria-pressed="filterSelected == 'starred'"
title="Starred"
:title="$t('starred')"
@click="filterSelected = 'starred'">
<span class="icon">{% inline "star-full.svg" %}</span>
</button>
<button class="toolbar-item mr-1"
:class="{active: filterSelected == ''}"
:aria-pressed="filterSelected == ''"
title="All"
:title="$t('all')"
@click="filterSelected = ''">
<span class="icon">{% inline "assorted.svg" %}</span>
</button>
<div class="flex-grow-1"></div>
<dropdown class="settings-dropdown" toggle-class="btn btn-link toolbar-item px-2" ref="menuDropdown" drop="right" title="Settings">
<dropdown class="settings-dropdown" toggle-class="btn btn-link toolbar-item px-2" ref="menuDropdown" drop="right" :title="$t('settings')">
<template v-slot:button>
<span class="icon">{% inline "more-horizontal.svg" %}</span>
</template>
<button class="dropdown-item" @click="showSettings('create')">
<span class="icon mr-1">{% inline "plus.svg" %}</span>
New Feed
{{ $t('new_feed') }}
</button>
<div class="dropdown-divider"></div>
<button class="dropdown-item" @click="fetchAllFeeds()">
<span class="icon mr-1">{% inline "rotate-cw.svg" %}</span>
Refresh Feeds
{{ $t('refresh_feeds') }}
</button>
<div class="dropdown-divider"></div>
<header class="dropdown-header" role="heading" aria-level="2">Theme</header>
<header class="dropdown-header" role="heading" aria-level="2">{{ $t('theme') }}</header>
<div class="row text-center m-0">
<button class="btn btn-link col-4 px-0 rounded-0"
:class="'theme-'+t"
@@ -77,7 +77,7 @@
<div class="dropdown-divider"></div>
<header class="dropdown-header" role="heading" aria-level="2">Auto Refresh</header>
<header class="dropdown-header" role="heading" aria-level="2">{{ $t('auto_refresh') }}</header>
<div class="row text-center m-0">
<button class="dropdown-item col-4 px-0"
@click.stop="changeRefreshRate(-1)"
@@ -97,13 +97,13 @@
<div class="dropdown-divider"></div>
<header class="dropdown-header" role="heading" aria-level="2">Show first</header>
<header class="dropdown-header" role="heading" aria-level="2">{{ $t('show_first') }}</header>
<div class="d-flex text-center">
<button class="dropdown-item px-0" :aria-pressed="itemSortNewestFirst" :class="{active: itemSortNewestFirst}" @click.stop="itemSortNewestFirst=true">New</button>
<button class="dropdown-item px-0" :aria-pressed="!itemSortNewestFirst" :class="{active: !itemSortNewestFirst}" @click.stop="itemSortNewestFirst=false">Old</button>
<button class="dropdown-item px-0" :aria-pressed="itemSortNewestFirst" :class="{active: itemSortNewestFirst}" @click.stop="itemSortNewestFirst=true">{{ $t('new') }}</button>
<button class="dropdown-item px-0" :aria-pressed="!itemSortNewestFirst" :class="{active: !itemSortNewestFirst}" @click.stop="itemSortNewestFirst=false">{{ $t('old') }}</button>
</div>
<div class="dropdown-divider"></div>
<header class="dropdown-header" role="heading" aria-level="2">Subscriptions</header>
<header class="dropdown-header" role="heading" aria-level="2">{{ $t('subscriptions') }}</header>
<form id="opml-import-form" enctype="multipart/form-data" tabindex="-1">
<input type="file"
id="opml-import"
@@ -112,22 +112,31 @@
style="opacity: 0; width: 1px; height: 0; position: absolute; z-index: -1;">
<label class="dropdown-item mb-0 cursor-pointer" for="opml-import" @click.stop="">
<span class="icon mr-1">{% inline "download.svg" %}</span>
Import
{{ $t('import') }}
</label>
</form>
<a class="dropdown-item" href="./opml/export">
<span class="icon mr-1">{% inline "upload.svg" %}</span>
Export
{{ $t('export') }}
</a>
<div class="dropdown-divider"></div>
<button class="dropdown-item" @click="showSettings('shortcuts')">
<span class="icon mr-1">{% inline "help-circle.svg" %}</span>
Shortcuts
{{ $t('shortcuts') }}
</button>
<div class="dropdown-divider"></div>
<header class="dropdown-header" role="heading" aria-level="2">ᚨ / 𐎠 / 𑖀</header>
<button
v-for="lang in languages"
class="dropdown-item"
:class="{active: language==lang.code}"
@click.stop="changeLanguage(lang.code)">
{{ lang.name }}
</button>
<div class="dropdown-divider" v-if="authenticated"></div>
<button class="dropdown-item" v-if="authenticated" @click="logout()">
<span class="icon mr-1">{% inline "log-out.svg" %}</span>
Log out
{{ $t('log_out') }}
</button>
</dropdown>
</div>
@@ -136,9 +145,9 @@
<input type="radio" name="feed" value="" v-model="feedSelected">
<div class="selectgroup-label d-flex align-items-center w-100">
<span class="icon mr-2">{% inline "layers.svg" %}</span>
<span class="flex-fill text-left text-truncate" v-if="filterSelected=='unread'">All Unread</span>
<span class="flex-fill text-left text-truncate" v-if="filterSelected=='starred'">All Starred</span>
<span class="flex-fill text-left text-truncate" v-if="filterSelected==''">All Feeds</span>
<span class="flex-fill text-left text-truncate" v-if="filterSelected=='unread'">{{ $t('all_unread') }}</span>
<span class="flex-fill text-left text-truncate" v-if="filterSelected=='starred'">{{ $t('all_starred') }}</span>
<span class="flex-fill text-left text-truncate" v-if="filterSelected==''">{{ $t('all_feeds') }}</span>
<span class="counter text-right">{{ filteredTotalStats }}</span>
</div>
</label>
@@ -179,7 +188,7 @@
</div>
<div class="p-2 toolbar d-flex align-items-center border-top flex-shrink-0" v-if="loading.feeds">
<span class="icon loading mx-2"></span>
<span class="text-truncate cursor-default noselect">Refreshing ({{ loading.feeds }} left)</span>
<span class="text-truncate cursor-default noselect">{{ $t('refreshing') }} ({{ loading.feeds }} {{ $t('left') }})</span>
</div>
</div>
<!-- item list -->
@@ -188,7 +197,7 @@
<div class="px-2 toolbar d-flex align-items-center">
<button class="toolbar-item mr-2 d-block d-md-none"
@click="feedSelected = null"
title="Show Feeds">
:title="$t('show_feeds')">
<span class="icon">{% inline "chevron-left.svg" %}</span>
</button>
<div class="input-icon flex-grow-1">
@@ -199,7 +208,7 @@
<button class="toolbar-item ml-2"
@click="markItemsRead()"
v-if="filterSelected == 'unread'"
title="Mark All Read">
:title="$t('mark_all_read')">
<span class="icon">{% inline "check.svg" %}</span>
</button>
@@ -210,7 +219,7 @@
<dropdown class="settings-dropdown"
toggle-class="btn btn-link toolbar-item px-2 ml-2"
drop="right"
title="Feed Settings"
:title="$t('feed_settings')"
v-if="current.type == 'feed'">
<template v-slot:button>
<span class="icon">{% inline "more-horizontal.svg" %}</span>
@@ -218,23 +227,23 @@
<header class="dropdown-header" role="heading" aria-level="2">{{ current.feed.title }}</header>
<a class="dropdown-item" :href="current.feed.link" rel="noopener noreferrer" target="_blank" referrerpolicy="no-referrer" v-if="current.feed.link">
<span class="icon mr-1">{% inline "globe.svg" %}</span>
Website
{{ $t('website') }}
</a>
<a class="dropdown-item" :href="current.feed.feed_link" rel="noopener noreferrer" target="_blank" referrerpolicy="no-referrer" v-if="current.feed.feed_link">
<span class="icon mr-1">{% inline "rss.svg" %}</span>
Feed Link
{{ $t('feed_link') }}
</a>
<div class="dropdown-divider" v-if="current.feed.link || current.feed.feed_link"></div>
<button class="dropdown-item" @click="renameFeed(current.feed)">
<span class="icon mr-1">{% inline "edit.svg" %}</span>
Rename
{{ $t('rename') }}
</button>
<button class="dropdown-item" @click="updateFeedLink(current.feed)" v-if="current.feed.feed_link">
<span class="icon mr-1">{% inline "edit.svg" %}</span>
Change Link
{{ $t('change_link') }}
</button>
<div class="dropdown-divider"></div>
<header class="dropdown-header" role="heading" aria-level="2">Move to...</header>
<header class="dropdown-header" role="heading" aria-level="2">{{ $t('move_to') }}</header>
<button class="dropdown-item"
v-if="folder.id != current.feed.folder_id"
v-for="folder in folders"
@@ -248,17 +257,17 @@
</button>
<button class="dropdown-item text-muted" @click="moveFeedToNewFolder(current.feed)">
<span class="icon mr-1">{% inline "folder-plus.svg" %}</span>
new folder
{{ $t('new_folder') }}
</button>
<div class="dropdown-divider"></div>
<button class="dropdown-item text-danger" @click.prevent="deleteFeed(current.feed)">
<span class="icon mr-1">{% inline "trash.svg" %}</span>
Delete
{{ $t('delete') }}
</button>
</dropdown>
<dropdown class="settings-dropdown"
toggle-class="btn btn-link toolbar-item px-2 ml-2"
title="Folder Settings"
:title="$t('folder_settings')"
drop="right"
v-if="current.type == 'folder'">
<template v-slot:button>
@@ -267,12 +276,12 @@
<header class="dropdown-header" role="heading" aria-level="2">{{ current.folder.title }}</header>
<button class="dropdown-item" @click="renameFolder(current.folder)">
<span class="icon mr-1">{% inline "edit.svg" %}</span>
Rename
{{ $t('rename') }}
</button>
<div class="dropdown-divider"></div>
<button class="dropdown-item text-danger" @click="deleteFolder(current.folder)">
<span class="icon mr-1">{% inline "trash.svg" %}</span>
Delete
{{ $t('delete') }}
</button>
</dropdown>
</div>
@@ -291,7 +300,7 @@
</small>
<small class="flex-shrink-0"><relative-time v-bind:title="formatDate(item.date)" :val="item.date"/></small>
</div>
<div>{{ item.title || 'untitled' }}</div>
<div>{{ item.title || $t('untitled') }}</div>
</div>
</label>
<button class="btn btn-link btn-block loading my-3" v-if="itemsHasMore"></button>
@@ -305,24 +314,24 @@
<div class="toolbar px-2 d-flex align-items-center" v-if="itemSelectedDetails">
<button class="toolbar-item"
@click="toggleItemStarred(itemSelectedDetails)"
title="Mark Starred">
:title="$t('mark_starred')">
<span class="icon" v-if="itemSelectedDetails.status=='starred'" >{% inline "star-full.svg" %}</span>
<span class="icon" v-else-if="itemSelectedDetails.status!='starred'" >{% inline "star.svg" %}</span>
</button>
<button class="toolbar-item"
title="Mark Unread"
:title="$t('mark_unread')"
@click="toggleItemRead(itemSelectedDetails)">
<span class="icon" v-if="itemSelectedDetails.status=='unread'">{% inline "circle-full.svg" %}</span>
<span class="icon" v-if="itemSelectedDetails.status!='unread'">{% inline "circle.svg" %}</span>
</button>
<dropdown class="settings-dropdown" toggle-class="toolbar-item px-2" drop="center" title="Appearance">
<dropdown class="settings-dropdown" toggle-class="toolbar-item px-2" drop="center" :title="$t('appearance')">
<template v-slot:button>
<span class="icon">{% inline "sliders.svg" %}</span>
</template>
<button class="dropdown-item" :class="{active: !theme.font}" @click.stop="theme.font = ''">sans-serif</button>
<button class="dropdown-item font-serif" :class="{active: theme.font == 'serif'}" @click.stop="theme.font = 'serif'">serif</button>
<button class="dropdown-item font-monospace" :class="{active: theme.font == 'monospace'}" @click.stop="theme.font = 'monospace'">monospace</button>
<button class="dropdown-item" :class="{active: !theme.font}" @click.stop="theme.font = ''">{{ $t('sans_serif') }}</button>
<button class="dropdown-item font-serif" :class="{active: theme.font == 'serif'}" @click.stop="theme.font = 'serif'">{{ $t('serif') }}</button>
<button class="dropdown-item font-monospace" :class="{active: theme.font == 'monospace'}" @click.stop="theme.font = 'monospace'">{{ $t('monospace') }}</button>
<div class="d-flex text-center">
<button class="dropdown-item" style="font-size: 0.8rem" @click.stop="incrFont(-1)">A</button>
@@ -332,20 +341,20 @@
<button class="toolbar-item"
:class="{active: itemSelectedReadability}"
@click="toggleReadability()"
title="Read Here">
:title="$t('read_here')">
<span class="icon" :class="{'icon-loading': loading.readability}">{% inline "book-open.svg" %}</span>
</button>
<a class="toolbar-item" :href="itemSelectedDetails.link" rel="noopener noreferrer" target="_blank" referrerpolicy="no-referrer" title="Open Link">
<a class="toolbar-item" :href="itemSelectedDetails.link" rel="noopener noreferrer" target="_blank" referrerpolicy="no-referrer" :title="$t('open_link')">
<span class="icon">{% inline "external-link.svg" %}</span>
</a>
<div class="flex-grow-1"></div>
<button class="toolbar-item" @click="navigateToItem(-1)" title="Previous Article" :disabled="!items.length || itemSelected == items[0].id">
<button class="toolbar-item" @click="navigateToItem(-1)" :title="$t('previous_article')" :disabled="!items.length || itemSelected == items[0].id">
<span class="icon">{% inline "chevron-left.svg" %}</span>
</button>
<button class="toolbar-item" @click="navigateToItem(+1)" title="Next Article" :disabled="!items.length || itemSelected == items[items.length - 1].id">
<button class="toolbar-item" @click="navigateToItem(+1)" :title="$t('next_article')" :disabled="!items.length || itemSelected == items[items.length - 1].id">
<span class="icon">{% inline "chevron-right.svg" %}</span>
</button>
<button class="toolbar-item" @click="itemSelected=null" title="Close Article">
<button class="toolbar-item" @click="itemSelected=null" :title="$t('close_article')">
<span class="icon">{% inline "x.svg" %}</span>
</button>
</div>
@@ -355,7 +364,7 @@
:class="{'font-serif': theme.font == 'serif', 'font-monospace': theme.font == 'monospace'}"
:style="{'font-size': theme.size + 'rem'}">
<div class="content-wrapper">
<h1><b>{{ itemSelectedDetails.title || 'untitled' }}</b></h1>
<h1><b>{{ itemSelectedDetails.title || $t('untitled') }}</b></h1>
<div class="text-muted">
<div>
<span class="cursor-pointer" @click="feedSelected = 'feed:'+(feedsById[itemSelectedDetails.feed_id] || {}).id">
@@ -384,13 +393,13 @@
<span class="icon">{% inline "x.svg" %}</span>
</button>
<div v-if="settings=='create'">
<p class="cursor-default"><b>New Feed</b></p>
<p class="cursor-default"><b>{{ $t('new_feed') }}</b></p>
<form action="" @submit.prevent="createFeed(event)" class="mt-4">
<label for="feed-url">URL</label>
<label for="feed-url">{{ $t('url') }}</label>
<input id="feed-url" name="url" type="url" class="form-control" required autocomplete="off" :readonly="feedNewChoice.length > 0" placeholder="https://example.com/feed" v-focus>
<label for="feed-folder" class="mt-3 d-block">
Folder
<a href="#" class="float-right text-decoration-none" @click.prevent="createNewFeedFolder()">new folder</a>
{{ $t('folder') }}
<a href="#" class="float-right text-decoration-none" @click.prevent="createNewFeedFolder()">{{ $t('new_folder') }}</a>
</label>
<select class="form-control" id="feed-folder" name="folder_id" ref="newFeedFolder">
<option value="">---</option>
@@ -398,8 +407,8 @@
</select>
<div class="mt-4" v-if="feedNewChoice.length">
<p class="mb-2">
Multiple feeds found. Choose one below:
<a href="#" class="float-right text-decoration-none" @click.prevent="resetFeedChoice()">cancel</a>
{{ $t('multiple_feeds_found') }}
<a href="#" class="float-right text-decoration-none" @click.prevent="resetFeedChoice()">{{ $t('cancel') }}</a>
</p>
<label class="selectgroup" v-for="choice in feedNewChoice">
<input type="radio" name="feedToAdd" :value="choice.url" v-model="feedNewChoiceSelected">
@@ -409,29 +418,29 @@
</div>
</label>
</div>
<button class="btn btn-block btn-default mt-3" :class="{loading: loading.newfeed}" type="submit">Add</button>
<button class="btn btn-block btn-default mt-3" :class="{loading: loading.newfeed}" type="submit">{{ $t('add') }}</button>
</form>
</div>
<div v-else-if="settings=='shortcuts'">
<p class="cursor-default"><b>Keyboard Shortcuts</b></p>
<p class="cursor-default"><b>{{ $t('keyboard_shortcuts') }}</b></p>
<table class="table table-borderless table-sm table-compact m-0">
<tr><td><kbd>1</kbd> <kbd>2</kbd> <kbd>3</kbd></td>
<td>show unread / starred / all feeds</td></tr>
<tr><td><kbd>/</kbd></td> <td>focus the search bar</td></tr>
<td>{{ $t('kb_show_filters') }}</td></tr>
<tr><td><kbd>/</kbd></td> <td>{{ $t('kb_focus_search') }}</td></tr>
<tr><td colspan=2>&nbsp;</td></tr>
<tr><td><kbd>j</kbd> <kbd>k</kbd></td> <td>next / prev article</td></tr>
<tr><td><kbd>l</kbd> <kbd>h</kbd></td> <td>next / prev feed</td></tr>
<tr><td><kbd>q</kbd></td> <td>close article</td></tr>
<tr><td><kbd>j</kbd> <kbd>k</kbd></td> <td>{{ $t('kb_next_prev_article') }}</td></tr>
<tr><td><kbd>l</kbd> <kbd>h</kbd></td> <td>{{ $t('kb_next_prev_feed') }}</td></tr>
<tr><td><kbd>q</kbd></td> <td>{{ $t('kb_close_article') }}</td></tr>
<tr><td colspan=2>&nbsp;</td></tr>
<tr><td><kbd>R</kbd></td> <td>mark all read</td></tr>
<tr><td><kbd>r</kbd></td> <td>mark read / unread</td></tr>
<tr><td><kbd>s</kbd></td> <td>mark starred / unstarred</td></tr>
<tr><td><kbd>o</kbd></td> <td>open link</td></tr>
<tr><td><kbd>i</kbd></td> <td>read here</td> </tr>
<tr><td><kbd>f</kbd> <kbd>b</kbd></td> <td>scroll content forward / backward</td>
<tr><td><kbd>R</kbd></td> <td>{{ $t('kb_mark_all_read') }}</td></tr>
<tr><td><kbd>r</kbd></td> <td>{{ $t('kb_mark_read') }}</td></tr>
<tr><td><kbd>s</kbd></td> <td>{{ $t('kb_mark_starred') }}</td></tr>
<tr><td><kbd>o</kbd></td> <td>{{ $t('kb_open_link') }}</td></tr>
<tr><td><kbd>i</kbd></td> <td>{{ $t('kb_read_here') }}</td> </tr>
<tr><td><kbd>f</kbd> <kbd>b</kbd></td> <td>{{ $t('kb_scroll_content') }}</td>
</tr>
</table>
</div>
@@ -440,6 +449,7 @@
<!-- external -->
<script src="./static/javascripts/vue.min.js"></script>
<!-- internal -->
<script src="./static/javascripts/i18n.js"></script>
<script src="./static/javascripts/api.js"></script>
<script src="./static/javascripts/app.js"></script>
<script src="./static/javascripts/key.js"></script>

View File

@@ -202,6 +202,8 @@ Vue.component('relative-time', {
},
})
Vue.use(i18n)
var vm = new Vue({
created: function() {
this.refreshStats()
@@ -212,6 +214,7 @@ var vm = new Vue({
vm.feed_errors = errors
})
this.updateMetaTheme(app.settings.theme_name)
this.$setLang(app.settings.language)
},
data: function() {
var s = app.settings
@@ -269,6 +272,13 @@ var vm = new Vue({
{ title: "12h", value: 720 },
{ title: "24h", value: 1440 },
],
'language': s.language,
'languages': [
{code: 'en', name: 'English' },
{code: 'zh', name: '简体中文'},
{code: 'ru', name: 'Русский'},
]
}
},
computed: {
@@ -834,6 +844,11 @@ var vm = new Vue({
&& !this.filteredFeedStats[feed.id]
&& (!this.itemSelectedDetails || this.itemSelectedDetails.feed_id != feed.id)
},
changeLanguage(lang) {
this.$setLang(lang)
this.language = lang
api.settings.update({language: lang})
}
}
})

View File

@@ -0,0 +1,372 @@
(function (exports) {
const translations = {
"unread": {
"en": "Unread",
"zh": "未读",
"ru": "Непрочитанные"
},
"starred": {
"en": "Starred",
"zh": "星标",
"ru": "Избранные"
},
"all": {
"en": "All",
"zh": "全部",
"ru": "Все"
},
"settings": {
"en": "Settings",
"zh": "设置",
"ru": "Настройки"
},
"new_feed": {
"en": "New Feed",
"zh": "新建订阅",
"ru": "Новая лента"
},
"refresh_feeds": {
"en": "Refresh Feeds",
"zh": "刷新订阅",
"ru": "Обновить ленты"
},
"theme": {
"en": "Theme",
"zh": "主题",
"ru": "Тема"
},
"auto_refresh": {
"en": "Auto Refresh",
"zh": "自动刷新",
"ru": "Автообновление"
},
"show_first": {
"en": "Show first",
"zh": "优先显示",
"ru": "Сначала"
},
"new": {
"en": "New",
"zh": "最新",
"ru": "Новые"
},
"old": {
"en": "Old",
"zh": "最旧",
"ru": "Старые"
},
"subscriptions": {
"en": "Subscriptions",
"zh": "订阅管理",
"ru": "Подписки"
},
"import": {
"en": "Import",
"zh": "导入",
"ru": "Импорт"
},
"export": {
"en": "Export",
"zh": "导出",
"ru": "Экспорт"
},
"shortcuts": {
"en": "Shortcuts",
"zh": "快捷键",
"ru": "Горячие клавиши"
},
"log_out": {
"en": "Log out",
"zh": "登出",
"ru": "Выйти"
},
"all_unread": {
"en": "All Unread",
"zh": "全部未读",
"ru": "Все непрочитанные"
},
"all_starred": {
"en": "All Starred",
"zh": "全部星标",
"ru": "Все избранные"
},
"all_feeds": {
"en": "All Feeds",
"zh": "全部订阅",
"ru": "Все ленты"
},
"refreshing": {
"en": "Refreshing",
"zh": "正在刷新",
"ru": "Обновление"
},
"left": {
"en": "left",
"zh": "剩余",
"ru": "осталось"
},
"show_feeds": {
"en": "Show Feeds",
"zh": "显示订阅",
"ru": "Показать ленты"
},
"mark_all_read": {
"en": "Mark All Read",
"zh": "全部标记为已读",
"ru": "Отметить все как прочитанные"
},
"feed_settings": {
"en": "Feed Settings",
"zh": "订阅设置",
"ru": "Настройки ленты"
},
"folder_settings": {
"en": "Folder Settings",
"zh": "文件夹设置",
"ru": "Настройки папки"
},
"website": {
"en": "Website",
"zh": "网站",
"ru": "Сайт"
},
"feed_link": {
"en": "Feed Link",
"zh": "订阅链接",
"ru": "Ссылка на ленту"
},
"rename": {
"en": "Rename",
"zh": "重命名",
"ru": "Переименовать"
},
"change_link": {
"en": "Change Link",
"zh": "修改链接",
"ru": "Изменить ссылку"
},
"move_to": {
"en": "Move to...",
"zh": "移动到...",
"ru": "Переместить в..."
},
"new_folder": {
"en": "new folder",
"zh": "新建文件夹",
"ru": "новая папка"
},
"delete": {
"en": "Delete",
"zh": "删除",
"ru": "Удалить"
},
"mark_starred": {
"en": "Mark Starred",
"zh": "标记星标",
"ru": "Пометить избранным"
},
"mark_unread": {
"en": "Mark Unread",
"zh": "标记未读",
"ru": "Пометить непрочитанным"
},
"appearance": {
"en": "Appearance",
"zh": "外观",
"ru": "Внешний вид"
},
"read_here": {
"en": "Read Here",
"zh": "在此阅读",
"ru": "Читать здесь"
},
"open_link": {
"en": "Open Link",
"zh": "打开链接",
"ru": "Открыть ссылку"
},
"previous_article": {
"en": "Previous Article",
"zh": "上一篇",
"ru": "Предыдущая статья"
},
"next_article": {
"en": "Next Article",
"zh": "下一篇",
"ru": "Следующая статья"
},
"close_article": {
"en": "Close Article",
"zh": "关闭文章",
"ru": "Закрыть статью"
},
"untitled": {
"en": "untitled",
"zh": "无标题",
"ru": "без названия"
},
"sans_serif": {
"en": "sans-serif",
"zh": "无衬线",
"ru": "sans-serif"
},
"serif": {
"en": "serif",
"zh": "衬线",
"ru": "serif"
},
"monospace": {
"en": "monospace",
"zh": "等宽",
"ru": "monospace"
},
"url": {
"en": "URL",
"zh": "网址",
"ru": "URL"
},
"folder": {
"en": "Folder",
"zh": "文件夹",
"ru": "Папка"
},
"add": {
"en": "Add",
"zh": "添加",
"ru": "Добавить"
},
"keyboard_shortcuts": {
"en": "Keyboard Shortcuts",
"zh": "键盘快捷键",
"ru": "Горячие клавиши"
},
"multiple_feeds_found": {
"en": "Multiple feeds found. Choose one below:",
"zh": "找到多个订阅源,请选择一个:",
"ru": "Найдено несколько лент. Выберите одну:"
},
"cancel": {
"en": "cancel",
"zh": "取消",
"ru": "отмена"
},
"kb_show_filters": {
"en": "show unread / starred / all feeds",
"zh": "显示未读/星标/全部订阅",
"ru": "показать непрочитанные / избранные / все ленты"
},
"kb_focus_search": {
"en": "focus the search bar",
"zh": "聚焦搜索栏",
"ru": "фокус на строку поиска"
},
"kb_next_prev_article": {
"en": "next / prev article",
"zh": "下一篇/上一篇文章",
"ru": "следующая / предыдущая статья"
},
"kb_next_prev_feed": {
"en": "next / prev feed",
"zh": "下一个/上一个订阅",
"ru": "следующая / предыдущая лента"
},
"kb_close_article": {
"en": "close article",
"zh": "关闭文章",
"ru": "закрыть статью"
},
"kb_mark_all_read": {
"en": "mark all read",
"zh": "全部标记为已读",
"ru": "отметить все как прочитанные"
},
"kb_mark_read": {
"en": "mark read / unread",
"zh": "标记已读/未读",
"ru": "отметить как прочитанное / непрочитанное"
},
"kb_mark_starred": {
"en": "mark starred / unstarred",
"zh": "标记星标/取消星标",
"ru": "пометить избранным / убрать из избранного"
},
"kb_open_link": {
"en": "open link",
"zh": "打开链接",
"ru": "открыть ссылку"
},
"kb_read_here": {
"en": "read here",
"zh": "在此阅读",
"ru": "читать здесь"
},
"kb_scroll_content": {
"en": "scroll content forward / backward",
"zh": "向前/向后滚动内容",
"ru": "прокрутка вперед / назад"
},
"prompt_folder_name": {
"en": "Enter folder name:",
"zh": "请输入文件夹名称:",
"ru": "Введите имя папки:"
},
"prompt_new_title": {
"en": "Enter new title",
"zh": "请输入新标题",
"ru": "Введите новый заголовок"
},
"prompt_feed_link": {
"en": "Enter feed link",
"zh": "请输入订阅链接",
"ru": "Введите ссылку на ленту"
},
"confirm_delete_folder": {
"en": "Are you sure you want to delete",
"zh": "确定要删除",
"ru": "Вы уверены, что хотите удалить"
},
"confirm_delete_feed": {
"en": "Are you sure you want to delete",
"zh": "确定要删除",
"ru": "Вы уверены, что хотите удалить"
},
"alert_no_feeds": {
"en": "No feeds found at the given url.",
"zh": "在指定的网址未找到订阅源。",
"ru": "Лент по данному адресу не найдено."
},
"login": {
"en": "Login",
"zh": "登录",
"ru": "Вход"
},
"username": {
"en": "Username",
"zh": "用户名",
"ru": "Имя пользователя"
},
"password": {
"en": "Password",
"zh": "密码",
"ru": "Пароль"
},
};
class i18n {
constructor() {
this.lang = 'en'
}
setLang(lang) {
this.lang = lang
}
$t(code) {
return translations[code][this.lang]
}
}
exports.i18n = {
install(Vue, opts) {
const x = new i18n();
Vue.prototype.$t = x.$t
Vue.prototype.$setLang = x.setLang
}
}
})(window)

View File

@@ -17,6 +17,7 @@ func settingsDefaults() map[string]any {
"theme_font": "",
"theme_size": 1,
"refresh_rate": 0,
"language": "en",
}
}