mirror of
https://github.com/yang991178/fluent-reader.git
synced 2025-02-17 20:21:05 +01: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 {
|
||||
color: #f8f8f8;
|
||||
--gray: #a19f9d;
|
||||
--primary: #3a96dd;
|
||||
--primary-alt: #4ba0e1;
|
||||
--primary: #4ba0e1;
|
||||
--primary-alt: #65aee6;
|
||||
}
|
||||
|
||||
a {
|
||||
|
8
dist/styles.css
vendored
8
dist/styles.css
vendored
@ -36,6 +36,7 @@ body.dark {
|
||||
}
|
||||
|
||||
html, body {
|
||||
background-color: transparent;
|
||||
font-family: "Segoe UI Regular", "Source Han Sans SC Regular", "Microsoft YaHei", sans-serif;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
@ -94,7 +95,7 @@ i.ms-Nav-chevron {
|
||||
.ms-Label, .ms-Spinner-label {
|
||||
user-select: none;
|
||||
}
|
||||
.ms-ActivityItem {
|
||||
.ms-ActivityItem, .ms-ActivityItem-commentText {
|
||||
color: var(--neutralSecondary);
|
||||
}
|
||||
.ms-ActivityItem-timeStamp {
|
||||
@ -192,7 +193,7 @@ body.dark .btn-group .btn:active {
|
||||
background-color: #fff2;
|
||||
}
|
||||
.btn-group .btn.disabled, .btn-group .btn.fetching {
|
||||
background-color: unset;
|
||||
background-color: unset !important;
|
||||
color: var(--neutralSecondaryAlt);
|
||||
}
|
||||
.btn-group .btn.fetching {
|
||||
@ -297,6 +298,9 @@ body.dark .settings .loading {
|
||||
.tab-body .ms-StackItem:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
.tab-body .ms-ChoiceFieldGroup {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
img.favicon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
|
@ -62,6 +62,7 @@
|
||||
"pac-proxy-agent": "^4.1.0",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-intl-universal": "^2.2.5",
|
||||
"react-redux": "^7.2.0",
|
||||
"redux": "^4.0.5",
|
||||
"redux-devtools": "^3.5.0",
|
||||
|
@ -7,9 +7,11 @@ import MenuContainer from "../containers/menu-container"
|
||||
import NavContainer from "../containers/nav-container"
|
||||
import LogMenuContainer from "../containers/log-menu-container"
|
||||
import SettingsContainer from "../containers/settings-container"
|
||||
import { RootState } from "../scripts/reducer"
|
||||
|
||||
const Root = ({ dispatch }) => (
|
||||
const Root = ({ locale, dispatch }) => locale && (
|
||||
<div id="root"
|
||||
key={locale}
|
||||
onMouseDown={() => dispatch(closeContextMenu())}
|
||||
onContextMenu={event => {
|
||||
let text = document.getSelection().toString()
|
||||
@ -24,4 +26,5 @@ const Root = ({ dispatch }) => (
|
||||
</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 SourcesTabContainer from "../containers/settings/sources-container"
|
||||
import GroupsTabContainer from "../containers/settings/groups-container"
|
||||
import AppTab from "./settings/app"
|
||||
import AppTabContainer from "../containers/settings/app-container"
|
||||
|
||||
type SettingsProps = {
|
||||
display: boolean,
|
||||
@ -38,7 +38,7 @@ class Settings extends React.Component<SettingsProps> {
|
||||
<GroupsTabContainer />
|
||||
</PivotItem>
|
||||
<PivotItem headerText="应用选项" itemIcon="Settings">
|
||||
<AppTab />
|
||||
<AppTabContainer />
|
||||
</PivotItem>
|
||||
<PivotItem headerText="关于" itemIcon="Info">
|
||||
<AboutTab />
|
||||
|
@ -1,20 +1,31 @@
|
||||
import * as React from "react"
|
||||
import intl = require("react-intl-universal")
|
||||
import { urlTest } from "../../scripts/utils"
|
||||
import { getProxy, getProxyStatus, toggleProxyStatus, setProxy, getThemeSettings, setThemeSettings, ThemeSettings } from "../../scripts/settings"
|
||||
import { Stack, Label, Toggle, TextField, DefaultButton, ChoiceGroup, IChoiceGroupOption, loadTheme } from "@fluentui/react"
|
||||
import { getProxy, getProxyStatus, toggleProxyStatus, setProxy, getThemeSettings, setThemeSettings, ThemeSettings, getLocaleSettings } from "../../scripts/settings"
|
||||
import { Stack, Label, Toggle, TextField, DefaultButton, ChoiceGroup, IChoiceGroupOption, loadTheme, Dropdown, IDropdownOption } from "@fluentui/react"
|
||||
|
||||
const themeChoices: IChoiceGroupOption[] = [
|
||||
{ key: ThemeSettings.Default, text: "系统默认" },
|
||||
{ key: ThemeSettings.Light, text: "浅色模式" },
|
||||
{ key: ThemeSettings.Dark, text: "深色模式" }
|
||||
]
|
||||
type AppTabProps = {
|
||||
setLanguage: (option: string) => void
|
||||
}
|
||||
|
||||
class AppTab extends React.Component {
|
||||
class AppTab extends React.Component<AppTabProps> {
|
||||
state = {
|
||||
pacStatus: getProxyStatus(),
|
||||
pacUrl: getProxy(),
|
||||
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 = () => {
|
||||
toggleProxyStatus()
|
||||
@ -41,9 +52,26 @@ class AppTab extends React.Component {
|
||||
|
||||
render = () => (
|
||||
<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.Item grow>
|
||||
<Label>启用代理</Label>
|
||||
<Label>{intl.get("app.enableProxy")}</Label>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Toggle checked={this.state.pacStatus} onChange={this.toggleStatus} />
|
||||
@ -54,8 +82,8 @@ class AppTab extends React.Component {
|
||||
<Stack.Item grow>
|
||||
<TextField
|
||||
required
|
||||
onGetErrorMessage={v => urlTest(v.trim()) ? "" : "请正确输入URL"}
|
||||
placeholder="PAC地址"
|
||||
onGetErrorMessage={v => urlTest(v.trim()) ? "" : intl.get("app.badUrl")}
|
||||
placeholder={intl.get("app.pac")}
|
||||
name="pacUrl"
|
||||
onChange={this.handleInputChange}
|
||||
value={this.state.pacUrl} />
|
||||
@ -64,16 +92,10 @@ class AppTab extends React.Component {
|
||||
<DefaultButton
|
||||
disabled={!urlTest(this.state.pacUrl)}
|
||||
type="sumbit"
|
||||
text="设置" />
|
||||
text={intl.get("app.setPac")} />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</form>}
|
||||
|
||||
<ChoiceGroup
|
||||
label="应用主题"
|
||||
options={themeChoices}
|
||||
onChange={this.onThemeChange}
|
||||
selectedKey={this.state.themeSettings} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import * as React from "react"
|
||||
import intl = require("react-intl-universal")
|
||||
import { Label, DefaultButton, TextField, Stack, PrimaryButton, DetailsList,
|
||||
IColumn, SelectionMode, Selection, IChoiceGroupOption, ChoiceGroup } from "@fluentui/react"
|
||||
import { SourceState, RSSSource, SourceOpenTarget } from "../../scripts/models/source"
|
||||
@ -20,42 +21,6 @@ type SourcesTabState = {
|
||||
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> {
|
||||
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) => {
|
||||
const name: string = event.target.name
|
||||
this.setState({[name]: event.target.value.trim()})
|
||||
@ -96,24 +97,24 @@ class SourcesTab extends React.Component<SourcesTabProps, SourcesTabState> {
|
||||
|
||||
render = () => (
|
||||
<div className="tab-body">
|
||||
<Label>OPML文件</Label>
|
||||
<Label>{intl.get("sources.opmlFile")}</Label>
|
||||
<Stack horizontal>
|
||||
<Stack.Item>
|
||||
<PrimaryButton onClick={this.props.importOPML} text="导入文件" />
|
||||
<PrimaryButton onClick={this.props.importOPML} text={intl.get("sources.import")} />
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<DefaultButton text="导出文件" />
|
||||
<DefaultButton text={intl.get("sources.export")} />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
|
||||
<form onSubmit={this.addSource}>
|
||||
<Label htmlFor="newUrl">添加订阅源</Label>
|
||||
<Label htmlFor="newUrl">{intl.get("sources.add")}</Label>
|
||||
<Stack horizontal>
|
||||
<Stack.Item grow>
|
||||
<TextField
|
||||
onGetErrorMessage={v => urlTest(v.trim()) ? "" : "请正确输入URL"}
|
||||
onGetErrorMessage={v => urlTest(v.trim()) ? "" : intl.get("sources.badUrl")}
|
||||
validateOnLoad={false}
|
||||
placeholder="输入URL"
|
||||
placeholder={intl.get("sources.inputUrl")}
|
||||
value={this.state.newUrl}
|
||||
id="newUrl"
|
||||
name="newUrl"
|
||||
@ -123,27 +124,27 @@ class SourcesTab extends React.Component<SourcesTabProps, SourcesTabState> {
|
||||
<PrimaryButton
|
||||
disabled={!urlTest(this.state.newUrl)}
|
||||
type="submit"
|
||||
text="添加" />
|
||||
text={intl.get("add")} />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</form>
|
||||
|
||||
<DetailsList
|
||||
items={Object.values(this.props.sources)}
|
||||
columns={columns}
|
||||
columns={this.columns()}
|
||||
getKey={s => s.sid}
|
||||
setKey="selected"
|
||||
selection={this.selection}
|
||||
selectionMode={SelectionMode.single} />
|
||||
|
||||
{this.state.selectedSource && <>
|
||||
<Label>选中订阅源</Label>
|
||||
<Label>{intl.get("sources.selected")}</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("sources.name")}
|
||||
value={this.state.newSourceName}
|
||||
name="newSourceName"
|
||||
onChange={this.handleInputChange} />
|
||||
@ -152,23 +153,23 @@ class SourcesTab extends React.Component<SourcesTabProps, SourcesTabState> {
|
||||
<DefaultButton
|
||||
disabled={this.state.newSourceName.length == 0}
|
||||
onClick={() => this.props.updateSourceName(this.state.selectedSource, this.state.newSourceName)}
|
||||
text="修改名称" />
|
||||
text={intl.get("sources.editName")} />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
<ChoiceGroup
|
||||
label="订阅源文章打开方式"
|
||||
options={sourceOpenTargetChoices}
|
||||
label={intl.get("sources.openTarget")}
|
||||
options={this.sourceOpenTargetChoices()}
|
||||
selectedKey={String(this.state.selectedSource.openTarget)}
|
||||
onChange={this.onOpenTargetChange} />
|
||||
<Stack horizontal style={{marginTop: 24}}>
|
||||
<Stack horizontal>
|
||||
<Stack.Item>
|
||||
<DangerButton
|
||||
onClick={() => this.props.deleteSource(this.state.selectedSource)}
|
||||
key={this.state.selectedSource.sid}
|
||||
text={`删除订阅源`} />
|
||||
text={intl.get("sources.delete")} />
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<span className="settings-hint">这将移除此订阅源与所有已保存的文章</span>
|
||||
<span className="settings-hint">{intl.get("sources.deleteWarning")}</span>
|
||||
</Stack.Item>
|
||||
</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 windowStateKeeper = require("electron-window-state")
|
||||
import Store = require('electron-store');
|
||||
import Store = require("electron-store")
|
||||
|
||||
let mainWindow: BrowserWindow
|
||||
const store = new Store()
|
||||
nativeTheme.themeSource = store.get("theme", "system")
|
||||
|
||||
function createWindow() {
|
||||
let mainWindowState = windowStateKeeper({
|
||||
@ -13,7 +14,7 @@ function createWindow() {
|
||||
// Create the browser window.
|
||||
mainWindow = new BrowserWindow({
|
||||
title: "Fluent Reader",
|
||||
backgroundColor: shouldUseDarkColors() ? "#282828" : "#faf9f8",
|
||||
backgroundColor: nativeTheme.shouldUseDarkColors ? "#282828" : "#faf9f8",
|
||||
x: mainWindowState.x,
|
||||
y: mainWindowState.y,
|
||||
width: mainWindowState.width,
|
||||
@ -22,15 +23,20 @@ function createWindow() {
|
||||
minHeight: 600,
|
||||
frame: false,
|
||||
fullscreenable: false,
|
||||
show: false,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
webviewTag: true
|
||||
}
|
||||
})
|
||||
mainWindowState.manage(mainWindow)
|
||||
mainWindow.on('ready-to-show', () => {
|
||||
mainWindow.show()
|
||||
mainWindow.focus()
|
||||
mainWindow.webContents.openDevTools()
|
||||
});
|
||||
// and load the index.html of the app.
|
||||
mainWindow.loadFile((app.isPackaged ? "dist/" : "") + 'index.html')
|
||||
mainWindow.webContents.openDevTools()
|
||||
}
|
||||
|
||||
Menu.setApplicationMenu(null)
|
||||
@ -54,10 +60,3 @@ ipcMain.on("set-theme", (_, theme) => {
|
||||
store.set("theme", 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 { createStore, applyMiddleware } from "redux"
|
||||
import thunkMiddleware from "redux-thunk"
|
||||
import { loadTheme } from '@fluentui/react'
|
||||
import { initializeIcons } from "@fluentui/react/lib/Icons"
|
||||
import { rootReducer, RootState } from "./scripts/reducer"
|
||||
import { initSources } from "./scripts/models/source"
|
||||
import { fetchItems } from "./scripts/models/item"
|
||||
import Root from "./components/root"
|
||||
import { initFeeds } from "./scripts/models/feed"
|
||||
import { AppDispatch } from "./scripts/utils"
|
||||
import { setProxy, applyThemeSettings } from "./scripts/settings"
|
||||
import { initApp } from "./scripts/models/app"
|
||||
|
||||
setProxy()
|
||||
|
||||
@ -23,7 +20,7 @@ const store = createStore(
|
||||
applyMiddleware<AppDispatch, RootState>(thunkMiddleware)
|
||||
)
|
||||
|
||||
store.dispatch(initSources()).then(() => store.dispatch(initFeeds())).then(() => store.dispatch(fetchItems()))
|
||||
store.dispatch(initApp())
|
||||
|
||||
ReactDOM.render(
|
||||
<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 { RSSItem, ItemActionTypes, FETCH_ITEMS } from "./item"
|
||||
import intl = require("react-intl-universal")
|
||||
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 { 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 { PageActionTypes, SELECT_PAGE, PageType, selectAllArticles } from "./page"
|
||||
import { getCurrentLocale, setLocaleSettings } from "../settings"
|
||||
import locales from "../i18n/_locales"
|
||||
|
||||
export enum ContextMenuType {
|
||||
Hidden, Item, Text, View
|
||||
@ -28,6 +31,7 @@ export class AppLog {
|
||||
}
|
||||
|
||||
export class AppState {
|
||||
locale = null as string
|
||||
sourceInit = false
|
||||
feedInit = 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(
|
||||
state = new AppState(),
|
||||
action: SourceActionTypes | ItemActionTypes | ContextMenuActionTypes | SettingsActionTypes
|
||||
action: SourceActionTypes | ItemActionTypes | ContextMenuActionTypes | SettingsActionTypes | InitIntlAction
|
||||
| MenuActionTypes | LogMenuActionType | FeedActionTypes | PageActionTypes | SourceGroupActionTypes
|
||||
): AppState {
|
||||
switch (action.type) {
|
||||
case INIT_INTL: return {
|
||||
...state,
|
||||
locale: action.locale
|
||||
}
|
||||
case INIT_SOURCES:
|
||||
switch (action.status) {
|
||||
case ActionStatus.Success: return {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { remote, ipcRenderer } from "electron"
|
||||
import { ViewType } from "./models/page"
|
||||
import { IPartialTheme, loadTheme } from "@fluentui/react"
|
||||
import locales from "./i18n/_locales"
|
||||
|
||||
const PAC_STORE_KEY = "PAC"
|
||||
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 = [
|
||||
PAC_STORE_KEY, PAC_STATUS_KEY, VIEW_STORE_KEY, THEME_STORE_KEY
|
||||
]
|
@ -1,6 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "react",
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"target": "ES2019",
|
||||
"module": "CommonJS"
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user