mirror of
https://github.com/yang991178/fluent-reader.git
synced 2025-03-27 08:50:22 +01:00
english translation
This commit is contained in:
parent
34b281f59a
commit
a0d5e41bf2
@ -1,4 +1,5 @@
|
||||
import * as React from "react"
|
||||
import intl = require("react-intl-universal")
|
||||
import { renderToString } from "react-dom/server"
|
||||
import { RSSItem } from "../scripts/models/item"
|
||||
import { openExternal } from "../scripts/utils"
|
||||
@ -11,6 +12,7 @@ const FONT_SIZE_OPTIONS = [12, 13, 14, 15, 16, 17, 18, 19, 20]
|
||||
type ArticleProps = {
|
||||
item: RSSItem
|
||||
source: RSSSource
|
||||
locale: string
|
||||
dismiss: () => void
|
||||
toggleHasRead: (item: RSSItem) => void
|
||||
toggleStarred: (item: RSSItem) => void
|
||||
@ -57,13 +59,13 @@ class Article extends React.Component<ArticleProps, ArticleState> {
|
||||
items: [
|
||||
{
|
||||
key: "openInBrowser",
|
||||
text: "在浏览器中打开",
|
||||
text: intl.get("openExternal"),
|
||||
iconProps: {iconName: "NavigateExternalInline"},
|
||||
onClick: this.openInBrowser
|
||||
},
|
||||
{
|
||||
key: "toggleHidden",
|
||||
text: this.props.item.hidden ? "取消隐藏" : "隐藏文章",
|
||||
text: this.props.item.hidden ? intl.get("article.unhide") : intl.get("article.hide"),
|
||||
onClick: () => { this.props.toggleHidden(this.props.item) }
|
||||
}
|
||||
]
|
||||
@ -128,7 +130,7 @@ class Article extends React.Component<ArticleProps, ArticleState> {
|
||||
|
||||
articleView = () => "article/article.html?h=" + window.btoa(encodeURIComponent(renderToString(<>
|
||||
<p className="title">{this.props.item.title}</p>
|
||||
<p className="date">{this.props.item.date.toLocaleString("zh-cn", {hour12: false})}</p>
|
||||
<p className="date">{this.props.item.date.toLocaleString(this.props.locale, {hour12: !this.props.locale.startsWith("zh")})}</p>
|
||||
<article dangerouslySetInnerHTML={{__html: this.props.item.content}}></article>
|
||||
</>))) + `&s=${this.state.fontSize}&u=${this.props.item.link}&d=${Number(document.body.classList.contains("dark"))}`
|
||||
|
||||
@ -144,35 +146,35 @@ class Article extends React.Component<ArticleProps, ArticleState> {
|
||||
</span>
|
||||
</Stack.Item>
|
||||
<CommandBarButton
|
||||
title={this.props.item.hasRead ? "标为未读" : "标为已读"}
|
||||
title={this.props.item.hasRead ? intl.get("article.markUnread") : intl.get("article.markRead")}
|
||||
iconProps={this.props.item.hasRead
|
||||
? {iconName: "StatusCircleRing"}
|
||||
: {iconName: "RadioBtnOn", style: {fontSize: 14, textAlign: "center"}}}
|
||||
onClick={() => this.props.toggleHasRead(this.props.item)} />
|
||||
<CommandBarButton
|
||||
title={this.props.item.starred ? "取消星标" : "标为星标"}
|
||||
title={this.props.item.starred ? intl.get("article.unstar") : intl.get("article.star")}
|
||||
iconProps={{iconName: this.props.item.starred ? "FavoriteStarFill" : "FavoriteStar"}}
|
||||
onClick={() => this.props.toggleStarred(this.props.item)} />
|
||||
<CommandBarButton
|
||||
title="字体大小"
|
||||
title={intl.get("article.fontSize")}
|
||||
disabled={this.state.loadWebpage}
|
||||
iconProps={{iconName: "FontSize"}}
|
||||
menuIconProps={{style: {display: "none"}}}
|
||||
menuProps={this.fontMenuProps()} />
|
||||
<CommandBarButton
|
||||
title="加载网页"
|
||||
title={intl.get("article.loadWebpage")}
|
||||
className={this.state.loadWebpage ? "active" : ""}
|
||||
iconProps={{iconName: "Globe"}}
|
||||
onClick={this.toggleWebpage} />
|
||||
<CommandBarButton
|
||||
title="更多"
|
||||
title={intl.get("more")}
|
||||
iconProps={{iconName: "More"}}
|
||||
menuIconProps={{style: {display: "none"}}}
|
||||
menuProps={this.moreMenuProps()} />
|
||||
</Stack>
|
||||
<Stack horizontal horizontalAlign="end" style={{width: 112}}>
|
||||
<CommandBarButton
|
||||
title="关闭"
|
||||
title={intl.get("close")}
|
||||
iconProps={{iconName: "BackToWindow"}}
|
||||
onClick={this.props.dismiss} />
|
||||
</Stack>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import * as React from "react"
|
||||
import intl = require("react-intl-universal")
|
||||
import { clipboard } from "electron"
|
||||
import { openExternal, cutText, googleSearch } from "../scripts/utils"
|
||||
import { ContextualMenu, IContextualMenuItem, ContextualMenuItemType, DirectionalHint } from "office-ui-fabric-react/lib/ContextualMenu"
|
||||
@ -34,7 +35,7 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
||||
case ContextMenuType.Item: return [
|
||||
{
|
||||
key: "showItem",
|
||||
text: "阅读",
|
||||
text: intl.get("context.read"),
|
||||
iconProps: { iconName: "TextDocument" },
|
||||
onClick: () => {
|
||||
this.props.markRead(this.props.item)
|
||||
@ -43,7 +44,7 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
||||
},
|
||||
{
|
||||
key: "openInBrowser",
|
||||
text: "在浏览器中打开",
|
||||
text: intl.get("openExternal"),
|
||||
iconProps: { iconName: "NavigateExternalInline" },
|
||||
onClick: () => {
|
||||
this.props.markRead(this.props.item)
|
||||
@ -53,25 +54,25 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
||||
this.props.item.hasRead
|
||||
? {
|
||||
key: "markAsUnread",
|
||||
text: "标为未读",
|
||||
text: intl.get("article.markUnread"),
|
||||
iconProps: { iconName: "RadioBtnOn", style: { fontSize: 14, textAlign: "center" } },
|
||||
onClick: () => { this.props.markUnread(this.props.item) }
|
||||
}
|
||||
: {
|
||||
key: "markAsRead",
|
||||
text: "标为已读",
|
||||
text: intl.get("article.markRead"),
|
||||
iconProps: { iconName: "StatusCircleRing" },
|
||||
onClick: () => { this.props.markRead(this.props.item) }
|
||||
},
|
||||
{
|
||||
key: "toggleStarred",
|
||||
text: this.props.item.starred ? "取消星标" : "标为星标",
|
||||
text: this.props.item.starred ? intl.get("article.unstar") : intl.get("article.star"),
|
||||
iconProps: { iconName: this.props.item.starred ? "FavoriteStar" : "FavoriteStarFill" },
|
||||
onClick: () => { this.props.toggleStarred(this.props.item) }
|
||||
},
|
||||
{
|
||||
key: "toggleHidden",
|
||||
text: this.props.item.hidden ? "取消隐藏" : "隐藏文章",
|
||||
text: this.props.item.hidden ? intl.get("article.unhide") : intl.get("article.hide"),
|
||||
onClick: () => { this.props.toggleHidden(this.props.item) }
|
||||
},
|
||||
{
|
||||
@ -80,25 +81,25 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
||||
},
|
||||
{
|
||||
key: "copyTitle",
|
||||
text: "复制标题",
|
||||
text: intl.get("context.copyTitle"),
|
||||
onClick: () => { clipboard.writeText(this.props.item.title) }
|
||||
},
|
||||
{
|
||||
key: "copyURL",
|
||||
text: "复制链接",
|
||||
text: intl.get("context.copyURL"),
|
||||
onClick: () => { clipboard.writeText(this.props.item.link) }
|
||||
}
|
||||
]
|
||||
case ContextMenuType.Text: return [
|
||||
{
|
||||
key: "copyText",
|
||||
text: "复制",
|
||||
text: intl.get("context.copy"),
|
||||
iconProps: { iconName: "Copy" },
|
||||
onClick: () => { clipboard.writeText(this.props.text) }
|
||||
},
|
||||
{
|
||||
key: "searchText",
|
||||
text: `使用Google搜索“${cutText(this.props.text, 15)}”`,
|
||||
text: intl.get("context.search", { text: cutText(this.props.text, 15) }),
|
||||
iconProps: { iconName: "Search" },
|
||||
onClick: () => { googleSearch(this.props.text) }
|
||||
}
|
||||
@ -108,12 +109,12 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
||||
key: "section_1",
|
||||
itemType: ContextualMenuItemType.Section,
|
||||
sectionProps: {
|
||||
title: "视图",
|
||||
title: intl.get("context.view"),
|
||||
bottomDivider: true,
|
||||
items: [
|
||||
{
|
||||
key: "cardView",
|
||||
text: "卡片视图",
|
||||
text: intl.get("context.cardView"),
|
||||
iconProps: { iconName: "GridViewMedium" },
|
||||
canCheck: true,
|
||||
checked: this.props.viewType === ViewType.Cards,
|
||||
@ -121,7 +122,7 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
||||
},
|
||||
{
|
||||
key: "listView",
|
||||
text: "列表视图",
|
||||
text: intl.get("context.listView"),
|
||||
iconProps: { iconName: "BacklogList" },
|
||||
canCheck: true,
|
||||
checked: this.props.viewType === ViewType.List,
|
||||
@ -134,12 +135,12 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
||||
key: "section_2",
|
||||
itemType: ContextualMenuItemType.Section,
|
||||
sectionProps: {
|
||||
title: "筛选",
|
||||
title: intl.get("context.filter"),
|
||||
bottomDivider: true,
|
||||
items: [
|
||||
{
|
||||
key: "allArticles",
|
||||
text: "全部文章",
|
||||
text: intl.get("allArticles"),
|
||||
iconProps: { iconName: "ClearFilter" },
|
||||
canCheck: true,
|
||||
checked: (this.props.filter & ~FeedFilter.ShowHidden) == FeedFilter.Default,
|
||||
@ -147,7 +148,7 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
||||
},
|
||||
{
|
||||
key: "unreadOnly",
|
||||
text: "仅未读文章",
|
||||
text: intl.get("context.unreadOnly"),
|
||||
iconProps: { iconName: "RadioBtnOn", style: { fontSize: 14, textAlign: "center" } },
|
||||
canCheck: true,
|
||||
checked: (this.props.filter & ~FeedFilter.ShowHidden) == FeedFilter.UnreadOnly,
|
||||
@ -155,7 +156,7 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
||||
},
|
||||
{
|
||||
key: "starredOnly",
|
||||
text: "仅星标文章",
|
||||
text: intl.get("context.starredOnly"),
|
||||
iconProps: { iconName: "FavoriteStarFill" },
|
||||
canCheck: true,
|
||||
checked: (this.props.filter & ~FeedFilter.ShowHidden) == FeedFilter.StarredOnly,
|
||||
@ -166,7 +167,7 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
||||
},
|
||||
{
|
||||
key: "showHidden",
|
||||
text: "显示隐藏文章",
|
||||
text: intl.get("context.showHidden"),
|
||||
canCheck: true,
|
||||
checked: Boolean(this.props.filter & FeedFilter.ShowHidden),
|
||||
onClick: () => this.props.toggleFilter(FeedFilter.ShowHidden)
|
||||
|
@ -1,5 +1,6 @@
|
||||
import * as React from "react"
|
||||
import { Feed, FeedProps } from "./feed"
|
||||
import intl = require("react-intl-universal")
|
||||
import { FeedProps } from "./feed"
|
||||
import DefaultCard from "../cards/default-card"
|
||||
import { PrimaryButton } from 'office-ui-fabric-react';
|
||||
|
||||
@ -47,7 +48,7 @@ class CardsFeed extends React.Component<FeedProps> {
|
||||
{
|
||||
(this.props.feed.loaded && !this.props.feed.allLoaded)
|
||||
? <div className="load-more-wrapper"><PrimaryButton
|
||||
text="加载更多"
|
||||
text={intl.get("loadMore")}
|
||||
disabled={this.props.feed.loading}
|
||||
onClick={() => this.props.loadMore(this.props.feed)} /></div>
|
||||
: null
|
||||
|
@ -1,4 +1,5 @@
|
||||
import * as React from "react"
|
||||
import intl = require("react-intl-universal")
|
||||
import { FeedProps } from "./feed"
|
||||
import { DefaultButton } from 'office-ui-fabric-react';
|
||||
import ListCard from "../cards/list-card";
|
||||
@ -22,7 +23,7 @@ class ListFeed extends React.Component<FeedProps> {
|
||||
{
|
||||
(this.props.feed.loaded && !this.props.feed.allLoaded)
|
||||
? <div className="load-more-wrapper"><DefaultButton
|
||||
text="加载更多"
|
||||
text={intl.get("loadMore")}
|
||||
disabled={this.props.feed.loading}
|
||||
onClick={() => this.props.loadMore(this.props.feed)} /></div>
|
||||
: null
|
||||
|
@ -1,4 +1,5 @@
|
||||
import * as React from "react"
|
||||
import intl = require("react-intl-universal")
|
||||
import { Callout, ActivityItem, Icon, DirectionalHint } from "@fluentui/react"
|
||||
import { AppLog, AppLogType } from "../scripts/models/app"
|
||||
import Time from "./utils/time"
|
||||
@ -29,7 +30,7 @@ class LogMenu extends React.Component<LogMenuProps> {
|
||||
onDismiss={() => this.props.close()}
|
||||
>
|
||||
{ this.props.logs.length == 0
|
||||
? <p style={{ textAlign: "center" }}>无消息</p>
|
||||
? <p style={{ textAlign: "center" }}>{intl.get("log.empty")}</p>
|
||||
: this.activityItems().map((item => (
|
||||
<ActivityItem {...item} key={item.key} style={{ margin: 12 }} />
|
||||
))) }
|
||||
|
@ -1,4 +1,5 @@
|
||||
import * as React from "react"
|
||||
import intl = require("react-intl-universal")
|
||||
import { Icon } from "@fluentui/react/lib/Icon"
|
||||
import { Nav, INavLink, INavLinkGroup } from "office-ui-fabric-react/lib/Nav"
|
||||
import { SourceGroup } from "../scripts/models/group"
|
||||
@ -23,13 +24,13 @@ export class Menu extends React.Component<MenuProps> {
|
||||
{
|
||||
links: [
|
||||
{
|
||||
name: "搜索",
|
||||
name: intl.get("search"),
|
||||
key: "search",
|
||||
icon: "Search",
|
||||
url: null
|
||||
},
|
||||
{
|
||||
name: "全部文章",
|
||||
name: intl.get("allArticles"),
|
||||
key: ALL,
|
||||
icon: "TextDocument",
|
||||
onClick: this.props.allArticles,
|
||||
@ -78,14 +79,14 @@ export class Menu extends React.Component<MenuProps> {
|
||||
<div className="menu-container" onClick={this.props.toggleMenu} style={{display: this.props.display ? "block" : "none"}}>
|
||||
<div className="menu" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="btn-group">
|
||||
<a className="btn hide-wide" title="关闭菜单" onClick={this.props.toggleMenu}><Icon iconName="Back" /></a>
|
||||
<a className="btn inline-block-wide" title="关闭菜单" onClick={this.props.toggleMenu}><Icon iconName="GlobalNavButton" /></a>
|
||||
<a className="btn hide-wide" title={intl.get("menu.close")} onClick={this.props.toggleMenu}><Icon iconName="Back" /></a>
|
||||
<a className="btn inline-block-wide" title={intl.get("menu.close")} onClick={this.props.toggleMenu}><Icon iconName="GlobalNavButton" /></a>
|
||||
</div>
|
||||
<div className="nav-wrapper">
|
||||
<Nav
|
||||
groups={this.getItems()}
|
||||
selectedKey={this.props.selected} />
|
||||
<p className={"subs-header " + AnimationClassNames.slideDownIn10}>订阅源</p>
|
||||
<p className={"subs-header " + AnimationClassNames.slideDownIn10}>{intl.get("menu.subscriptions")}</p>
|
||||
<Nav
|
||||
selectedKey={this.props.selected}
|
||||
groups={this.getGroups()} />
|
||||
|
@ -1,4 +1,5 @@
|
||||
import * as React from "react"
|
||||
import intl = require("react-intl-universal")
|
||||
import { remote } from "electron"
|
||||
import { Icon } from "@fluentui/react/lib/Icon"
|
||||
import { AppState } from "../scripts/models/app"
|
||||
@ -76,25 +77,59 @@ class Nav extends React.Component<NavProps, NavState> {
|
||||
return (
|
||||
<nav className={this.hideButtons() + this.menuOn() + this.itemOn()}>
|
||||
<div className="btn-group">
|
||||
<a className="btn hide-wide" title="菜单" onClick={this.props.menu}><Icon iconName="GlobalNavButton" /></a>
|
||||
<a className="btn hide-wide"
|
||||
title={intl.get("nav.menu")}
|
||||
onClick={this.props.menu}>
|
||||
<Icon iconName="GlobalNavButton" />
|
||||
</a>
|
||||
</div>
|
||||
<span className="title">{this.props.state.title}</span>
|
||||
<div className="btn-group" style={{float:"right"}}>
|
||||
<a className={"btn"+this.fetching()} onClick={this.fetch} title="刷新"><Icon iconName="Refresh" /></a>
|
||||
<a className="btn" title="全部标为已读"><Icon iconName="InboxCheck" /></a>
|
||||
<a className="btn" id="log-toggle" title="消息" onClick={this.props.logs}>
|
||||
<a className={"btn"+this.fetching()}
|
||||
onClick={this.fetch}
|
||||
title={intl.get("nav.refresh")}>
|
||||
<Icon iconName="Refresh" />
|
||||
</a>
|
||||
<a className="btn" title={intl.get("nav.markAllRead")}>
|
||||
<Icon iconName="InboxCheck" />
|
||||
</a>
|
||||
<a className="btn"
|
||||
id="log-toggle"
|
||||
title={intl.get("nav.notifications")}
|
||||
onClick={this.props.logs}>
|
||||
{this.props.state.logMenu.notify ? <Icon iconName="RingerSolid" /> : <Icon iconName="Ringer" />}
|
||||
</a>
|
||||
<a className="btn" id="view-toggle" title="视图" onClick={this.props.views}
|
||||
onMouseDown={e => {if (this.props.state.contextMenu.event === "#view-toggle") e.stopPropagation()}}>
|
||||
<a className="btn"
|
||||
id="view-toggle"
|
||||
title={intl.get("nav.view")}
|
||||
onClick={this.props.views}
|
||||
onMouseDown={e => {
|
||||
if (this.props.state.contextMenu.event === "#view-toggle") e.stopPropagation()}}>
|
||||
<Icon iconName="View" /></a>
|
||||
<a className="btn" title="选项" onClick={this.props.settings}><Icon iconName="Settings" /></a>
|
||||
<span className="seperator"></span>
|
||||
<a className="btn system" title="最小化" onClick={this.minimize} style={{fontSize: 12}}><Icon iconName="Remove" /></a>
|
||||
<a className="btn system" title="最大化" onClick={this.maximize}>
|
||||
{this.state.maximized ? <Icon iconName="ChromeRestore" style={{fontSize: 11}} /> :<Icon iconName="Checkbox" style={{fontSize: 10}} />}
|
||||
<a className="btn"
|
||||
title={intl.get("nav.settings")}
|
||||
onClick={this.props.settings}>
|
||||
<Icon iconName="Settings" />
|
||||
</a>
|
||||
<span className="seperator"></span>
|
||||
<a className="btn system"
|
||||
title={intl.get("nav.minimize")}
|
||||
onClick={this.minimize}
|
||||
style={{fontSize: 12}}>
|
||||
<Icon iconName="Remove" />
|
||||
</a>
|
||||
<a className="btn system"
|
||||
title={intl.get("nav.maximize")}
|
||||
onClick={this.maximize}>
|
||||
{this.state.maximized
|
||||
? <Icon iconName="ChromeRestore" style={{fontSize: 11}} />
|
||||
: <Icon iconName="Checkbox" style={{fontSize: 10}} />}
|
||||
</a>
|
||||
<a className="btn system close"
|
||||
title={intl.get("close")}
|
||||
onClick={this.close}>
|
||||
<Icon iconName="Cancel" />
|
||||
</a>
|
||||
<a className="btn system close" title="关闭" onClick={this.close}><Icon iconName="Cancel" /></a>
|
||||
</div>
|
||||
{!this.canFetch() &&
|
||||
<ProgressIndicator
|
||||
|
@ -1,4 +1,5 @@
|
||||
import * as React from "react"
|
||||
import intl = require("react-intl-universal")
|
||||
import { Icon } from "@fluentui/react/lib/Icon"
|
||||
import { AnimationClassNames } from "@fluentui/react/lib/Styling"
|
||||
import AboutTab from "./settings/about"
|
||||
@ -23,24 +24,24 @@ class Settings extends React.Component<SettingsProps> {
|
||||
<div className="settings-container">
|
||||
<div className={"settings " + AnimationClassNames.slideUpIn20}>
|
||||
{this.props.blocked && <div className="loading">
|
||||
<Spinner label="正在更新订阅源,请稍候…" />
|
||||
<Spinner label={intl.get("settings.fetching")} />
|
||||
</div>}
|
||||
<div className="btn-group" style={{position: "absolute", top: 6, left: -64}}>
|
||||
<a className={"btn" + (this.props.exitting ? " disabled" : "")} title="退出设置" onClick={this.props.close}>
|
||||
<a className={"btn" + (this.props.exitting ? " disabled" : "")} title={intl.get("settings.exit")} onClick={this.props.close}>
|
||||
<Icon iconName="Back" />
|
||||
</a>
|
||||
</div>
|
||||
<Pivot>
|
||||
<PivotItem headerText="订阅源" itemIcon="Source">
|
||||
<PivotItem headerText={intl.get("settings.sources")} itemIcon="Source">
|
||||
<SourcesTabContainer />
|
||||
</PivotItem>
|
||||
<PivotItem headerText="分组与排序" itemIcon="GroupList">
|
||||
<PivotItem headerText={intl.get("settings.grouping")} itemIcon="GroupList">
|
||||
<GroupsTabContainer />
|
||||
</PivotItem>
|
||||
<PivotItem headerText="应用选项" itemIcon="Settings">
|
||||
<PivotItem headerText={intl.get("settings.app")} itemIcon="Settings">
|
||||
<AppTabContainer />
|
||||
</PivotItem>
|
||||
<PivotItem headerText="关于" itemIcon="Info">
|
||||
<PivotItem headerText={intl.get("settings.about")} itemIcon="Info">
|
||||
<AboutTab />
|
||||
</PivotItem>
|
||||
</Pivot>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import * as React from "react"
|
||||
import intl = require("react-intl-universal")
|
||||
import { Stack, Link } from "@fluentui/react"
|
||||
import { openExternal } from "../../scripts/utils"
|
||||
|
||||
@ -8,11 +9,11 @@ class AboutTab extends React.Component {
|
||||
<Stack className="settings-about" horizontalAlign="center">
|
||||
<img src="logo.svg" style={{width: 120, height: 120}} />
|
||||
<h3>Fluent Reader</h3>
|
||||
<small>版本 0.1.0</small>
|
||||
<small>{intl.get("settings.version")} 0.1.0</small>
|
||||
<p className="settings-hint">Copyright © 2020 Haoyuan Liu. All rights reserved.</p>
|
||||
<Stack horizontal horizontalAlign="center" tokens={{childrenGap: 8}}>
|
||||
<small><Link onClick={() => openExternal("https://github.com/yang991178/fluent-reader")}>开源项目</Link></small>
|
||||
<small><Link onClick={() => openExternal("https://github.com/yang991178/fluent-reader/issues")}>反馈</Link></small>
|
||||
<Stack horizontal horizontalAlign="center" tokens={{childrenGap: 12}}>
|
||||
<small><Link onClick={() => openExternal("https://github.com/yang991178/fluent-reader")}>{intl.get("settings.openSource")}</Link></small>
|
||||
<small><Link onClick={() => openExternal("https://github.com/yang991178/fluent-reader/issues")}>{intl.get("settings.feedback")}</Link></small>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</div>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import * as React from "react"
|
||||
import intl = require("react-intl-universal")
|
||||
import { SourceGroup } from "../../scripts/models/group"
|
||||
import { SourceState, RSSSource } from "../../scripts/models/source"
|
||||
import { IColumn, Selection, SelectionMode, DetailsList, Label, Stack,
|
||||
@ -69,22 +70,22 @@ class GroupsTab extends React.Component<GroupsTabProps, GroupsTabState> {
|
||||
})
|
||||
}
|
||||
|
||||
groupColumns: IColumn[] = [
|
||||
groupColumns = (): IColumn[] => [
|
||||
{
|
||||
key: "type",
|
||||
name: "类型",
|
||||
name: intl.get("groups.type"),
|
||||
minWidth: 40,
|
||||
maxWidth: 40,
|
||||
data: "string",
|
||||
onRender: (g: SourceGroup) => <>
|
||||
{g.isMultiple ? "分组" : "订阅源"}
|
||||
{g.isMultiple ? intl.get("groups.group") : intl.get("groups.source")}
|
||||
</>
|
||||
},
|
||||
{
|
||||
key: "capacity",
|
||||
name: "容量",
|
||||
name: intl.get("groups.capacity"),
|
||||
minWidth: 40,
|
||||
maxWidth: 40,
|
||||
maxWidth: 60,
|
||||
data: "string",
|
||||
onRender: (g: SourceGroup) => <>
|
||||
{g.isMultiple ? g.sids.length : ""}
|
||||
@ -92,7 +93,7 @@ class GroupsTab extends React.Component<GroupsTabProps, GroupsTabState> {
|
||||
},
|
||||
{
|
||||
key: "name",
|
||||
name: "名称",
|
||||
name: intl.get("name"),
|
||||
minWidth: 200,
|
||||
data: "string",
|
||||
isRowHeader: true,
|
||||
@ -105,7 +106,7 @@ class GroupsTab extends React.Component<GroupsTabProps, GroupsTabState> {
|
||||
sourceColumns: IColumn[] = [
|
||||
{
|
||||
key: "favicon",
|
||||
name: "图标",
|
||||
name: intl.get("icon"),
|
||||
fieldName: "name",
|
||||
isIconOnly: true,
|
||||
iconName: "ImagePixel",
|
||||
@ -117,7 +118,7 @@ class GroupsTab extends React.Component<GroupsTabProps, GroupsTabState> {
|
||||
},
|
||||
{
|
||||
key: "name",
|
||||
name: "名称",
|
||||
name: intl.get("name"),
|
||||
fieldName: "name",
|
||||
minWidth: 200,
|
||||
data: 'string',
|
||||
@ -252,11 +253,11 @@ class GroupsTab extends React.Component<GroupsTabProps, GroupsTabState> {
|
||||
<>
|
||||
<Stack horizontal horizontalAlign="space-between" style={{height: 40}}>
|
||||
<CommandBarButton
|
||||
text="退出分组"
|
||||
text={intl.get("groups.exitGroup")}
|
||||
iconProps={{iconName: "BackToWindow"}}
|
||||
onClick={() => this.setState({manageGroup: false})} />
|
||||
{this.state.selectedSources != null && <CommandBarButton
|
||||
text="从分组删除订阅源"
|
||||
text={intl.get("groups.deleteSource")}
|
||||
onClick={this.removeFromGroup}
|
||||
iconProps={{iconName: "RemoveFromShoppingList", style: {color: "#d13438"}}} />}
|
||||
</Stack>
|
||||
@ -272,18 +273,18 @@ class GroupsTab extends React.Component<GroupsTabProps, GroupsTabState> {
|
||||
selectionMode={SelectionMode.multiple} />
|
||||
</MarqueeSelection>
|
||||
|
||||
<span className="settings-hint">拖拽订阅源以排序</span>
|
||||
<span className="settings-hint">{intl.get("groups.sourceHint")}</span>
|
||||
</>}
|
||||
{(!this.state.manageGroup || !this.state.selectedGroup)
|
||||
?<>
|
||||
<form onSubmit={this.createGroup}>
|
||||
<Label htmlFor="newGroupName">新建分组</Label>
|
||||
<Label htmlFor="newGroupName">{intl.get("groups.create")}</Label>
|
||||
<Stack horizontal>
|
||||
<Stack.Item grow>
|
||||
<TextField
|
||||
onGetErrorMessage={v => v.trim().length == 0 ? "名称不得为空" : ""}
|
||||
onGetErrorMessage={v => v.trim().length == 0 ? intl.get("emptyName") : ""}
|
||||
validateOnLoad={false}
|
||||
placeholder="输入名称"
|
||||
placeholder={intl.get("groups.enterName")}
|
||||
value={this.state.newGroupName}
|
||||
id="newGroupName"
|
||||
name="newGroupName"
|
||||
@ -293,7 +294,7 @@ class GroupsTab extends React.Component<GroupsTabProps, GroupsTabState> {
|
||||
<PrimaryButton
|
||||
disabled={this.state.newGroupName.length == 0}
|
||||
type="sumbit"
|
||||
text="新建" />
|
||||
text={intl.get("create")} />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</form>
|
||||
@ -301,7 +302,7 @@ class GroupsTab extends React.Component<GroupsTabProps, GroupsTabState> {
|
||||
<DetailsList
|
||||
compact={true}
|
||||
items={this.props.groups}
|
||||
columns={this.groupColumns}
|
||||
columns={this.groupColumns()}
|
||||
setKey="selected"
|
||||
onItemInvoked={this.manageGroup}
|
||||
dragDropEvents={this.groupDragDropEvents}
|
||||
@ -311,13 +312,13 @@ class GroupsTab extends React.Component<GroupsTabProps, GroupsTabState> {
|
||||
{this.state.selectedGroup
|
||||
? ( this.state.selectedGroup.isMultiple
|
||||
?<>
|
||||
<Label>选中分组</Label>
|
||||
<Label>{intl.get("groups.selectedGroup")}</Label>
|
||||
<Stack horizontal>
|
||||
<Stack.Item grow>
|
||||
<TextField
|
||||
onGetErrorMessage={v => v.trim().length == 0 ? "名称不得为空" : ""}
|
||||
onGetErrorMessage={v => v.trim().length == 0 ? intl.get("emptyName") : ""}
|
||||
validateOnLoad={false}
|
||||
placeholder="分组名称"
|
||||
placeholder={intl.get("groups.enterName")}
|
||||
value={this.state.editGroupName}
|
||||
name="editGroupName"
|
||||
onChange={this.handleInputChange} />
|
||||
@ -326,22 +327,22 @@ class GroupsTab extends React.Component<GroupsTabProps, GroupsTabState> {
|
||||
<DefaultButton
|
||||
disabled={this.state.editGroupName.length == 0}
|
||||
onClick={this.updateGroupName}
|
||||
text="修改名称" />
|
||||
text={intl.get("groups.editName")} />
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<DangerButton
|
||||
key={this.state.selectedGroup.index}
|
||||
onClick={this.deleteGroup}
|
||||
text={`删除分组`} />
|
||||
text={intl.get("groups.deleteGroup")} />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</>
|
||||
:<>
|
||||
<Label>选中订阅源</Label>
|
||||
<Label>{intl.get("groups.selectedSource")}</Label>
|
||||
<Stack horizontal>
|
||||
<Stack.Item grow>
|
||||
<Dropdown
|
||||
placeholder="选择分组"
|
||||
placeholder={intl.get("groups.chooseGroup")}
|
||||
selectedKey={this.state.dropdownIndex}
|
||||
options={this.dropdownOptions()}
|
||||
onChange={this.dropdownChange} />
|
||||
@ -350,12 +351,12 @@ class GroupsTab extends React.Component<GroupsTabProps, GroupsTabState> {
|
||||
<DefaultButton
|
||||
disabled={this.state.dropdownIndex === null}
|
||||
onClick={this.addToGroup}
|
||||
text="添加至分组" />
|
||||
text={intl.get("groups.addToGroup")} />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</>
|
||||
)
|
||||
: <span className="settings-hint">双击分组以修改订阅源,可通过拖拽排序</span>
|
||||
: <span className="settings-hint">{intl.get("groups.groupHint")}</span>
|
||||
}
|
||||
</> : null}
|
||||
</div>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import * as React from "react"
|
||||
import intl = require("react-intl-universal")
|
||||
import { PrimaryButton } from "@fluentui/react";
|
||||
|
||||
class DangerButton extends PrimaryButton {
|
||||
@ -35,7 +36,7 @@ class DangerButton extends PrimaryButton {
|
||||
{...this.props}
|
||||
className={this.props.className + " danger"}
|
||||
onClick={this.onClick}
|
||||
text={this.state.confirming ? `确认${this.props.text}?` : this.props.text}
|
||||
text={this.state.confirming ? intl.get("dangerButton", { action: this.props.text.toLowerCase() }) : this.props.text}
|
||||
>
|
||||
{this.props.children}
|
||||
</PrimaryButton>
|
||||
|
@ -13,13 +13,15 @@ type ArticleContainerProps = {
|
||||
|
||||
const getItem = (state: RootState, props: ArticleContainerProps) => state.items[props.itemId]
|
||||
const getSource = (state: RootState, props: ArticleContainerProps) => state.sources[state.items[props.itemId].source]
|
||||
const getLocale = (state: RootState) => state.app.locale
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
return createSelector(
|
||||
[getItem, getSource],
|
||||
(item, source) => ({
|
||||
[getItem, getSource, getLocale],
|
||||
(item, source, locale) => ({
|
||||
item: item,
|
||||
source: source
|
||||
source: source,
|
||||
locale: locale
|
||||
})
|
||||
)
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import intl = require("react-intl-universal")
|
||||
import { remote } from "electron"
|
||||
import { connect } from "react-redux"
|
||||
import { createSelector } from "reselect"
|
||||
@ -30,7 +31,7 @@ const mapDispatchToProps = (dispatch: AppDispatch) => {
|
||||
let path = remote.dialog.showOpenDialogSync(
|
||||
remote.getCurrentWindow(),
|
||||
{
|
||||
filters: [{ name: "OPML文件", extensions: ["xml", "opml"] }],
|
||||
filters: [{ name: intl.get("sources.opmlFile"), extensions: ["xml", "opml"] }],
|
||||
properties: ["openFile"]
|
||||
}
|
||||
)
|
||||
|
@ -5,8 +5,68 @@
|
||||
"icon": "Icon",
|
||||
"name": "Name",
|
||||
"openExternal": "Open externally",
|
||||
"emptyName": "This field cannot be empty",
|
||||
"emptyName": "This field cannot be empty.",
|
||||
"followSystem": "Follow system",
|
||||
"more": "More",
|
||||
"close": "Close",
|
||||
"search": "Search",
|
||||
"loadMore": "Load more",
|
||||
"dangerButton": "Confirm {action}?",
|
||||
"log": {
|
||||
"empty": "No notifications",
|
||||
"fetchFailure": "Failed to load source \"{name}\".",
|
||||
"fetchSuccess": "Successfully fetched {count, plural, =1 {# article} other {# articles}}."
|
||||
},
|
||||
"nav": {
|
||||
"menu": "Menu",
|
||||
"refresh": "Refresh",
|
||||
"markAllRead": "Mark all as read",
|
||||
"notifications": "Notifications",
|
||||
"view": "View",
|
||||
"settings": "Settings",
|
||||
"minimize": "Minimize",
|
||||
"maximize": "Maximize"
|
||||
},
|
||||
"menu": {
|
||||
"close": "Close menu",
|
||||
"subscriptions": "Subscriptions"
|
||||
},
|
||||
"article": {
|
||||
"hide": "Hide article",
|
||||
"unhide": "Unhide article",
|
||||
"markRead": "Mark as read",
|
||||
"markUnread": "Mark as unread",
|
||||
"star": "Star",
|
||||
"unstar": "Remove star",
|
||||
"fontSize": "Font size",
|
||||
"loadWebpage": "Load webpage"
|
||||
},
|
||||
"context": {
|
||||
"read": "Read",
|
||||
"copyTitle": "Copy title",
|
||||
"copyURL": "Copy link",
|
||||
"copy": "Copy",
|
||||
"search": "Search \"{text}\" on Google",
|
||||
"view": "View",
|
||||
"cardView": "Card view",
|
||||
"listView": "List view",
|
||||
"filter": "Filtering",
|
||||
"unreadOnly": "Unread only",
|
||||
"starredOnly": "Starred only",
|
||||
"showHidden": "Show hidden articles"
|
||||
},
|
||||
"settings": {
|
||||
"name": "Settings",
|
||||
"fetching": "Updating sources, please wait …",
|
||||
"exit": "Exit settings",
|
||||
"sources": "Sources",
|
||||
"grouping": "Grouping",
|
||||
"app": "Preferences",
|
||||
"about": "About",
|
||||
"version": "Version",
|
||||
"openSource": "Open source",
|
||||
"feedback": "Feedback"
|
||||
},
|
||||
"sources": {
|
||||
"opmlFile": "OPML File",
|
||||
"name": "Source name",
|
||||
@ -20,9 +80,27 @@
|
||||
"loadWebpage": "Load webpage",
|
||||
"inputUrl": "Enter URL",
|
||||
"badUrl": "Invalid URL",
|
||||
"deleteWarning": "The source and all saved articles will be removed",
|
||||
"deleteWarning": "The source and all saved articles will be removed.",
|
||||
"selected": "Selected source"
|
||||
},
|
||||
"groups": {
|
||||
"type": "Type",
|
||||
"group": "Group",
|
||||
"source": "Source",
|
||||
"capacity": "Capacity",
|
||||
"exitGroup": "Back to groups",
|
||||
"deleteSource": "Delete from group",
|
||||
"sourceHint": "Drag and drop sources to reorder.",
|
||||
"create": "Create group",
|
||||
"selectedGroup": "Selected group",
|
||||
"selectedSource": "Selected source",
|
||||
"enterName": "Enter name",
|
||||
"editName": "Edit name",
|
||||
"deleteGroup": "Delete group",
|
||||
"chooseGroup": "Select a group",
|
||||
"addToGroup": "Add to ...",
|
||||
"groupHint": "Double click on group to edit sources. Drag and drop to reorder."
|
||||
},
|
||||
"app": {
|
||||
"language": "Display language",
|
||||
"theme": "Theme",
|
||||
|
@ -7,6 +7,66 @@
|
||||
"openExternal": "在浏览器中打开",
|
||||
"emptyName": "名称不得为空",
|
||||
"followSystem": "跟随系统",
|
||||
"more": "更多",
|
||||
"close": "关闭",
|
||||
"search": "搜索",
|
||||
"loadMore": "加载更多",
|
||||
"dangerButton": "确认{action}?",
|
||||
"log": {
|
||||
"empty": "无消息",
|
||||
"fetchFailure": "无法加载订阅源“{name}”",
|
||||
"fetchSuccess": "成功加载 {count} 篇文章"
|
||||
},
|
||||
"nav": {
|
||||
"menu": "菜单",
|
||||
"refresh": "刷新",
|
||||
"markAllRead": "全部标为已读",
|
||||
"notifications": "消息",
|
||||
"view": "视图",
|
||||
"settings": "选项",
|
||||
"minimize": "最小化",
|
||||
"maximize": "最大化"
|
||||
},
|
||||
"menu": {
|
||||
"close": "关闭菜单",
|
||||
"subscriptions": "订阅源"
|
||||
},
|
||||
"article": {
|
||||
"hide": "隐藏文章",
|
||||
"unhide": "取消隐藏",
|
||||
"markRead": "标为已读",
|
||||
"markUnread": "标为未读",
|
||||
"star": "标为星标",
|
||||
"unstar": "取消星标",
|
||||
"fontSize": "字体大小",
|
||||
"loadWebpage": "加载网页"
|
||||
},
|
||||
"context": {
|
||||
"read": "阅读",
|
||||
"copyTitle": "复制标题",
|
||||
"copyURL": "复制链接",
|
||||
"copy": "复制",
|
||||
"search": "使用Google搜索“{text}”",
|
||||
"view": "视图",
|
||||
"cardView": "卡片视图",
|
||||
"listView": "列表视图",
|
||||
"filter": "筛选",
|
||||
"unreadOnly": "仅未读文章",
|
||||
"starredOnly": "仅星标文章",
|
||||
"showHidden": "显示隐藏文章"
|
||||
},
|
||||
"settings": {
|
||||
"name": "选项",
|
||||
"fetching": "正在更新订阅源,请稍候…",
|
||||
"exit": "退出选项",
|
||||
"sources": "订阅源",
|
||||
"grouping": "分组与排序",
|
||||
"app": "应用偏好",
|
||||
"about": "关于",
|
||||
"version": "版本",
|
||||
"openSource": "开源项目",
|
||||
"feedback": "反馈"
|
||||
},
|
||||
"sources": {
|
||||
"opmlFile": "OPML文件",
|
||||
"name": "订阅源名称",
|
||||
@ -23,6 +83,24 @@
|
||||
"deleteWarning": "这将移除此订阅源与所有已保存的文章",
|
||||
"selected": "选中订阅源"
|
||||
},
|
||||
"groups": {
|
||||
"type": "类型",
|
||||
"group": "分组",
|
||||
"source": "订阅源",
|
||||
"capacity": "容量",
|
||||
"exitGroup": "退出分组",
|
||||
"deleteSource": "从分组删除订阅源",
|
||||
"sourceHint": "拖拽订阅源以排序",
|
||||
"create": "新建分组",
|
||||
"selectedGroup": "选中分组",
|
||||
"selectedSource": "选中订阅源",
|
||||
"enterName": "输入名称",
|
||||
"editName": "修改名称",
|
||||
"deleteGroup": "删除分组",
|
||||
"chooseGroup": "选择分组",
|
||||
"addToGroup": "添加至分组",
|
||||
"groupHint": "双击分组以修改订阅源,可通过拖拽排序"
|
||||
},
|
||||
"app": {
|
||||
"language": "界面语言",
|
||||
"theme": "应用主题",
|
||||
|
@ -39,7 +39,7 @@ export class AppState {
|
||||
fetchingTotal = 0
|
||||
menu = getWindowBreakpoint()
|
||||
menuKey = ALL
|
||||
title = "全部文章"
|
||||
title = ""
|
||||
settings = {
|
||||
display: false,
|
||||
changed: false,
|
||||
@ -163,21 +163,27 @@ export const initIntlDone = (locale: string): InitIntlAction => ({
|
||||
locale: locale
|
||||
})
|
||||
|
||||
export function initIntl(): AppThunk {
|
||||
export function initIntl(): AppThunk<Promise<void>> {
|
||||
return (dispatch) => {
|
||||
let locale = getCurrentLocale()
|
||||
intl.init({
|
||||
return intl.init({
|
||||
currentLocale: locale,
|
||||
locales: locales,
|
||||
fallbackLocale: "zh-CN"
|
||||
}).then(() => dispatch(initIntlDone(locale)))
|
||||
}).then(() => { dispatch(initIntlDone(locale)) })
|
||||
}
|
||||
}
|
||||
|
||||
export function initApp(): AppThunk {
|
||||
return (dispatch) => {
|
||||
dispatch(initSources()).then(() => dispatch(initFeeds())).then(() => dispatch(fetchItems()))
|
||||
dispatch(initIntl())
|
||||
dispatch(initIntl()).then(() =>
|
||||
dispatch(initSources())
|
||||
).then(() =>
|
||||
dispatch(initFeeds())
|
||||
).then(() => {
|
||||
dispatch(selectAllArticles())
|
||||
dispatch(fetchItems())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -257,7 +263,7 @@ export function appReducer(
|
||||
notify: !state.logMenu.display,
|
||||
logs: [...state.logMenu.logs, new AppLog(
|
||||
AppLogType.Failure,
|
||||
`无法加载订阅源“${action.errSource.name}”`,
|
||||
intl.get("log.fetchFailure", { name: action.errSource.name }),
|
||||
String(action.err)
|
||||
)]
|
||||
}
|
||||
@ -270,7 +276,7 @@ export function appReducer(
|
||||
...state.logMenu,
|
||||
logs: [...state.logMenu.logs, new AppLog(
|
||||
AppLogType.Info,
|
||||
`成功加载 ${action.items.length} 篇文章`
|
||||
intl.get("log.fetchSuccess", { count: action.items.length })
|
||||
)]
|
||||
}
|
||||
}
|
||||
@ -286,7 +292,7 @@ export function appReducer(
|
||||
...state,
|
||||
menu: state.menu && action.keepMenu,
|
||||
menuKey: ALL,
|
||||
title: "全部文章"
|
||||
title: intl.get("allArticles")
|
||||
}
|
||||
case PageType.Sources: return {
|
||||
...state,
|
||||
|
Loading…
x
Reference in New Issue
Block a user