mirror of
https://github.com/yang991178/fluent-reader.git
synced 2025-04-14 02:12:18 +02:00
add partial intl
This commit is contained in:
parent
ea75d5a893
commit
34b281f59a
4
dist/article/article.css
vendored
4
dist/article/article.css
vendored
@ -13,8 +13,8 @@ body {
|
|||||||
body.dark {
|
body.dark {
|
||||||
color: #f8f8f8;
|
color: #f8f8f8;
|
||||||
--gray: #a19f9d;
|
--gray: #a19f9d;
|
||||||
--primary: #3a96dd;
|
--primary: #4ba0e1;
|
||||||
--primary-alt: #4ba0e1;
|
--primary-alt: #65aee6;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
|
8
dist/styles.css
vendored
8
dist/styles.css
vendored
@ -36,6 +36,7 @@ body.dark {
|
|||||||
}
|
}
|
||||||
|
|
||||||
html, body {
|
html, body {
|
||||||
|
background-color: transparent;
|
||||||
font-family: "Segoe UI Regular", "Source Han Sans SC Regular", "Microsoft YaHei", sans-serif;
|
font-family: "Segoe UI Regular", "Source Han Sans SC Regular", "Microsoft YaHei", sans-serif;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -94,7 +95,7 @@ i.ms-Nav-chevron {
|
|||||||
.ms-Label, .ms-Spinner-label {
|
.ms-Label, .ms-Spinner-label {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
.ms-ActivityItem {
|
.ms-ActivityItem, .ms-ActivityItem-commentText {
|
||||||
color: var(--neutralSecondary);
|
color: var(--neutralSecondary);
|
||||||
}
|
}
|
||||||
.ms-ActivityItem-timeStamp {
|
.ms-ActivityItem-timeStamp {
|
||||||
@ -192,7 +193,7 @@ body.dark .btn-group .btn:active {
|
|||||||
background-color: #fff2;
|
background-color: #fff2;
|
||||||
}
|
}
|
||||||
.btn-group .btn.disabled, .btn-group .btn.fetching {
|
.btn-group .btn.disabled, .btn-group .btn.fetching {
|
||||||
background-color: unset;
|
background-color: unset !important;
|
||||||
color: var(--neutralSecondaryAlt);
|
color: var(--neutralSecondaryAlt);
|
||||||
}
|
}
|
||||||
.btn-group .btn.fetching {
|
.btn-group .btn.fetching {
|
||||||
@ -297,6 +298,9 @@ body.dark .settings .loading {
|
|||||||
.tab-body .ms-StackItem:last-child {
|
.tab-body .ms-StackItem:last-child {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
.tab-body .ms-ChoiceFieldGroup {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
img.favicon {
|
img.favicon {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
|
@ -62,6 +62,7 @@
|
|||||||
"pac-proxy-agent": "^4.1.0",
|
"pac-proxy-agent": "^4.1.0",
|
||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
"react-dom": "^16.13.1",
|
"react-dom": "^16.13.1",
|
||||||
|
"react-intl-universal": "^2.2.5",
|
||||||
"react-redux": "^7.2.0",
|
"react-redux": "^7.2.0",
|
||||||
"redux": "^4.0.5",
|
"redux": "^4.0.5",
|
||||||
"redux-devtools": "^3.5.0",
|
"redux-devtools": "^3.5.0",
|
||||||
|
@ -7,9 +7,11 @@ import MenuContainer from "../containers/menu-container"
|
|||||||
import NavContainer from "../containers/nav-container"
|
import NavContainer from "../containers/nav-container"
|
||||||
import LogMenuContainer from "../containers/log-menu-container"
|
import LogMenuContainer from "../containers/log-menu-container"
|
||||||
import SettingsContainer from "../containers/settings-container"
|
import SettingsContainer from "../containers/settings-container"
|
||||||
|
import { RootState } from "../scripts/reducer"
|
||||||
|
|
||||||
const Root = ({ dispatch }) => (
|
const Root = ({ locale, dispatch }) => locale && (
|
||||||
<div id="root"
|
<div id="root"
|
||||||
|
key={locale}
|
||||||
onMouseDown={() => dispatch(closeContextMenu())}
|
onMouseDown={() => dispatch(closeContextMenu())}
|
||||||
onContextMenu={event => {
|
onContextMenu={event => {
|
||||||
let text = document.getSelection().toString()
|
let text = document.getSelection().toString()
|
||||||
@ -24,4 +26,5 @@ const Root = ({ dispatch }) => (
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
export default connect()(Root)
|
const getLocale = (state: RootState) => ({ locale: state.app.locale })
|
||||||
|
export default connect(getLocale)(Root)
|
@ -5,7 +5,7 @@ import AboutTab from "./settings/about"
|
|||||||
import { Pivot, PivotItem, Spinner } from "@fluentui/react"
|
import { Pivot, PivotItem, Spinner } from "@fluentui/react"
|
||||||
import SourcesTabContainer from "../containers/settings/sources-container"
|
import SourcesTabContainer from "../containers/settings/sources-container"
|
||||||
import GroupsTabContainer from "../containers/settings/groups-container"
|
import GroupsTabContainer from "../containers/settings/groups-container"
|
||||||
import AppTab from "./settings/app"
|
import AppTabContainer from "../containers/settings/app-container"
|
||||||
|
|
||||||
type SettingsProps = {
|
type SettingsProps = {
|
||||||
display: boolean,
|
display: boolean,
|
||||||
@ -38,7 +38,7 @@ class Settings extends React.Component<SettingsProps> {
|
|||||||
<GroupsTabContainer />
|
<GroupsTabContainer />
|
||||||
</PivotItem>
|
</PivotItem>
|
||||||
<PivotItem headerText="应用选项" itemIcon="Settings">
|
<PivotItem headerText="应用选项" itemIcon="Settings">
|
||||||
<AppTab />
|
<AppTabContainer />
|
||||||
</PivotItem>
|
</PivotItem>
|
||||||
<PivotItem headerText="关于" itemIcon="Info">
|
<PivotItem headerText="关于" itemIcon="Info">
|
||||||
<AboutTab />
|
<AboutTab />
|
||||||
|
@ -1,20 +1,31 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
|
import intl = require("react-intl-universal")
|
||||||
import { urlTest } from "../../scripts/utils"
|
import { urlTest } from "../../scripts/utils"
|
||||||
import { getProxy, getProxyStatus, toggleProxyStatus, setProxy, getThemeSettings, setThemeSettings, ThemeSettings } from "../../scripts/settings"
|
import { getProxy, getProxyStatus, toggleProxyStatus, setProxy, getThemeSettings, setThemeSettings, ThemeSettings, getLocaleSettings } from "../../scripts/settings"
|
||||||
import { Stack, Label, Toggle, TextField, DefaultButton, ChoiceGroup, IChoiceGroupOption, loadTheme } from "@fluentui/react"
|
import { Stack, Label, Toggle, TextField, DefaultButton, ChoiceGroup, IChoiceGroupOption, loadTheme, Dropdown, IDropdownOption } from "@fluentui/react"
|
||||||
|
|
||||||
const themeChoices: IChoiceGroupOption[] = [
|
type AppTabProps = {
|
||||||
{ key: ThemeSettings.Default, text: "系统默认" },
|
setLanguage: (option: string) => void
|
||||||
{ key: ThemeSettings.Light, text: "浅色模式" },
|
}
|
||||||
{ key: ThemeSettings.Dark, text: "深色模式" }
|
|
||||||
]
|
|
||||||
|
|
||||||
class AppTab extends React.Component {
|
class AppTab extends React.Component<AppTabProps> {
|
||||||
state = {
|
state = {
|
||||||
pacStatus: getProxyStatus(),
|
pacStatus: getProxyStatus(),
|
||||||
pacUrl: getProxy(),
|
pacUrl: getProxy(),
|
||||||
themeSettings: getThemeSettings()
|
themeSettings: getThemeSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
themeChoices = (): IChoiceGroupOption[] => [
|
||||||
|
{ key: ThemeSettings.Default, text: intl.get("followSystem") },
|
||||||
|
{ key: ThemeSettings.Light, text: intl.get("app.lightTheme") },
|
||||||
|
{ key: ThemeSettings.Dark, text: intl.get("app.darkTheme") }
|
||||||
|
]
|
||||||
|
|
||||||
|
languageOptions = (): IDropdownOption[] => [
|
||||||
|
{ key: "default", text: intl.get("followSystem") },
|
||||||
|
{ key: "en-US", text: "English" },
|
||||||
|
{ key: "zh-CN", text: "中文(简体)"}
|
||||||
|
]
|
||||||
|
|
||||||
toggleStatus = () => {
|
toggleStatus = () => {
|
||||||
toggleProxyStatus()
|
toggleProxyStatus()
|
||||||
@ -41,9 +52,26 @@ class AppTab extends React.Component {
|
|||||||
|
|
||||||
render = () => (
|
render = () => (
|
||||||
<div className="tab-body">
|
<div className="tab-body">
|
||||||
|
<Label>{intl.get("app.language")}</Label>
|
||||||
|
<Stack horizontal>
|
||||||
|
<Stack.Item>
|
||||||
|
<Dropdown
|
||||||
|
defaultSelectedKey={getLocaleSettings()}
|
||||||
|
options={this.languageOptions()}
|
||||||
|
onChanged={option => this.props.setLanguage(String(option.key))}
|
||||||
|
style={{width: 200}} />
|
||||||
|
</Stack.Item>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<ChoiceGroup
|
||||||
|
label={intl.get("app.theme")}
|
||||||
|
options={this.themeChoices()}
|
||||||
|
onChange={this.onThemeChange}
|
||||||
|
selectedKey={this.state.themeSettings} />
|
||||||
|
|
||||||
<Stack horizontal verticalAlign="baseline">
|
<Stack horizontal verticalAlign="baseline">
|
||||||
<Stack.Item grow>
|
<Stack.Item grow>
|
||||||
<Label>启用代理</Label>
|
<Label>{intl.get("app.enableProxy")}</Label>
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
<Stack.Item>
|
<Stack.Item>
|
||||||
<Toggle checked={this.state.pacStatus} onChange={this.toggleStatus} />
|
<Toggle checked={this.state.pacStatus} onChange={this.toggleStatus} />
|
||||||
@ -54,8 +82,8 @@ class AppTab extends React.Component {
|
|||||||
<Stack.Item grow>
|
<Stack.Item grow>
|
||||||
<TextField
|
<TextField
|
||||||
required
|
required
|
||||||
onGetErrorMessage={v => urlTest(v.trim()) ? "" : "请正确输入URL"}
|
onGetErrorMessage={v => urlTest(v.trim()) ? "" : intl.get("app.badUrl")}
|
||||||
placeholder="PAC地址"
|
placeholder={intl.get("app.pac")}
|
||||||
name="pacUrl"
|
name="pacUrl"
|
||||||
onChange={this.handleInputChange}
|
onChange={this.handleInputChange}
|
||||||
value={this.state.pacUrl} />
|
value={this.state.pacUrl} />
|
||||||
@ -64,16 +92,10 @@ class AppTab extends React.Component {
|
|||||||
<DefaultButton
|
<DefaultButton
|
||||||
disabled={!urlTest(this.state.pacUrl)}
|
disabled={!urlTest(this.state.pacUrl)}
|
||||||
type="sumbit"
|
type="sumbit"
|
||||||
text="设置" />
|
text={intl.get("app.setPac")} />
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
</Stack>
|
</Stack>
|
||||||
</form>}
|
</form>}
|
||||||
|
|
||||||
<ChoiceGroup
|
|
||||||
label="应用主题"
|
|
||||||
options={themeChoices}
|
|
||||||
onChange={this.onThemeChange}
|
|
||||||
selectedKey={this.state.themeSettings} />
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
|
import intl = require("react-intl-universal")
|
||||||
import { Label, DefaultButton, TextField, Stack, PrimaryButton, DetailsList,
|
import { Label, DefaultButton, TextField, Stack, PrimaryButton, DetailsList,
|
||||||
IColumn, SelectionMode, Selection, IChoiceGroupOption, ChoiceGroup } from "@fluentui/react"
|
IColumn, SelectionMode, Selection, IChoiceGroupOption, ChoiceGroup } from "@fluentui/react"
|
||||||
import { SourceState, RSSSource, SourceOpenTarget } from "../../scripts/models/source"
|
import { SourceState, RSSSource, SourceOpenTarget } from "../../scripts/models/source"
|
||||||
@ -20,42 +21,6 @@ type SourcesTabState = {
|
|||||||
selectedSource: RSSSource
|
selectedSource: RSSSource
|
||||||
}
|
}
|
||||||
|
|
||||||
const columns: IColumn[] = [
|
|
||||||
{
|
|
||||||
key: "favicon",
|
|
||||||
name: "图标",
|
|
||||||
fieldName: "name",
|
|
||||||
isIconOnly: true,
|
|
||||||
iconName: "ImagePixel",
|
|
||||||
minWidth: 16,
|
|
||||||
maxWidth: 16,
|
|
||||||
onRender: (s: RSSSource) => s.iconurl && (
|
|
||||||
<img src={s.iconurl} className="favicon" />
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "name",
|
|
||||||
name: "名称",
|
|
||||||
fieldName: "name",
|
|
||||||
minWidth: 200,
|
|
||||||
data: 'string',
|
|
||||||
isRowHeader: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "url",
|
|
||||||
name: "URL",
|
|
||||||
fieldName: "url",
|
|
||||||
minWidth: 280,
|
|
||||||
data: 'string'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const sourceOpenTargetChoices: IChoiceGroupOption[] = [
|
|
||||||
{ key: String(SourceOpenTarget.Local), text: "RSS正文" },
|
|
||||||
{ key: String(SourceOpenTarget.Webpage), text: "加载网页" },
|
|
||||||
{ key: String(SourceOpenTarget.External), text: "在浏览器中打开" }
|
|
||||||
]
|
|
||||||
|
|
||||||
class SourcesTab extends React.Component<SourcesTabProps, SourcesTabState> {
|
class SourcesTab extends React.Component<SourcesTabProps, SourcesTabState> {
|
||||||
selection: Selection
|
selection: Selection
|
||||||
|
|
||||||
@ -78,6 +43,42 @@ class SourcesTab extends React.Component<SourcesTabProps, SourcesTabState> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
columns = (): IColumn[] => [
|
||||||
|
{
|
||||||
|
key: "favicon",
|
||||||
|
name: intl.get("icon"),
|
||||||
|
fieldName: "name",
|
||||||
|
isIconOnly: true,
|
||||||
|
iconName: "ImagePixel",
|
||||||
|
minWidth: 16,
|
||||||
|
maxWidth: 16,
|
||||||
|
onRender: (s: RSSSource) => s.iconurl && (
|
||||||
|
<img src={s.iconurl} className="favicon" />
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "name",
|
||||||
|
name: intl.get("name"),
|
||||||
|
fieldName: "name",
|
||||||
|
minWidth: 200,
|
||||||
|
data: 'string',
|
||||||
|
isRowHeader: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "url",
|
||||||
|
name: "URL",
|
||||||
|
fieldName: "url",
|
||||||
|
minWidth: 280,
|
||||||
|
data: 'string'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
sourceOpenTargetChoices = (): IChoiceGroupOption[] => [
|
||||||
|
{ key: String(SourceOpenTarget.Local), text: intl.get("sources.rssText") },
|
||||||
|
{ key: String(SourceOpenTarget.Webpage), text: intl.get("sources.loadWebpage") },
|
||||||
|
{ key: String(SourceOpenTarget.External), text: intl.get("openExternal") }
|
||||||
|
]
|
||||||
|
|
||||||
handleInputChange = (event) => {
|
handleInputChange = (event) => {
|
||||||
const name: string = event.target.name
|
const name: string = event.target.name
|
||||||
this.setState({[name]: event.target.value.trim()})
|
this.setState({[name]: event.target.value.trim()})
|
||||||
@ -96,24 +97,24 @@ class SourcesTab extends React.Component<SourcesTabProps, SourcesTabState> {
|
|||||||
|
|
||||||
render = () => (
|
render = () => (
|
||||||
<div className="tab-body">
|
<div className="tab-body">
|
||||||
<Label>OPML文件</Label>
|
<Label>{intl.get("sources.opmlFile")}</Label>
|
||||||
<Stack horizontal>
|
<Stack horizontal>
|
||||||
<Stack.Item>
|
<Stack.Item>
|
||||||
<PrimaryButton onClick={this.props.importOPML} text="导入文件" />
|
<PrimaryButton onClick={this.props.importOPML} text={intl.get("sources.import")} />
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
<Stack.Item>
|
<Stack.Item>
|
||||||
<DefaultButton text="导出文件" />
|
<DefaultButton text={intl.get("sources.export")} />
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<form onSubmit={this.addSource}>
|
<form onSubmit={this.addSource}>
|
||||||
<Label htmlFor="newUrl">添加订阅源</Label>
|
<Label htmlFor="newUrl">{intl.get("sources.add")}</Label>
|
||||||
<Stack horizontal>
|
<Stack horizontal>
|
||||||
<Stack.Item grow>
|
<Stack.Item grow>
|
||||||
<TextField
|
<TextField
|
||||||
onGetErrorMessage={v => urlTest(v.trim()) ? "" : "请正确输入URL"}
|
onGetErrorMessage={v => urlTest(v.trim()) ? "" : intl.get("sources.badUrl")}
|
||||||
validateOnLoad={false}
|
validateOnLoad={false}
|
||||||
placeholder="输入URL"
|
placeholder={intl.get("sources.inputUrl")}
|
||||||
value={this.state.newUrl}
|
value={this.state.newUrl}
|
||||||
id="newUrl"
|
id="newUrl"
|
||||||
name="newUrl"
|
name="newUrl"
|
||||||
@ -123,27 +124,27 @@ class SourcesTab extends React.Component<SourcesTabProps, SourcesTabState> {
|
|||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
disabled={!urlTest(this.state.newUrl)}
|
disabled={!urlTest(this.state.newUrl)}
|
||||||
type="submit"
|
type="submit"
|
||||||
text="添加" />
|
text={intl.get("add")} />
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
</Stack>
|
</Stack>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<DetailsList
|
<DetailsList
|
||||||
items={Object.values(this.props.sources)}
|
items={Object.values(this.props.sources)}
|
||||||
columns={columns}
|
columns={this.columns()}
|
||||||
getKey={s => s.sid}
|
getKey={s => s.sid}
|
||||||
setKey="selected"
|
setKey="selected"
|
||||||
selection={this.selection}
|
selection={this.selection}
|
||||||
selectionMode={SelectionMode.single} />
|
selectionMode={SelectionMode.single} />
|
||||||
|
|
||||||
{this.state.selectedSource && <>
|
{this.state.selectedSource && <>
|
||||||
<Label>选中订阅源</Label>
|
<Label>{intl.get("sources.selected")}</Label>
|
||||||
<Stack horizontal>
|
<Stack horizontal>
|
||||||
<Stack.Item grow>
|
<Stack.Item grow>
|
||||||
<TextField
|
<TextField
|
||||||
onGetErrorMessage={v => v.trim().length == 0 ? "名称不得为空" : ""}
|
onGetErrorMessage={v => v.trim().length == 0 ? intl.get("emptyName") : ""}
|
||||||
validateOnLoad={false}
|
validateOnLoad={false}
|
||||||
placeholder="订阅源名称"
|
placeholder={intl.get("sources.name")}
|
||||||
value={this.state.newSourceName}
|
value={this.state.newSourceName}
|
||||||
name="newSourceName"
|
name="newSourceName"
|
||||||
onChange={this.handleInputChange} />
|
onChange={this.handleInputChange} />
|
||||||
@ -152,23 +153,23 @@ class SourcesTab extends React.Component<SourcesTabProps, SourcesTabState> {
|
|||||||
<DefaultButton
|
<DefaultButton
|
||||||
disabled={this.state.newSourceName.length == 0}
|
disabled={this.state.newSourceName.length == 0}
|
||||||
onClick={() => this.props.updateSourceName(this.state.selectedSource, this.state.newSourceName)}
|
onClick={() => this.props.updateSourceName(this.state.selectedSource, this.state.newSourceName)}
|
||||||
text="修改名称" />
|
text={intl.get("sources.editName")} />
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
</Stack>
|
</Stack>
|
||||||
<ChoiceGroup
|
<ChoiceGroup
|
||||||
label="订阅源文章打开方式"
|
label={intl.get("sources.openTarget")}
|
||||||
options={sourceOpenTargetChoices}
|
options={this.sourceOpenTargetChoices()}
|
||||||
selectedKey={String(this.state.selectedSource.openTarget)}
|
selectedKey={String(this.state.selectedSource.openTarget)}
|
||||||
onChange={this.onOpenTargetChange} />
|
onChange={this.onOpenTargetChange} />
|
||||||
<Stack horizontal style={{marginTop: 24}}>
|
<Stack horizontal>
|
||||||
<Stack.Item>
|
<Stack.Item>
|
||||||
<DangerButton
|
<DangerButton
|
||||||
onClick={() => this.props.deleteSource(this.state.selectedSource)}
|
onClick={() => this.props.deleteSource(this.state.selectedSource)}
|
||||||
key={this.state.selectedSource.sid}
|
key={this.state.selectedSource.sid}
|
||||||
text={`删除订阅源`} />
|
text={intl.get("sources.delete")} />
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
<Stack.Item>
|
<Stack.Item>
|
||||||
<span className="settings-hint">这将移除此订阅源与所有已保存的文章</span>
|
<span className="settings-hint">{intl.get("sources.deleteWarning")}</span>
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
</Stack>
|
</Stack>
|
||||||
</>}
|
</>}
|
||||||
|
14
src/containers/settings/app-container.tsx
Normal file
14
src/containers/settings/app-container.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { connect } from "react-redux"
|
||||||
|
import { setLocaleSettings } from "../../scripts/settings"
|
||||||
|
import { initIntl } from "../../scripts/models/app"
|
||||||
|
import AppTab from "../../components/settings/app"
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
setLanguage: (option: string) => {
|
||||||
|
setLocaleSettings(option)
|
||||||
|
dispatch(initIntl())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const AppTabContainer = connect(null, mapDispatchToProps)(AppTab)
|
||||||
|
export default AppTabContainer
|
@ -1,9 +1,10 @@
|
|||||||
import { app, ipcMain, BrowserWindow, Menu, nativeTheme } from "electron"
|
import { app, ipcMain, BrowserWindow, Menu, nativeTheme } from "electron"
|
||||||
import windowStateKeeper = require("electron-window-state")
|
import windowStateKeeper = require("electron-window-state")
|
||||||
import Store = require('electron-store');
|
import Store = require("electron-store")
|
||||||
|
|
||||||
let mainWindow: BrowserWindow
|
let mainWindow: BrowserWindow
|
||||||
const store = new Store()
|
const store = new Store()
|
||||||
|
nativeTheme.themeSource = store.get("theme", "system")
|
||||||
|
|
||||||
function createWindow() {
|
function createWindow() {
|
||||||
let mainWindowState = windowStateKeeper({
|
let mainWindowState = windowStateKeeper({
|
||||||
@ -13,7 +14,7 @@ function createWindow() {
|
|||||||
// Create the browser window.
|
// Create the browser window.
|
||||||
mainWindow = new BrowserWindow({
|
mainWindow = new BrowserWindow({
|
||||||
title: "Fluent Reader",
|
title: "Fluent Reader",
|
||||||
backgroundColor: shouldUseDarkColors() ? "#282828" : "#faf9f8",
|
backgroundColor: nativeTheme.shouldUseDarkColors ? "#282828" : "#faf9f8",
|
||||||
x: mainWindowState.x,
|
x: mainWindowState.x,
|
||||||
y: mainWindowState.y,
|
y: mainWindowState.y,
|
||||||
width: mainWindowState.width,
|
width: mainWindowState.width,
|
||||||
@ -22,15 +23,20 @@ function createWindow() {
|
|||||||
minHeight: 600,
|
minHeight: 600,
|
||||||
frame: false,
|
frame: false,
|
||||||
fullscreenable: false,
|
fullscreenable: false,
|
||||||
|
show: false,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
nodeIntegration: true,
|
nodeIntegration: true,
|
||||||
webviewTag: true
|
webviewTag: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
mainWindowState.manage(mainWindow)
|
mainWindowState.manage(mainWindow)
|
||||||
|
mainWindow.on('ready-to-show', () => {
|
||||||
|
mainWindow.show()
|
||||||
|
mainWindow.focus()
|
||||||
|
mainWindow.webContents.openDevTools()
|
||||||
|
});
|
||||||
// and load the index.html of the app.
|
// and load the index.html of the app.
|
||||||
mainWindow.loadFile((app.isPackaged ? "dist/" : "") + 'index.html')
|
mainWindow.loadFile((app.isPackaged ? "dist/" : "") + 'index.html')
|
||||||
mainWindow.webContents.openDevTools()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Menu.setApplicationMenu(null)
|
Menu.setApplicationMenu(null)
|
||||||
@ -54,10 +60,3 @@ ipcMain.on("set-theme", (_, theme) => {
|
|||||||
store.set("theme", theme)
|
store.set("theme", theme)
|
||||||
nativeTheme.themeSource = theme
|
nativeTheme.themeSource = theme
|
||||||
})
|
})
|
||||||
|
|
||||||
function shouldUseDarkColors() {
|
|
||||||
let option = store.get("theme", "system")
|
|
||||||
return option === "system"
|
|
||||||
? nativeTheme.shouldUseDarkColors
|
|
||||||
: option === "dark"
|
|
||||||
}
|
|
@ -3,15 +3,12 @@ import * as ReactDOM from "react-dom"
|
|||||||
import { Provider } from "react-redux"
|
import { Provider } from "react-redux"
|
||||||
import { createStore, applyMiddleware } from "redux"
|
import { createStore, applyMiddleware } from "redux"
|
||||||
import thunkMiddleware from "redux-thunk"
|
import thunkMiddleware from "redux-thunk"
|
||||||
import { loadTheme } from '@fluentui/react'
|
|
||||||
import { initializeIcons } from "@fluentui/react/lib/Icons"
|
import { initializeIcons } from "@fluentui/react/lib/Icons"
|
||||||
import { rootReducer, RootState } from "./scripts/reducer"
|
import { rootReducer, RootState } from "./scripts/reducer"
|
||||||
import { initSources } from "./scripts/models/source"
|
|
||||||
import { fetchItems } from "./scripts/models/item"
|
|
||||||
import Root from "./components/root"
|
import Root from "./components/root"
|
||||||
import { initFeeds } from "./scripts/models/feed"
|
|
||||||
import { AppDispatch } from "./scripts/utils"
|
import { AppDispatch } from "./scripts/utils"
|
||||||
import { setProxy, applyThemeSettings } from "./scripts/settings"
|
import { setProxy, applyThemeSettings } from "./scripts/settings"
|
||||||
|
import { initApp } from "./scripts/models/app"
|
||||||
|
|
||||||
setProxy()
|
setProxy()
|
||||||
|
|
||||||
@ -23,7 +20,7 @@ const store = createStore(
|
|||||||
applyMiddleware<AppDispatch, RootState>(thunkMiddleware)
|
applyMiddleware<AppDispatch, RootState>(thunkMiddleware)
|
||||||
)
|
)
|
||||||
|
|
||||||
store.dispatch(initSources()).then(() => store.dispatch(initFeeds())).then(() => store.dispatch(fetchItems()))
|
store.dispatch(initApp())
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
|
9
src/scripts/i18n/_locales.ts
Normal file
9
src/scripts/i18n/_locales.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import en_US from "./en-US.json"
|
||||||
|
import zh_CN from "./zh-CN.json"
|
||||||
|
|
||||||
|
const locales = {
|
||||||
|
"en-US": en_US,
|
||||||
|
"zh-CN": zh_CN,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default locales
|
36
src/scripts/i18n/en-US.json
Normal file
36
src/scripts/i18n/en-US.json
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"allArticles": "All articles",
|
||||||
|
"add": "Add",
|
||||||
|
"create": "Create",
|
||||||
|
"icon": "Icon",
|
||||||
|
"name": "Name",
|
||||||
|
"openExternal": "Open externally",
|
||||||
|
"emptyName": "This field cannot be empty",
|
||||||
|
"followSystem": "Follow system",
|
||||||
|
"sources": {
|
||||||
|
"opmlFile": "OPML File",
|
||||||
|
"name": "Source name",
|
||||||
|
"editName": "Edit name",
|
||||||
|
"openTarget": "Default open target for articles",
|
||||||
|
"delete": "Delete source",
|
||||||
|
"add": "Add source",
|
||||||
|
"import": "Import",
|
||||||
|
"export": "Export",
|
||||||
|
"rssText": "RSS full text",
|
||||||
|
"loadWebpage": "Load webpage",
|
||||||
|
"inputUrl": "Enter URL",
|
||||||
|
"badUrl": "Invalid URL",
|
||||||
|
"deleteWarning": "The source and all saved articles will be removed",
|
||||||
|
"selected": "Selected source"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"language": "Display language",
|
||||||
|
"theme": "Theme",
|
||||||
|
"lightTheme": "Light mode",
|
||||||
|
"darkTheme": "Dark mode",
|
||||||
|
"enableProxy": "Enable Proxy",
|
||||||
|
"badUrl": "Invalid URL",
|
||||||
|
"pac": "PAC Address",
|
||||||
|
"setPac": "Set PAC"
|
||||||
|
}
|
||||||
|
}
|
36
src/scripts/i18n/zh-CN.json
Normal file
36
src/scripts/i18n/zh-CN.json
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"allArticles": "全部文章",
|
||||||
|
"add": "添加",
|
||||||
|
"create": "新建",
|
||||||
|
"icon": "图标",
|
||||||
|
"name": "名称",
|
||||||
|
"openExternal": "在浏览器中打开",
|
||||||
|
"emptyName": "名称不得为空",
|
||||||
|
"followSystem": "跟随系统",
|
||||||
|
"sources": {
|
||||||
|
"opmlFile": "OPML文件",
|
||||||
|
"name": "订阅源名称",
|
||||||
|
"editName": "修改名称",
|
||||||
|
"openTarget": "订阅源文章打开方式",
|
||||||
|
"delete": "删除订阅源",
|
||||||
|
"add": "添加订阅源",
|
||||||
|
"import": "导入文件",
|
||||||
|
"export": "导出文件",
|
||||||
|
"rssText": "RSS正文",
|
||||||
|
"loadWebpage": "加载网页",
|
||||||
|
"inputUrl": "输入URL",
|
||||||
|
"badUrl": "请正确输入URL",
|
||||||
|
"deleteWarning": "这将移除此订阅源与所有已保存的文章",
|
||||||
|
"selected": "选中订阅源"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"language": "界面语言",
|
||||||
|
"theme": "应用主题",
|
||||||
|
"lightTheme": "浅色模式",
|
||||||
|
"darkTheme": "深色模式",
|
||||||
|
"enableProxy": "启用代理",
|
||||||
|
"badUrl": "请正确输入URL",
|
||||||
|
"pac": "PAC地址",
|
||||||
|
"setPac": "设置PAC"
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,12 @@
|
|||||||
import { RSSSource, INIT_SOURCES, SourceActionTypes, ADD_SOURCE, UPDATE_SOURCE, DELETE_SOURCE } from "./source"
|
import intl = require("react-intl-universal")
|
||||||
import { RSSItem, ItemActionTypes, FETCH_ITEMS } from "./item"
|
import { RSSSource, INIT_SOURCES, SourceActionTypes, ADD_SOURCE, UPDATE_SOURCE, DELETE_SOURCE, initSources } from "./source"
|
||||||
|
import { RSSItem, ItemActionTypes, FETCH_ITEMS, fetchItems } from "./item"
|
||||||
import { ActionStatus, AppThunk, getWindowBreakpoint } from "../utils"
|
import { ActionStatus, AppThunk, getWindowBreakpoint } from "../utils"
|
||||||
import { INIT_FEEDS, FeedActionTypes, ALL, initFeeds } from "./feed"
|
import { INIT_FEEDS, FeedActionTypes, ALL, initFeeds } from "./feed"
|
||||||
import { SourceGroupActionTypes, UPDATE_SOURCE_GROUP, ADD_SOURCE_TO_GROUP, DELETE_SOURCE_GROUP, REMOVE_SOURCE_FROM_GROUP } from "./group"
|
import { SourceGroupActionTypes, UPDATE_SOURCE_GROUP, ADD_SOURCE_TO_GROUP, DELETE_SOURCE_GROUP, REMOVE_SOURCE_FROM_GROUP } from "./group"
|
||||||
import { PageActionTypes, SELECT_PAGE, PageType, selectAllArticles } from "./page"
|
import { PageActionTypes, SELECT_PAGE, PageType, selectAllArticles } from "./page"
|
||||||
|
import { getCurrentLocale, setLocaleSettings } from "../settings"
|
||||||
|
import locales from "../i18n/_locales"
|
||||||
|
|
||||||
export enum ContextMenuType {
|
export enum ContextMenuType {
|
||||||
Hidden, Item, Text, View
|
Hidden, Item, Text, View
|
||||||
@ -28,6 +31,7 @@ export class AppLog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class AppState {
|
export class AppState {
|
||||||
|
locale = null as string
|
||||||
sourceInit = false
|
sourceInit = false
|
||||||
feedInit = false
|
feedInit = false
|
||||||
fetchingItems = false
|
fetchingItems = false
|
||||||
@ -149,12 +153,44 @@ export function exitSettings(): AppThunk {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const INIT_INTL = "INIT_INTL"
|
||||||
|
export interface InitIntlAction {
|
||||||
|
type: typeof INIT_INTL
|
||||||
|
locale: string
|
||||||
|
}
|
||||||
|
export const initIntlDone = (locale: string): InitIntlAction => ({
|
||||||
|
type: INIT_INTL,
|
||||||
|
locale: locale
|
||||||
|
})
|
||||||
|
|
||||||
|
export function initIntl(): AppThunk {
|
||||||
|
return (dispatch) => {
|
||||||
|
let locale = getCurrentLocale()
|
||||||
|
intl.init({
|
||||||
|
currentLocale: locale,
|
||||||
|
locales: locales,
|
||||||
|
fallbackLocale: "zh-CN"
|
||||||
|
}).then(() => dispatch(initIntlDone(locale)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initApp(): AppThunk {
|
||||||
|
return (dispatch) => {
|
||||||
|
dispatch(initSources()).then(() => dispatch(initFeeds())).then(() => dispatch(fetchItems()))
|
||||||
|
dispatch(initIntl())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function appReducer(
|
export function appReducer(
|
||||||
state = new AppState(),
|
state = new AppState(),
|
||||||
action: SourceActionTypes | ItemActionTypes | ContextMenuActionTypes | SettingsActionTypes
|
action: SourceActionTypes | ItemActionTypes | ContextMenuActionTypes | SettingsActionTypes | InitIntlAction
|
||||||
| MenuActionTypes | LogMenuActionType | FeedActionTypes | PageActionTypes | SourceGroupActionTypes
|
| MenuActionTypes | LogMenuActionType | FeedActionTypes | PageActionTypes | SourceGroupActionTypes
|
||||||
): AppState {
|
): AppState {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
case INIT_INTL: return {
|
||||||
|
...state,
|
||||||
|
locale: action.locale
|
||||||
|
}
|
||||||
case INIT_SOURCES:
|
case INIT_SOURCES:
|
||||||
switch (action.status) {
|
switch (action.status) {
|
||||||
case ActionStatus.Success: return {
|
case ActionStatus.Success: return {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { remote, ipcRenderer } from "electron"
|
import { remote, ipcRenderer } from "electron"
|
||||||
import { ViewType } from "./models/page"
|
import { ViewType } from "./models/page"
|
||||||
import { IPartialTheme, loadTheme } from "@fluentui/react"
|
import { IPartialTheme, loadTheme } from "@fluentui/react"
|
||||||
|
import locales from "./i18n/_locales"
|
||||||
|
|
||||||
const PAC_STORE_KEY = "PAC"
|
const PAC_STORE_KEY = "PAC"
|
||||||
const PAC_STATUS_KEY = "PAC_ON"
|
const PAC_STATUS_KEY = "PAC_ON"
|
||||||
@ -97,6 +98,20 @@ export function applyThemeSettings() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const LOCALE_STORE_KEY = "locale"
|
||||||
|
export function setLocaleSettings(option: string) {
|
||||||
|
localStorage.setItem(LOCALE_STORE_KEY, option)
|
||||||
|
}
|
||||||
|
export function getLocaleSettings() {
|
||||||
|
let stored = localStorage.getItem(LOCALE_STORE_KEY)
|
||||||
|
return stored === null ? "default" : stored
|
||||||
|
}
|
||||||
|
export function getCurrentLocale() {
|
||||||
|
let set = getLocaleSettings()
|
||||||
|
let locale = set === "default" ? remote.app.getLocale() : set
|
||||||
|
return (locale in locales) ? locale : "en-US"
|
||||||
|
}
|
||||||
|
|
||||||
export const STORE_KEYS = [
|
export const STORE_KEYS = [
|
||||||
PAC_STORE_KEY, PAC_STATUS_KEY, VIEW_STORE_KEY, THEME_STORE_KEY
|
PAC_STORE_KEY, PAC_STATUS_KEY, VIEW_STORE_KEY, THEME_STORE_KEY
|
||||||
]
|
]
|
@ -1,6 +1,8 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
"target": "ES2019",
|
"target": "ES2019",
|
||||||
"module": "CommonJS"
|
"module": "CommonJS"
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user