english translation

This commit is contained in:
刘浩远 2020-06-11 21:24:18 +08:00
parent 34b281f59a
commit a0d5e41bf2
16 changed files with 310 additions and 99 deletions

View File

@ -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>

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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 }} />
))) }

View File

@ -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()} />

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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
})
)
}

View File

@ -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"]
}
)

View File

@ -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",

View File

@ -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": "应用主题",

View File

@ -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,