Skip to content

Commit

Permalink
Merge pull request #117 from mr-wolf-gb/master
Browse files Browse the repository at this point in the history
Fix missing translations
Add french translation
xianyunleo authored Jun 26, 2024
2 parents 0394286 + 46ff487 commit 618160a
Showing 21 changed files with 1,751 additions and 1,540 deletions.
2 changes: 1 addition & 1 deletion .prettierrc.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
singleQuote: true
semi: false
printWidth: 100
printWidth: 180
trailingComma: none
endOfLine: auto
462 changes: 229 additions & 233 deletions extra/darwin/Core/config/software/software.json

Large diffs are not rendered by default.

514 changes: 255 additions & 259 deletions extra/win32/core/config/software/software.json

Large diffs are not rendered by default.

180 changes: 90 additions & 90 deletions src/main/Settings.js
Original file line number Diff line number Diff line change
@@ -1,90 +1,90 @@
import App from '@/main/App'
import Store from 'electron-store'
import { SETTINGS_FILE_NAME } from '@/main/utils/constant'
import Path from '@/main/utils/Path'
import GetPath from '@/shared/utils/GetPath'
import { isMacOS, isWindows } from '@/main/utils/utils'
import GetAppPath from '@/main/utils/GetAppPath'

export default class Settings {
static #_instance;
static #_fileName = SETTINGS_FILE_NAME;
static #_fileExtension = 'json';

static get(key) {
return this.getInstance().get(key);
}

static set(key, val) {
return this.getInstance().set(key, val);
}

static getAll() {
return this.getInstance().store
}

/**
*
* @returns {ElectronStore<T>}
*/
static getInstance() {
if (this.#_instance) {
return this.#_instance;
}
this.init();
return this.#_instance;
}

static init() {
if (this.#_instance) {
return;
}
const options = {
name: this.#_fileName,
fileExtension: this.#_fileExtension,
cwd: this.getDir(),
};
options.defaults = this.#_getDefault();
this.#_instance = new Store(options);
}

/**
* @returns {object}
*/
static #_getDefault() {
return {
Language:'zh',
ThemeMode:'system',
ThemeColor:'#1890FF',
EnableEnv: false,
PhpCliVersion: '',
EnableComposer: false,
TextEditor: this.#_getDefaultTextEditorPath(),
WebsiteDir: Path.Join(GetAppPath.getUserCoreDir(), 'www'),
OneClickServerList: ['Nginx', 'PHP-FPM', 'MySQL-5.7'],
AutoStartAndRestartServer: true,
AfterOpenAppStartServer: false,
};
}

static #_getDefaultTextEditorPath() {
let toolTypePath = GetPath.getToolTypeDir();
if (isMacOS) {
return Path.Join(toolTypePath, 'Notepad--.app');
} else if (isWindows) {
return Path.Join(toolTypePath, 'Notepad3/Notepad3.exe');
}
}

static getDir() {
return GetAppPath.getSettingsDir()
}

/**
* 获取设置文件完整的路径
* @returns {string}
*/
static getFilePath(){
return Path.Join(this.getDir(), `${this.#_fileName}.${this.#_fileExtension}`);
}
}
import App from '@/main/App'
import Store from 'electron-store'
import { SETTINGS_FILE_NAME } from '@/main/utils/constant'
import Path from '@/main/utils/Path'
import GetPath from '@/shared/utils/GetPath'
import { isMacOS, isWindows } from '@/main/utils/utils'
import GetAppPath from '@/main/utils/GetAppPath'

export default class Settings {
static #_instance
static #_fileName = SETTINGS_FILE_NAME
static #_fileExtension = 'json'

static get(key) {
return this.getInstance().get(key)
}

static set(key, val) {
return this.getInstance().set(key, val)
}

static getAll() {
return this.getInstance().store
}

/**
*
* @returns {ElectronStore<T>}
*/
static getInstance() {
if (this.#_instance) {
return this.#_instance
}
this.init()
return this.#_instance
}

static init() {
if (this.#_instance) {
return
}
const options = {
name: this.#_fileName,
fileExtension: this.#_fileExtension,
cwd: this.getDir()
}
options.defaults = this.#_getDefault()
this.#_instance = new Store(options)
}

/**
* @returns {object}
*/
static #_getDefault() {
return {
Language: 'zh',
ThemeMode: 'system',
ThemeColor: '#1890FF',
EnableEnv: false,
PhpCliVersion: '',
EnableComposer: false,
TextEditor: this.#_getDefaultTextEditorPath(),
WebsiteDir: Path.Join(GetAppPath.getUserCoreDir(), 'www'),
OneClickServerList: ['Nginx', 'PHP-FPM', 'MySQL-5.7'],
AutoStartAndRestartServer: true,
AfterOpenAppStartServer: false
}
}

static #_getDefaultTextEditorPath() {
let toolTypePath = GetPath.getToolTypeDir()
if (isMacOS) {
return Path.Join(toolTypePath, 'Notepad--.app')
} else if (isWindows) {
return Path.Join(toolTypePath, 'Notepad3/Notepad3.exe')
}
}

static getDir() {
return GetAppPath.getSettingsDir()
}

/**
* 获取设置文件完整的路径
* @returns {string}
*/
static getFilePath() {
return Path.Join(this.getDir(), `${this.#_fileName}.${this.#_fileExtension}`)
}
}
6 changes: 4 additions & 2 deletions src/main/i18n/I18n.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import zh from '@/shared/i18n/zh'
import en from '@/shared/i18n/en'
import fr from '@/shared/i18n/fr'
import nls from '@tiny-libs/nls'

export default class I18n {
@@ -10,8 +11,9 @@ export default class I18n {
this._instance = {
locale: 'zh',
messages: {
'zh': zh,
'en': en
zh: zh,
en: en,
fr: fr
}
}
this._instance.translate = this._loadMessages()
49 changes: 24 additions & 25 deletions src/main/utils/Native.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { electronRequire } from '@/main/utils/electron'
import Shell from "@/main/utils/Shell";
import MessageBox from "@/renderer/utils/MessageBox";
import fixPath from "fix-path";
import GetPath from "@/shared/utils/GetPath";
import FileUtil from "@/main/utils/FileUtil";
import Settings from "@/main/Settings";
import Shell from '@/main/utils/Shell'
import MessageBox from '@/renderer/utils/MessageBox'
import fixPath from 'fix-path'
import GetPath from '@/shared/utils/GetPath'
import FileUtil from '@/main/utils/FileUtil'
import Settings from '@/main/Settings'
import { isMacOS, isWindows } from '@/main/utils/utils'
import FsUtil from '@/main/utils/FsUtil'
import { t } from '@/renderer/utils/i18n'

const shell = electronRequire('shell')

@@ -17,11 +18,11 @@ export default class Native {
* @returns {Promise<void>}
*/
static async openApp(path) {
if(isWindows){
if (isWindows) {
Native.openExternal(path)
}else if(isMacOS){
} else if (isMacOS) {
await Shell.exec(`open -a "${path}"`)
}else {
} else {
throw new Error(`todo`)
}
}
@@ -32,17 +33,17 @@ export default class Native {
*/
static async openTextFile(filePath) {
if (isMacOS) {
fixPath() //mac下修复环境变量不识别的问题
fixPath() //mac下修复环境变量不识别的问题
}
try {
if (!await FileUtil.Exists(filePath)) {
throw new Error(`${filePath} 文件不存在`)
if (!(await FileUtil.Exists(filePath))) {
throw new Error(`${filePath} ${t('does not exist!')}`)
}

let editorPath = Settings.get('TextEditor')
//Mac app是目录,Windows app是文件
if (!await FsUtil.Exists(editorPath)) {
throw new Error(`${editorPath} 不存在!\n请重新设置文本编辑器`)
if (!(await FsUtil.Exists(editorPath))) {
throw new Error(`${editorPath} ${t('does not exist!')}\n${t('Please reset the text editor')}`)
}
let command
if (isMacOS) {
@@ -51,40 +52,38 @@ export default class Native {
command = `"${editorPath}" "${filePath}"`
}
await Shell.exec(command)

} catch (error) {
//todo渲染进程捕捉错误
MessageBox.error(error.message ?? error, '打开文件出错!')
MessageBox.error(error.message ?? error, t('Error opening file!'))
}

}

static async openPath(path) {
return await shell.openPath(path);
return await shell.openPath(path)
}

static async showItemInFolder(path) {
await shell.showItemInFolder(path);
await shell.showItemInFolder(path)
}

static async openExternal(path) {
return await shell.openExternal(path);
return await shell.openExternal(path)
}

static async openDirectory(path) {
if(isWindows){
return await shell.openExternal(path);
if (isWindows) {
return await shell.openExternal(path)
}
return await shell.openPath(path);
return await shell.openPath(path)
}

static async openUrl(url) {
return await shell.openExternal(url);
return await shell.openExternal(url)
}

static async openHosts() {
let path = GetPath.getHostsPath()
if (await FileUtil.Exists(path) && !await FsUtil.CanReadWrite(path)) {
if ((await FileUtil.Exists(path)) && !(await FsUtil.CanReadWrite(path))) {
await FsUtil.ChmodReadWrite(path)
}
await Native.openTextFile(path)
85 changes: 42 additions & 43 deletions src/renderer/App.vue
Original file line number Diff line number Diff line change
@@ -1,49 +1,48 @@
<template>
<ConfigProvider>
<a-spin :spinning='store.loading' :tip="store.loadingTip+' ...'"
size='large' style='height: 100vh;'>
<a-spin :spinning="store.loading" :tip="store.loadingTip + ' ...'" size="large" style="height: 100vh">
<a-layout>
<a-row>
<a-col :span='4' style='height: 100vh;'>
<a-col :span="4" style="height: 100vh">
<SideBar />
</a-col>
<a-col :span='20' style='display: flex;flex-direction: column;height: 100vh'>
<a-col :span="20" style="display: flex; flex-direction: column; height: 100vh">
<TitleBar />
<router-view />
</a-col>
</a-row>
<!-- v-if防止不显示就执行modal里面的代码-->
<user-pwd-modal v-if='userPwdModalShow' v-model:show='userPwdModalShow' :cancel-is-exit='true' />
<set-language v-if='setLanguageShow' v-model:show='setLanguageShow'></set-language>
<user-pwd-modal v-if="userPwdModalShow" v-model:show="userPwdModalShow" :cancel-is-exit="true" />
<set-language v-if="setLanguageShow" v-model:show="setLanguageShow"></set-language>
</a-layout>
</a-spin>
</ConfigProvider>
</template>

<script setup>
import { isDev, isMacOS, isWindows } from '@/main/utils/utils'
import TitleBar from "./components/TitleBar.vue";
import SideBar from "./components/SideBar.vue";
import App from "@/main/App";
import TitleBar from './components/TitleBar.vue'
import SideBar from './components/SideBar.vue'
import App from '@/main/App'
import { onMounted, provide, reactive, ref, watch } from 'vue'
import MessageBox from "@/renderer/utils/MessageBox";
import UserPwdModal from "@/renderer/components/UserPwdModal.vue";
import Software from "@/main/core/software/Software";
import Service from "@/main/utils/Service";
import {message} from "ant-design-vue";
import DirUtil from "@/main/utils/DirUtil";
import {MAC_USER_CORE_DIR} from "@/main/utils/constant";
import ConfigProvider from "@/renderer/components/Theme/ConfigProvider.vue";
import SetLanguage from "@/renderer/components/SetLanguage.vue";
import MessageBox from '@/renderer/utils/MessageBox'
import UserPwdModal from '@/renderer/components/UserPwdModal.vue'
import Software from '@/main/core/software/Software'
import Service from '@/main/utils/Service'
import { message } from 'ant-design-vue'
import DirUtil from '@/main/utils/DirUtil'
import { MAC_USER_CORE_DIR } from '@/main/utils/constant'
import ConfigProvider from '@/renderer/components/Theme/ConfigProvider.vue'
import SetLanguage from '@/renderer/components/SetLanguage.vue'
import { useMainStore } from '@/renderer/store'
import Settings from '@/main/Settings'
import { t } from '@/renderer/utils/i18n'
import { changeLanguageWrapper } from '@/renderer/utils/language'
import SystemExtend from '@/main/utils/SystemExtend'
const store = useMainStore();
const userPwdModalShow = ref(false);
const setLanguageShow = ref(false);
const store = useMainStore()
const userPwdModalShow = ref(false)
const setLanguageShow = ref(false)
const serverReactive = reactive({ restartFn: undefined, startPhpFpmFn: undefined })
@@ -55,7 +54,7 @@ store.settings = settings
onMounted(async () => {
try {
if (await App.initFileExists() && !isDev) {
if ((await App.initFileExists()) && !isDev) {
await App.checkInstall()
await initOrUpdate()
}
@@ -81,13 +80,14 @@ async function initOrUpdate() {
App.exit()
}
//存在initFile文件的情况下,判断是第一次安装,还是覆盖安装
if (!await Software.DirExists()) { //目录不存在说明是第一次安装
if (!(await Software.DirExists())) {
//目录不存在说明是第一次安装
if (isMacOS) {
//调用设置(electron-store)会自动创建USER_CORE_DIR,为了捕捉创建失败的错误,先提前写好创建文件夹的代码。
await macCreateUserCoreDir()
}
setLanguageShow.value = true
watch(setLanguageShow, async setLanguageShow => {
watch(setLanguageShow, async (setLanguageShow) => {
if (!setLanguageShow) {
if (isWindows) {
await winInit()
@@ -104,47 +104,46 @@ async function initOrUpdate() {
async function winInit() {
try {
store.loading = true;
await App.init();
await store.refreshSoftwareList();
store.loading = false;
store.loading = true
await App.init()
await store.refreshSoftwareList()
store.loading = false
} catch (error) {
await MessageBox.error(error.message ?? error, t('errorOccurredDuring', [t('initializing')]));
App.exit();
await MessageBox.error(error.message ?? error, t('errorOccurredDuring', [t('initializing')]))
App.exit()
}
}
async function macCreateUserCoreDir() {
try {
if (!await DirUtil.Exists(MAC_USER_CORE_DIR)) {
await DirUtil.Create(MAC_USER_CORE_DIR);
if (!(await DirUtil.Exists(MAC_USER_CORE_DIR))) {
await DirUtil.Create(MAC_USER_CORE_DIR)
}
} catch (error) {
await MessageBox.error(error.message ?? error, t('errorOccurredDuring', [t('initializing')]));
App.exit();
await MessageBox.error(error.message ?? error, t('errorOccurredDuring', [t('initializing')]))
App.exit()
}
}
async function update() {
try {
store.loading = true;
await App.update();
await App.deleteInitFile();
store.loading = false;
store.loading = true
await App.update()
await App.deleteInitFile()
store.loading = false
} catch (error) {
await MessageBox.error(error.message ?? error, t('errorOccurredDuring', [t('update')]));
App.exit();
await MessageBox.error(error.message ?? error, t('errorOccurredDuring', [t('update')]))
App.exit()
}
}
async function stopIIS() {
const IISServiceName = 'W3SVC'
if (await Service.isRunning(IISServiceName)) {
await Service.stop(IISServiceName)
message.info('已自动停止IIS服务')
message.info('')
}
}
</script>
<style>
</style>
<style></style>
41 changes: 19 additions & 22 deletions src/renderer/components/SetLanguage.vue
Original file line number Diff line number Diff line change
@@ -1,48 +1,48 @@
<template>
<a-modal
title="Set Language"
v-model:open="visible"
centered :maskClosable="false">
<a-modal title="Set Language" v-model:open="visible" centered :maskClosable="false">
<div class="modal-content">
<span> {{ t('Language') }} (Language):</span>
<a-select
v-model:value='store.settings.Language'
:options='languageOptions' @change='languageChange'
placeholder='请选择' style='width: 200px'
v-model:value="store.settings.Language"
:options="languageOptions"
@change="languageChange"
placeholder="请选择"
style="width: 200px"
></a-select>
</div>
<template #footer>
<a-button key="submit" type="primary" @click="handleOk">{{t('Confirm')}}</a-button>
<a-button key="submit" type="primary" @click="handleOk">{{ t('Confirm') }}</a-button>
</template>
</a-modal>
</template>

<script setup>
import {t} from "@/renderer/utils/i18n";
import {computed} from "vue";
import Settings from "@/main/Settings";
import MessageBox from "@/renderer/utils/MessageBox";
import { t } from '@/renderer/utils/i18n'
import { computed } from 'vue'
import Settings from '@/main/Settings'
import MessageBox from '@/renderer/utils/MessageBox'
import { useI18n } from 'vue-i18n'
import { useMainStore } from '@/renderer/store'
import { changeLanguageWrapper } from '@/renderer/utils/language'
const { locale } = useI18n()
const props = defineProps({
show:Boolean,
const props = defineProps({
show: Boolean
})
const emit = defineEmits(['update:show'])
const store = useMainStore()
const visible = computed({
get() {
return props.show;
return props.show
},
set(value) {
emit('update:show', value);
emit('update:show', value)
}
});
const languageOptions= [
})
const languageOptions = [
{ label: '中文', value: 'zh' },
{ label: 'English', value: 'en' },
{ label: 'Français', value: 'fr' }
]
const handleOk = () => {
@@ -58,10 +58,7 @@ const languageChange = async () => {
} catch (error) {
MessageBox.error(error.message ?? error, t('errorOccurredDuring', [t('set')]))
}
}
</script>
<style scoped>
</style>
<style scoped></style>
57 changes: 31 additions & 26 deletions src/renderer/components/Settings/App.vue
Original file line number Diff line number Diff line change
@@ -1,40 +1,46 @@
<template>
<a-card size="small" :title='t("Application")' class='settings-card'>
<a-row type='flex' align='middle' class='settings-card-row'>
<a-col :span='24' class='flex-vertical-center'>
<a-card size="small" :title="t('Application')" class="settings-card">
<a-row type="flex" align="middle" class="settings-card-row">
<a-col :span="24" class="flex-vertical-center">
<span> {{ t('Language') }}:</span>
<a-select
v-model:value='store.settings.Language'
:options='languageOptions' @change='languageChange'
placeholder='请选择' style='width: 200px'
v-model:value="store.settings.Language"
:options="languageOptions"
@change="languageChange"
placeholder="{{ t('pleaseChoose') }}"
style="width: 200px"
></a-select>
</a-col>
</a-row>
<a-row type='flex' align='middle' class='settings-card-row'>
<a-col :span='12' class='flex-vertical-center'>
<span>{{ mt('Theme','ws','Mode') }}:</span>
<a-row type="flex" align="middle" class="settings-card-row">
<a-col :span="12" class="flex-vertical-center">
<span>{{ mt('Theme', 'ws', 'Mode') }}:</span>
<a-select
v-model:value='store.settings.ThemeMode'
:options='modeOptions' @change='themeModeChange'
placeholder='请选择' style='width: 200px'
v-model:value="store.settings.ThemeMode"
:options="modeOptions"
@change="themeModeChange"
placeholder="{{ t('pleaseChoose') }}"
style="width: 200px"
></a-select>
</a-col>
<a-col :span='12' class='flex-vertical-center'>
<span>{{ mt('Theme','ws','Color') }}:</span>
<a-col :span="12" class="flex-vertical-center">
<span>{{ mt('Theme', 'ws', 'Color') }}:</span>
<a-select
v-model:value='store.settings.ThemeColor'
:options='colorOptions' @change='themeColorChange'
placeholder='请选择' style='width: 200px'
v-model:value="store.settings.ThemeColor"
:options="colorOptions"
@change="themeColorChange"
placeholder="{{ t('pleaseChoose') }}"
style="width: 200px"
></a-select>
</a-col>
</a-row>
</a-card>
</template>

<script setup>
import {computed} from 'vue'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import {mt,t} from '@/renderer/utils/i18n'
import { mt, t } from '@/renderer/utils/i18n'
import { createAsyncComponent } from '@/renderer/utils/utils'
import { useMainStore } from '@/renderer/store'
import { changeLanguageWrapper } from '@/renderer/utils/language'
@@ -46,6 +52,7 @@ const store = useMainStore()
const languageOptions = [
{ label: '中文', value: 'zh' },
{ label: 'English', value: 'en' },
{ label: 'Français', value: 'fr' }
]
const modeOptions = computed(() => {
@@ -63,29 +70,27 @@ const colorOptions = computed(() => {
{ label: t('red'), value: '#DC4437' },
{ label: t('cyan'), value: '#02BCAA' },
{ label: t('pink'), value: '#fb7299' },
{ label: t('purple'), value: '#673BB7' },
{ label: t('purple'), value: '#673BB7' }
]
})
const languageChange = () => {
store.setSettings('Language', async originVal => {
store.setSettings('Language', async (originVal) => {
await changeLanguageWrapper(store.settings.Language)
})
}
const themeModeChange = () => {
store.setSettings('ThemeMode', async originVal => {
store.setSettings('ThemeMode', async (originVal) => {
store.changeTheme(store.settings.ThemeMode, store.settings.ThemeColor)
})
}
const themeColorChange = () => {
store.setSettings('ThemeColor', async originVal => {
store.setSettings('ThemeColor', async (originVal) => {
store.changeTheme(store.settings.ThemeMode, store.settings.ThemeColor)
})
}
</script>

<style scoped>
</style>
<style scoped></style>
40 changes: 18 additions & 22 deletions src/renderer/components/Settings/EnvVar.vue
Original file line number Diff line number Diff line change
@@ -3,24 +3,23 @@
<a-row type="flex" align="middle" class="settings-card-row">
<a-col :span="24" class="flex-vertical-center">
<a-switch v-model:checked="store.settings.EnableEnv" @change="changeEnableEnv" class="settings-switch" />
<span>{{mt('Enable','ws','EnvironmentVariables')}}</span>
<a-typography-text style="margin-left: 20px" type="danger">* {{t('needRestartTerminal')}}
</a-typography-text>
<span>{{ mt('Enable', 'ws', 'EnvironmentVariables') }}</span>
<a-typography-text style="margin-left: 20px" type="danger">* {{ t('needRestartTerminal') }} </a-typography-text>
</a-col>
</a-row>
<a-row type="flex" justify="space-around" align="middle" class="settings-card-row">
<a-col :span="12" class="flex-vertical-center">
<span :class="!store.settings.EnableEnv?'disabled-text':''">
PHP-CLI {{t('Version')}}:
</span>
<a-select style="width: 120px" :options="phpVersionList" :disabled="!store.settings.EnableEnv"
v-model:value="store.settings.PhpCliVersion" @change="phpCliVersionChange"/>
<span :class="!store.settings.EnableEnv ? 'disabled-text' : ''"> PHP-CLI {{ t('Version') }}: </span>
<a-select style="width: 120px" :options="phpVersionList" :disabled="!store.settings.EnableEnv" v-model:value="store.settings.PhpCliVersion" @change="phpCliVersionChange" />
</a-col>
<a-col :span="12" class="flex-vertical-center">
<a-switch v-model:checked="store.settings.EnableComposer" @change="changeEnableComposer"
class="settings-switch"
:disabled="!store.settings.EnableEnv || store.settings.PhpCliVersion===''" />
<span :class="!store.settings.EnableEnv?'disabled-text':''">{{t('Enable')}} Composer:</span>
<a-switch
v-model:checked="store.settings.EnableComposer"
@change="changeEnableComposer"
class="settings-switch"
:disabled="!store.settings.EnableEnv || store.settings.PhpCliVersion === ''"
/>
<span :class="!store.settings.EnableEnv ? 'disabled-text' : ''">{{ t('Enable') }} Composer:</span>
</a-col>
</a-row>
</a-card>
@@ -36,12 +35,11 @@ import { mt, t } from '@/renderer/utils/i18n'
import { createAsyncComponent } from '@/renderer/utils/utils'
import { useMainStore } from '@/renderer/store'
const ACard = createAsyncComponent(import('ant-design-vue'), 'Card')
const store = useMainStore()
const changeEnableEnv = async () => {
store.setSettings('EnableEnv', async originVal => {
store.setSettings('EnableEnv', async (originVal) => {
await Env.switch(store.settings.EnableEnv)
})
}
@@ -54,36 +52,34 @@ const phpVersionList = computed(() => {
;(async () => {
const list = await SoftwareExtend.getPHPList()
phpVersionListTemp.value = list.map(item => {
phpVersionListTemp.value = list.map((item) => {
return { value: item.version, label: item.name }
})
})()
const phpCliVersionChange = () => {
store.setSettings('PhpCliVersion', async originVal => {
store.setSettings('PhpCliVersion', async (originVal) => {
if (store.settings.PhpCliVersion) {
let path = GetPath.getPhpExePath(store.settings.PhpCliVersion)
await Env.createBinFile(path, 'php')
} else {
await Env.deleteBinFile('php')
}
message.success('设置成功,已生效,不需要重启终端!')
message.success(t('The setting is successful and has taken effect. There is no need to restart the terminal!'))
})
}
const changeEnableComposer = async () => {
store.setSettings('EnableComposer', async originVal => {
store.setSettings('EnableComposer', async (originVal) => {
if (store.settings.EnableComposer) {
let path = GetPath.getComposerExePath()
await Env.createBinFile(path, 'composer')
} else {
await Env.deleteBinFile('composer')
}
message.success('设置成功,已生效,不需要重启终端!')
message.success(t('The setting is successful and has taken effect. There is no need to restart the terminal!'))
})
}
</script>

<style scoped>
</style>
<style scoped></style>
172 changes: 84 additions & 88 deletions src/renderer/components/Settings/Other.vue
Original file line number Diff line number Diff line change
@@ -1,88 +1,84 @@
<template>
<a-card size="small" :title="t('Other')" class='settings-card'>
<div class='settings-card-row flex-vertical-center'>
<span>{{ mt('Text', 'ws', 'Editor') }}:</span>
<a-input v-model:value='store.settings.TextEditor' readonly style='flex: 1'></a-input>
<a-button @click='changeTextEditor' style='margin-left: 5px'>...</a-button>
</div>

<div class='settings-card-row flex-vertical-center'>
<span>{{ mt('Website', 'ws', 'Directory') }}:</span>
<a-input v-model:value='store.settings.WebsiteDir' readonly style='flex: 1'></a-input>
<a-button @click='changeWebsiteDir' style='margin-left: 5px'>...</a-button>
</div>

<a-row type='flex' align='middle' class='settings-card-row'>
<a-col :span='6' class='flex-vertical-center'>
<a-button @click='exitApp' type='primary'>{{ t('Exit') }} {{APP_NAME}}</a-button>
</a-col>
<a-col :span='6' class='flex-vertical-center'>
<a-button @click='init' type='primary'>{{ t('Initialize') }}</a-button>
</a-col>
</a-row>

</a-card>
</template>

<script setup>
import FileDialog from '@/main/utils/FileDialog'
import MessageBox from '@/renderer/utils/MessageBox'
import SoftwareInit from '@/main/core/software/SoftwareInit'
import { message } from 'ant-design-vue'
import { mt, t } from '@/renderer/utils/i18n'
import {APP_NAME} from "@/shared/utils/constant";
import App from "@/main/App";
import { createAsyncComponent } from '@/renderer/utils/utils'
import { useMainStore } from '@/renderer/store'
const ACard = createAsyncComponent(import('ant-design-vue'), 'Card')
const store = useMainStore()
const changeTextEditor = () => {
store.setSettings('TextEditor', async originVal => {
let path = FileDialog.showOpenApp(originVal)
if (!path) {
return false
}
return path
})
}
const changeWebsiteDir = () => {
store.setSettings('WebsiteDir', async originVal => {
let path = FileDialog.showOpenDirectory(originVal)
if (!path) {
return false
}
if (path.includes(' ')) {
throw new Error('网站目录不能有空格!')
}
return path
})
}
const exitApp = ()=>{
App.exit()
}
const init = async () => {
try {
const options = {
content: t('initConfirmText'),
okText: t('Confirm'),
cancelText: t('Cancel')
}
if (await MessageBox.confirm(options)) {
await SoftwareInit.initAll()
message.success('初始化成功')
}
} catch (error) {
MessageBox.error(error.message ?? error, '初始化失败!')
}
}
</script>
<style scoped>
</style>
<template>
<a-card size="small" :title="t('Other')" class="settings-card">
<div class="settings-card-row flex-vertical-center">
<span>{{ mt('Text', 'ws', 'Editor') }}:</span>
<a-input v-model:value="store.settings.TextEditor" readonly style="flex: 1"></a-input>
<a-button @click="changeTextEditor" style="margin-left: 5px">...</a-button>
</div>

<div class="settings-card-row flex-vertical-center">
<span>{{ mt('Website', 'ws', 'Directory') }}:</span>
<a-input v-model:value="store.settings.WebsiteDir" readonly style="flex: 1"></a-input>
<a-button @click="changeWebsiteDir" style="margin-left: 5px">...</a-button>
</div>

<a-row type="flex" align="middle" class="settings-card-row">
<a-col :span="6" class="flex-vertical-center">
<a-button @click="exitApp" type="primary">{{ t('Exit') }} {{ APP_NAME }}</a-button>
</a-col>
<a-col :span="6" class="flex-vertical-center">
<a-button @click="init" type="primary">{{ t('Initialize') }}</a-button>
</a-col>
</a-row>
</a-card>
</template>

<script setup>
import FileDialog from '@/main/utils/FileDialog'
import MessageBox from '@/renderer/utils/MessageBox'
import SoftwareInit from '@/main/core/software/SoftwareInit'
import { message } from 'ant-design-vue'
import { mt, t } from '@/renderer/utils/i18n'
import { APP_NAME } from '@/shared/utils/constant'
import App from '@/main/App'
import { createAsyncComponent } from '@/renderer/utils/utils'
import { useMainStore } from '@/renderer/store'
const ACard = createAsyncComponent(import('ant-design-vue'), 'Card')
const store = useMainStore()
const changeTextEditor = () => {
store.setSettings('TextEditor', async (originVal) => {
let path = FileDialog.showOpenApp(originVal)
if (!path) {
return false
}
return path
})
}
const changeWebsiteDir = () => {
store.setSettings('WebsiteDir', async (originVal) => {
let path = FileDialog.showOpenDirectory(originVal)
if (!path) {
return false
}
if (path.includes(' ')) {
throw new Error(t('The website directory cannot have spaces!'))
}
return path
})
}
const exitApp = () => {
App.exit()
}
const init = async () => {
try {
const options = {
content: t('initConfirmText'),
okText: t('Confirm'),
cancelText: t('Cancel')
}
if (await MessageBox.confirm(options)) {
await SoftwareInit.initAll()
message.success(t('Initialization successful'))
}
} catch (error) {
MessageBox.error(error.message ?? error, t('Initialization failed!'))
}
}
</script>
<style scoped></style>
49 changes: 18 additions & 31 deletions src/renderer/components/WebSite/AddWebSiteModal.vue
Original file line number Diff line number Diff line change
@@ -1,40 +1,27 @@
<template>
<a-modal
:title="mt('Add','ws','Website')"
:ok-text="t('Submit')"
:cancel-text="t('Cancel')"
@ok='addWebClick'
v-model:open='visible'
centered
:maskClosable='false'>
<div class='modal-content'>
<a-form
ref='formRef' :model='formData' name='basic' autocomplete='off'
:label-col='{ span: labelColSpan}' :wrapper-col='{ span: wrapperColSpan}'
>
<a-form-item :label="t('DomainName')+''" name='serverName'
:rules="[{ required: true, message: t('cannotBeEmpty')}]">
<a-input v-model:value='formData.serverName' @change='serverNameChange' spellcheck='false' />
<a-modal :title="mt('Add', 'ws', 'Website')" :ok-text="t('Submit')" :cancel-text="t('Cancel')" @ok="addWebClick" v-model:open="visible" centered :maskClosable="false">
<div class="modal-content">
<a-form ref="formRef" :model="formData" name="basic" autocomplete="off" :label-col="{ span: labelColSpan }" :wrapper-col="{ span: wrapperColSpan }">
<a-form-item :label="t('DomainName') + ''" name="serverName" :rules="[{ required: true, message: t('cannotBeEmpty') }]">
<a-input v-model:value="formData.serverName" @change="serverNameChange" spellcheck="false" />
</a-form-item>

<a-form-item :label="t('Port')" name='port'
:rules="[{ required: true, type: 'number', min: 80, max: 65535 }]">
<a-input-number v-model:value='formData.port' min='80' max='65535' />
<a-form-item :label="t('Port')" name="port" :rules="[{ required: true, type: 'number', min: 80, max: 65535 }]">
<a-input-number v-model:value="formData.port" min="80" max="65535" />
</a-form-item>

<a-form-item :label="t('RootPath')" name='rootPath' :rules='rootPathRules'>
<input-open-dir-dialog v-model:value='formData.rootPath' :toForwardSlash='true'></input-open-dir-dialog>
<a-form-item :label="t('RootPath')" name="rootPath" :rules="rootPathRules">
<input-open-dir-dialog v-model:value="formData.rootPath" :toForwardSlash="true"></input-open-dir-dialog>
</a-form-item>

<a-form-item :label="'PHP'+mt('ws','Version')" name='phpVersion'>
<a-select style='width: 120px' v-model:value='formData.phpVersion' :options='phpVersionList' />
<a-form-item :label="'PHP' + mt('ws', 'Version')" name="phpVersion">
<a-select style="width: 120px" v-model:value="formData.phpVersion" :options="phpVersionList" />
</a-form-item>

<a-form-item :label="mt('Sync','ws')+'hosts'" name='syncHosts'>
<a-switch v-model:checked='formData.syncHosts' />
<a-form-item :label="mt('Sync', 'ws') + 'hosts'" name="syncHosts">
<a-switch v-model:checked="formData.syncHosts" />
</a-form-item>
</a-form>

</div>
</a-modal>
</template>
@@ -66,12 +53,12 @@ const formData = reactive({
syncHosts: true
})
const labelColSpan = store.settings.Language === 'zh' ? 6 : 8;
const wrapperColSpan = store.settings.Language === 'zh' ? 18 : 16;
const labelColSpan = store.settings.Language === 'zh' ? 6 : 8
const wrapperColSpan = store.settings.Language === 'zh' ? 18 : 16
;(async () => {
const list = await SoftwareExtend.getPHPList()
phpVersionList.value = list.map(item => {
phpVersionList.value = list.map((item) => {
return { value: item.version, label: item.name }
})
phpVersionList.value.push({ value: '', label: t('Static') })
@@ -96,7 +83,7 @@ const addWeb = async (websiteInfo) => {
try {
await Website.add(websiteInfo)
} catch (error) {
MessageBox.error(error.message ?? error, '添加网站出错!')
MessageBox.error(error.message ?? error, t('Error adding website!'))
return
}
visible.value = false
@@ -105,7 +92,7 @@ const addWeb = async (websiteInfo) => {
try {
await Hosts.add(websiteInfo.serverName)
} catch (error) {
MessageBox.error(error.message ?? error, t('errorOccurredDuring', [mt('sync', 'ws') + 'hosts']))
MessageBox.error(error.message ?? error, t('errorOccurredDuring', [mt('sync', 'ws') + 'hosts']))
}
}
6 changes: 4 additions & 2 deletions src/renderer/i18n/I18n.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createI18n } from 'vue-i18n'
import zh from '@/shared/i18n/zh'
import en from '@/shared/i18n/en'
import fr from '@/shared/i18n/fr'

export default class I18n {
static _instance
@@ -10,8 +11,9 @@ export default class I18n {
legacy: false,
locale: 'zh',
messages: {
'zh': zh,
'en': en
zh: zh,
en: en,
fr: fr
}
})
}
67 changes: 35 additions & 32 deletions src/renderer/views/About.vue
Original file line number Diff line number Diff line change
@@ -1,32 +1,35 @@
<template>
<div class="content-container color-text">
<p style="text-align: center;font-size: 18px;margin-top: 50px">{{ APP_NAME }} 集成服务环境</p>
<p style="text-align: center">{{$t("Version")}}:{{version}}</p>
<p style="text-align: center">
{{t('OfficialSite')}}:<a @click="openUrl('http://www.eserver.app')">www.eserver.app</a>
</p>
<p style="text-align: center">
{{t('Doc')}}:<a @click="openUrl('http://www.eserver.app/doc')">www.eserver.app/doc</a>
</p>
<p style="text-align: center">
Github:<a @click="openUrl('http://github.com/xianyunleo/EServer')">github.com/xianyunleo/EServer</a>
</p>
</div>
</template>

<script setup>
import App from "@/main/App";
import {APP_NAME} from "@/shared/utils/constant";
import Native from "@/main/utils/Native";
import { t } from '@/renderer/utils/i18n'
const version = App.getVersion();
const openUrl = (url) => {
Native.openUrl(url);
}
</script>

<style scoped>
</style>
<template>
<div class="content-container color-text">
<p style="text-align: center; font-size: 18px; margin-top: 50px">{{ APP_NAME }}</p>
<p style="text-align: center; font-size: 18px; font-weight: bold">
{{ t('integratedServiceEnvironment') }}
</p>
<p style="text-align: center">{{ $t('Version') }}:{{ version }}</p>
<p style="text-align: center">
{{ t('OfficialSite') }}:<a @click="openUrl('http://www.eserver.app')">www.eserver.app</a>
</p>
<p style="text-align: center">
{{ t('Doc') }}:<a @click="openUrl('http://www.eserver.app/doc')">www.eserver.app/doc</a>
</p>
<p style="text-align: center">
Github:<a @click="openUrl('http://github.com/xianyunleo/EServer')"
>github.com/xianyunleo/EServer</a
>
</p>
</div>
</template>

<script setup>
import App from '@/main/App'
import { APP_NAME } from '@/shared/utils/constant'
import Native from '@/main/utils/Native'
import { t } from '@/renderer/utils/i18n'
const version = App.getVersion()
const openUrl = (url) => {
Native.openUrl(url)
}
</script>

<style scoped></style>
732 changes: 366 additions & 366 deletions src/renderer/views/Home.vue

Large diffs are not rendered by default.

114 changes: 53 additions & 61 deletions src/renderer/views/Software.vue
Original file line number Diff line number Diff line change
@@ -1,54 +1,51 @@
<template>
<div class='content-container'>
<div class='category'>
<a-radio-group v-model:value='softwareTypeSelected' button-style='solid' @change='radioGroupChange'>
<a-radio-button :value='InstalledType'>{{ t('Installed') }}</a-radio-button>
<a-radio-button :value='enumGetName(EnumSoftwareType, EnumSoftwareType.Server)'>{{ t('Server') }}
</a-radio-button>
<a-radio-button :value='enumGetName(EnumSoftwareType, EnumSoftwareType.PHP)'>PHP</a-radio-button>
<a-radio-button :value='enumGetName(EnumSoftwareType, EnumSoftwareType.Tool)'>{{ t('Tool') }}</a-radio-button>
<div class="content-container">
<div class="category">
<a-radio-group v-model:value="softwareTypeSelected" button-style="solid" @change="radioGroupChange">
<a-radio-button :value="InstalledType">{{ t('Installed') }}</a-radio-button>
<a-radio-button :value="enumGetName(EnumSoftwareType, EnumSoftwareType.Server)">{{ t('Server') }} </a-radio-button>
<a-radio-button :value="enumGetName(EnumSoftwareType, EnumSoftwareType.PHP)">PHP</a-radio-button>
<a-radio-button :value="enumGetName(EnumSoftwareType, EnumSoftwareType.Tool)">{{ t('Tool') }}</a-radio-button>
</a-radio-group>
</div>

<div class='soft-list piece'>
<div class='soft-head'>
<div class='soft-item'>
<div class='soft-item-content'>
<div class='soft-item-avatar'>
<div class="soft-list piece">
<div class="soft-head">
<div class="soft-item">
<div class="soft-item-content">
<div class="soft-item-avatar">
<span></span>
</div>
<div class='soft-item-title'>{{ t('Name') }}</div>
<div class='soft-item-desc'>{{ t('Desc') }}</div>
<div class='soft-item-operate'>{{ t('Operation') }}</div>
<div class="soft-item-title">{{ t('Name') }}</div>
<div class="soft-item-desc">{{ t('Desc') }}</div>
<div class="soft-item-operate">{{ t('Operation') }}</div>
</div>
</div>
</div>

<div class='soft-body'>
<template v-for='item in softwareList' :key='item.Name'>
<div v-if='item.show' class='soft-item'>
<div class='soft-item-content'>
<div class='soft-item-avatar'>
<img :src='item.Icon' />
<div class="soft-body">
<template v-for="item in softwareList" :key="item.Name">
<div v-if="item.show" class="soft-item">
<div class="soft-item-content">
<div class="soft-item-avatar">
<img :src="item.Icon" />
</div>
<div class='soft-item-title'>{{ item.Name }}</div>
<div class='soft-item-desc'>{{ item.Desc }}</div>
<div class='soft-item-operate'>
<template v-if='item.Installed'>
<a-button type='primary' danger style='margin-right: 5px'
:disabled='item.CanDelete === false' @click='uninstall(item)'
>{{ t('Uninstall') }}</a-button>
<div class="soft-item-title">{{ item.Name }}</div>
<div class="soft-item-desc">{{ t(item.Desc) }}</div>
<div class="soft-item-operate">
<template v-if="item.Installed">
<a-button type="primary" danger style="margin-right: 5px" :disabled="item.CanDelete === false" @click="uninstall(item)">{{ t('Uninstall') }}</a-button>

<a-dropdown :trigger="['click']">
<template #overlay>
<a-menu>
<a-menu-item v-if='item.IsMacApp || item.WinExePath' @click='openApp(item)'>
<a-menu-item v-if="item.IsMacApp || item.WinExePath" @click="openApp(item)">
{{ t('Open') }}
</a-menu-item>
<a-menu-item @click='openInstallPath(item)'>
<a-menu-item @click="openInstallPath(item)">
{{ mt('Open', 'ws', 'Directory') }}
</a-menu-item>
<a-menu-item v-if='item.Type === phpTypeValue' @click='showPhpExtManager(item)'>
<a-menu-item v-if="item.Type === phpTypeValue" @click="showPhpExtManager(item)">
{{ mt('Install', 'ws', 'Extension') }}
</a-menu-item>
</a-menu>
@@ -57,31 +54,28 @@
</a-dropdown>
</template>
<template v-else>
<a-button v-if='item.installInfo == null || item.showStatusErrorText'
type='primary' @click='clickInstall(item)'>{{ t('Install') }}</a-button>
<a-button v-if="item.installInfo == null || item.showStatusErrorText" type="primary" @click="clickInstall(item)">{{ t('Install') }}</a-button>

<a-button v-else type='primary' @click='clickStop(item)'>{{ t('Stop') }}</a-button>
<a-button v-else type="primary" @click="clickStop(item)">{{ t('Stop') }}</a-button>
</template>
</div>
</div>
<div v-if='item.installInfo' class='soft-item-progress'>
<a-progress :percent='item.installInfo?.dlInfo?.percent' :show-info='false' status='active' />
<div class='progress-info'>
<div class='progress-info-left'>
<span v-if='item.installInfo?.status === EnumSoftwareInstallStatus.Downloading'>
<div v-if="item.installInfo" class="soft-item-progress">
<a-progress :percent="item.installInfo?.dlInfo?.percent" :show-info="false" status="active" />
<div class="progress-info">
<div class="progress-info-left">
<span v-if="item.installInfo?.status === EnumSoftwareInstallStatus.Downloading">
{{ item.installInfo?.dlInfo?.completedSizeText }}/{{ item.installInfo?.dlInfo?.totalSizeText }}
</span>
</div>
<div v-if='item.showStatusErrorText' class='status-text-error'>
<div v-if="item.showStatusErrorText" class="status-text-error">
<a-tooltip>
<template #title>{{ item.statusErrorText }}</template>
{{ item.statusErrorText }}
</a-tooltip>
</div>
<div class='progress-info-right'>
<span v-if='item.installInfo?.status === EnumSoftwareInstallStatus.Downloading'>
↓{{ item.installInfo?.dlInfo?.perSecondText }}/S
</span>
<div class="progress-info-right">
<span v-if="item.installInfo?.status === EnumSoftwareInstallStatus.Downloading"> ↓{{ item.installInfo?.dlInfo?.perSecondText }}/S </span>
<span v-else>
{{ item.statusText }}
</span>
@@ -93,24 +87,22 @@
</div>
</div>

<a-card size='small'>
<div style='display: flex; justify-content: space-between'>
<a-button type='primary' :icon='h(AppstoreAddOutlined)' @click='localInstall'>
<a-card size="small">
<div style="display: flex; justify-content: space-between">
<a-button type="primary" :icon="h(AppstoreAddOutlined)" @click="localInstall">
{{ mt('Local', 'ws', 'Install') }}
</a-button>
<div style='display: flex; align-items: center'>
<div style="display: flex; align-items: center">
{{ t('installPackageDownloadUrl') }}:
<a style='margin: 0 10px' @click="openUrl('http://github.com/xianyunleo/EServerAppStore')">Github</a>
<a style="margin: 0 10px" @click="openUrl('http://github.com/xianyunleo/EServerAppStore')">Github</a>
<a @click="openUrl('https://gitee.com/xianyunleo/EServerAppStore')">Gitee</a>
</div>
</div>
</a-card>
</div>

<!-- v-if防止不显示就执行modal里面的代码-->
<php-ext-manager v-if='phpExtManagerShow' v-model:show='phpExtManagerShow' :phpVersion='phpVersion'>
</php-ext-manager>

<php-ext-manager v-if="phpExtManagerShow" v-model:show="phpExtManagerShow" :phpVersion="phpVersion"> </php-ext-manager>
</template>

<script setup>
@@ -175,13 +167,13 @@ const clickInstall = async (item) => {
}
switch (item.installInfo.status) {
case EnumSoftwareInstallStatus.Ready:
return '正在开始'
return t('Ready')
case EnumSoftwareInstallStatus.Downloading:
return '下载中'
return t('Downloading')
case EnumSoftwareInstallStatus.Extracting:
return '解压中'
return t('Unzipping')
case EnumSoftwareInstallStatus.Configuring:
return '配置中'
return t('Configuring')
default:
return ''
}
@@ -287,7 +279,7 @@ const localInstall = async () => {
MessageBox.info(dirName + mt('ws', 'Installed'))
return
}
store.loading = true;
store.loading = true
store.loadingTip = t('Installing')
await LocalInstall.install(path)
item.Installed = true
@@ -297,12 +289,12 @@ const localInstall = async () => {
} catch (error) {
MessageBox.error(error.message ?? error)
} finally {
store.loading = false;
store.loading = false
}
}
const showPhpExtManager = async (item) => {
if (isMacOS && !await SystemExtend.isInstalledBrew()) {
if (isMacOS && !(await SystemExtend.isInstalledBrew())) {
MessageBox.error(`Homebrew未安装!\n请复制命令到终端执行安装\n/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"`)
return
}
@@ -315,7 +307,7 @@ const openUrl = (url) => {
}
</script>
<style scoped lang='less'>
<style scoped lang="less">
@import '@/renderer/assets/css/var';
.category {
301 changes: 147 additions & 154 deletions src/renderer/views/Tool.vue
Original file line number Diff line number Diff line change
@@ -1,154 +1,147 @@
<template>
<div class="content-container" style='padding:0'>
<a-row>
<a-col :span="12" v-for="item in toolItems" :key="item.key">
<div class="tool-item piece piece-hover" @click="item.func">
<div class="tool-item-avatar">
<template v-if="item.iconType">
<template v-if="item.iconType === iconTypes.textFile">
<file-text-two-tone />
</template>
<template v-else-if="item.iconType === iconTypes.dir">
<folder-open-two-tone />
</template>
<template v-else-if="item.iconType === iconTypes.list">
<database-two-tone />
</template>

</template>
<template v-else>
<img :src="item.icon" alt="icon">
</template>

</div>
<div class="tool-item-content">
<h4 class="tool-item-title">{{ item.title }}</h4>
<div class="tool-item-desc">{{ item.desc }}</div>
</div>
</div>
</a-col>
</a-row>
</div>
<!-- v-if防止不显示就执行modal里面的代码-->
<mysql-reset-pwd-modal v-if="mysqlResetPwdModalShow" v-model:show="mysqlResetPwdModalShow">
</mysql-reset-pwd-modal>
<tcp-process-list-modal v-if="tcpProcessListModalShow" v-model:show="tcpProcessListModalShow">
</tcp-process-list-modal>
</template>

<script setup>
import { mt,t } from '@/renderer/utils/i18n'
import {ref} from "vue";
import {message} from 'ant-design-vue';
import {FileTextTwoTone,FolderOpenTwoTone,DatabaseTwoTone} from "@ant-design/icons-vue";
import MysqlResetPwdModal from "@/renderer/components/ToolPage/MysqlResetPwdModal.vue"
import MessageBox from "@/renderer/utils/MessageBox";
import GetPath from "@/shared/utils/GetPath";
import {sleep} from "@/shared/utils/utils";
import Native from "@/main/utils/Native";
import TcpProcessListModal from "@/renderer/components/ToolPage/TcpProcessListModal.vue";
import OS from "@/main/utils/OS";
import { isWindows } from '@/main/utils/utils'
const iconTypes = {
dir: 'dir',
file: 'file',
list: 'list',
textFile: 'textFile',
tool: 'tool',
}
const toolItems = [
{
key: 'editHosts',
iconType: iconTypes.textFile,
title: `${mt('Edit','ws')}hosts`,
desc: '查看、编辑hosts文件',
func: editHosts,
},
{
key: 'mysqlResetPwd',
icon: GetPath.getMysqlIconPath(),
title: t('mysqlResetRootAccountPwd'),
desc: '修改、重置MySQL的root账户的密码',
func: mysqlResetPwd,
},
{
key: 'tcpProcessList',
iconType: iconTypes.list,
title: `Tcp${mt('ws', 'Port', 'ws', 'Process', 'ws', 'List')}`,
desc: '查看端口占用情况',
func: tcpProcessList,
},
];
async function editHosts() {
message.info(t('pleaseWait'));
await sleep(100);
try {
await Native.openHosts();
} catch (error) {
MessageBox.error(error.message ?? error, '打开hosts文件出错!');
}
}
const mysqlResetPwdModalShow = ref(false);
function mysqlResetPwd() {
mysqlResetPwdModalShow.value = true;
}
const tcpProcessListModalShow = ref(false);
function tcpProcessList() {
if (isWindows && OS.getMajorVersion() <= 6.1) {
MessageBox.error('你的系统版本过低,此功能不可用!', '此功能不可用!');
return;
}
tcpProcessListModalShow.value = true;
}
</script>
<style scoped lang="less">
@import "@/renderer/assets/css/var";
.tool-item {
display: flex;
align-items: center;
justify-content: space-between;
margin: 10px 10px 0 10px;
padding: 12px 0;
cursor: pointer;
}
.tool-item-avatar {
margin-left: 16px;
margin-right: 16px;
display: flex;
> img {
width: 50px;
height: 50px;
}
> span {
font-size: 50px;
width: 50px;
height: 50px;
}
}
.tool-item-content {
flex: 1 0;
width: 0;
color: @colorText;
}
.tool-item-desc {
font-size: 14px;
color: @colorTextSecondary;
}
</style>
<template>
<div class="content-container" style="padding: 0">
<a-row>
<a-col :span="12" v-for="item in toolItems" :key="item.key">
<div class="tool-item piece piece-hover" @click="item.func">
<div class="tool-item-avatar">
<template v-if="item.iconType">
<template v-if="item.iconType === iconTypes.textFile">
<file-text-two-tone />
</template>
<template v-else-if="item.iconType === iconTypes.dir">
<folder-open-two-tone />
</template>
<template v-else-if="item.iconType === iconTypes.list">
<database-two-tone />
</template>
</template>
<template v-else>
<img :src="item.icon" alt="icon" />
</template>
</div>
<div class="tool-item-content">
<h4 class="tool-item-title">{{ item.title }}</h4>
<div class="tool-item-desc">{{ item.desc }}</div>
</div>
</div>
</a-col>
</a-row>
</div>
<!-- v-if防止不显示就执行modal里面的代码-->
<mysql-reset-pwd-modal v-if="mysqlResetPwdModalShow" v-model:show="mysqlResetPwdModalShow"> </mysql-reset-pwd-modal>
<tcp-process-list-modal v-if="tcpProcessListModalShow" v-model:show="tcpProcessListModalShow"> </tcp-process-list-modal>
</template>

<script setup>
import { mt, t } from '@/renderer/utils/i18n'
import { ref } from 'vue'
import { message } from 'ant-design-vue'
import { FileTextTwoTone, FolderOpenTwoTone, DatabaseTwoTone } from '@ant-design/icons-vue'
import MysqlResetPwdModal from '@/renderer/components/ToolPage/MysqlResetPwdModal.vue'
import MessageBox from '@/renderer/utils/MessageBox'
import GetPath from '@/shared/utils/GetPath'
import { sleep } from '@/shared/utils/utils'
import Native from '@/main/utils/Native'
import TcpProcessListModal from '@/renderer/components/ToolPage/TcpProcessListModal.vue'
import OS from '@/main/utils/OS'
import { isWindows } from '@/main/utils/utils'
const iconTypes = {
dir: 'dir',
file: 'file',
list: 'list',
textFile: 'textFile',
tool: 'tool'
}
const toolItems = [
{
key: 'editHosts',
iconType: iconTypes.textFile,
title: `${mt('Edit', 'ws')}hosts`,
desc: t('View and edit hosts file'),
func: editHosts
},
{
key: 'mysqlResetPwd',
icon: GetPath.getMysqlIconPath(),
title: t('mysqlResetRootAccountPwd'),
desc: t('Modify or reset the password of the MySQL root account'),
func: mysqlResetPwd
},
{
key: 'tcpProcessList',
iconType: iconTypes.list,
title: `Tcp${mt('ws', 'Port', 'ws', 'Process', 'ws', 'List')}`,
desc: t('Check port usage'),
func: tcpProcessList
}
]
async function editHosts() {
message.info(t('pleaseWait'))
await sleep(100)
try {
await Native.openHosts()
} catch (error) {
MessageBox.error(error.message ?? error, t('Error opening hosts file!'))
}
}
const mysqlResetPwdModalShow = ref(false)
function mysqlResetPwd() {
mysqlResetPwdModalShow.value = true
}
const tcpProcessListModalShow = ref(false)
function tcpProcessList() {
if (isWindows && OS.getMajorVersion() <= 6.1) {
MessageBox.error(t('Your system version is too low, this function is not available!'), t('This feature is not available!'))
return
}
tcpProcessListModalShow.value = true
}
</script>
<style scoped lang="less">
@import '@/renderer/assets/css/var';
.tool-item {
display: flex;
align-items: center;
justify-content: space-between;
margin: 10px 10px 0 10px;
padding: 12px 0;
cursor: pointer;
}
.tool-item-avatar {
margin-left: 16px;
margin-right: 16px;
display: flex;
> img {
width: 50px;
height: 50px;
}
> span {
font-size: 50px;
width: 50px;
height: 50px;
}
}
.tool-item-content {
flex: 1 0;
width: 0;
color: @colorText;
}
.tool-item-desc {
font-size: 14px;
color: @colorTextSecondary;
}
</style>
130 changes: 63 additions & 67 deletions src/renderer/views/Website.vue
Original file line number Diff line number Diff line change
@@ -1,78 +1,74 @@
<template>
<div class="content-container">
<a-card size="small">
<div class='web-header' >
<a-button type="primary" @click="showAdd">{{t("Add")}}</a-button>
<div class="web-header">
<a-button type="primary" @click="showAdd">{{ t('Add') }}</a-button>
<div>
{{mt('Show','ws', 'Column')}}:
<a-checkbox v-model:checked='store.websiteList.showSecondDomainName'>
{{ mt('Show', 'ws', 'Column') }}:
<a-checkbox v-model:checked="store.websiteList.showSecondDomainName">
{{ mt('Second', 'ws', 'DomainName') }}
</a-checkbox>
<a-checkbox v-model:checked="store.websiteList.showNote">
{{mt('Note')}}
{{ mt('Note') }}
</a-checkbox>
</div>
<input-with-search :placeholder="mt('Input','ws','DomainName')" style="width: 200px" @search="search" />
<input-with-search :placeholder="mt('Input', 'ws', 'DomainName')" style="width: 200px" @search="search" />
</div>
</a-card>

<a-table :scroll="{y: 'calc(100vh - 220px)'}"
:columns="columns"
:data-source="list"
class="content-table web-table scroller"
:pagination="pagination"
size="middle">
<template #bodyCell="{ column, text, record}">
<a-table :scroll="{ y: 'calc(100vh - 220px)' }" :columns="columns" :data-source="list" class="content-table web-table scroller" :pagination="pagination" size="middle">
<template #bodyCell="{ column, text, record }">
<template v-if="column.dataIndex === 'serverName'">
<a class='non-draggable color-text' @click='openUrl(record)'>{{ text }}</a>
<a class="non-draggable color-text" @click="openUrl(record)">{{ text }}</a>
</template>
<template v-if="column.dataIndex === 'rootPath'">
<a class='non-draggable color-text' @click='openRootPath(record)'>{{ text }}</a>
<a class="non-draggable color-text" @click="openRootPath(record)">{{ text }}</a>
</template>
<template v-if="column.dataIndex === 'operate'">
<div class="operate">
<a-dropdown :trigger="['click']">
<template #overlay>
<a-menu>
<a-menu-item @click="showEdit(record)">{{t('Modify')}}</a-menu-item>
<a-menu-item @click="del(record)">{{t('Delete')}}</a-menu-item>
<a-menu-item @click="openUrl(record)">{{mt('Open','ws')}}URL</a-menu-item>
<a-menu-item @click="openRootPath(record)">{{mt('Open','ws','RootPath')}}</a-menu-item>
<a-menu-item @click="showEdit(record)">{{ t('Modify') }}</a-menu-item>
<a-menu-item @click="del(record)">{{ t('Delete') }}</a-menu-item>
<a-menu-item @click="openUrl(record)">{{ mt('Open', 'ws') }}URL</a-menu-item>
<a-menu-item @click="openRootPath(record)">{{ mt('Open', 'ws', 'RootPath') }}</a-menu-item>
<a-menu-item @click="openConfFile(record)">
{{mt('Open','ws','Conf','ws','File')}}
{{ mt('Open', 'ws', 'Conf', 'ws', 'File') }}
</a-menu-item>
<a-menu-item @click="openRewriteConfFile(record)">
{{mt('Open','ws','UrlRewrite','ws','File')}}
{{ mt('Open', 'ws', 'UrlRewrite', 'ws', 'File') }}
</a-menu-item>
<a-menu-item @click="openAccessLog(record)">
{{mt('Open','ws','Access','ws','Log')}}
{{ mt('Open', 'ws', 'Access', 'ws', 'Log') }}
</a-menu-item>
<a-menu-item @click="openErrorLog(record)">
{{mt('Open','ws','Error','ws','Log')}}
{{ mt('Open', 'ws', 'Error', 'ws', 'Log') }}
</a-menu-item>
<!-- <a-menu-item >打开命令行终端</a-menu-item>-->
</a-menu>
</template>
<a-button>{{t('Manage')}}<DownOutlined/></a-button>
<a-button>{{ t('Manage') }}<DownOutlined /></a-button>
</a-dropdown>
</div>
</template>
</template>
</a-table>
</div>
<add-web-site-modal v-if="addModalVisible" />
<edit-web-site-modal v-if="editModalVisible" /> <!--加v-if是为了后代组件重新加载,从而更新网站配置信息-->
<edit-web-site-modal v-if="editModalVisible" />
<!--加v-if是为了后代组件重新加载,从而更新网站配置信息-->
</template>

<script setup>
import { ref, provide, computed } from 'vue'
import InputWithSearch from "@/renderer/components/Input/InputWithSearch.vue";
import AddWebSiteModal from "@/renderer/components/WebSite/AddWebSiteModal.vue";
import EditWebSiteModal from "@/renderer/components/WebSite/EditWebSiteModal.vue";
import Website from "@/main/core/website/Website";
import MessageBox from "@/renderer/utils/MessageBox";
import Native from "@/main/utils/Native";
import Hosts from "@/main/utils/Hosts";
import InputWithSearch from '@/renderer/components/Input/InputWithSearch.vue'
import AddWebSiteModal from '@/renderer/components/WebSite/AddWebSiteModal.vue'
import EditWebSiteModal from '@/renderer/components/WebSite/EditWebSiteModal.vue'
import Website from '@/main/core/website/Website'
import MessageBox from '@/renderer/utils/MessageBox'
import Native from '@/main/utils/Native'
import Hosts from '@/main/utils/Hosts'
import { mt, t } from '@/renderer/utils/i18n'
import { isWindows } from '@/main/utils/utils'
import { createAsyncComponent } from '@/renderer/utils/utils'
@@ -88,7 +84,7 @@ const DownOutlined = createAsyncComponent(import('@ant-design/icons-vue'), 'Down
const pagination = {
defaultPageSize: 10,
showSizeChanger: true,
showTotal: total => `Total ${total} items`
showTotal: (total) => `Total ${total} items`
}
const columns = computed(() => {
@@ -135,15 +131,15 @@ const columns = computed(() => {
width: 120,
align: 'center'
}
].filter(item => !item.hidden)
].filter((item) => !item.hidden)
})
const list = ref([]);
const confName = ref('');
const serverName = ref('');
const port = ref('');
const addModalVisible = ref(false);
const editModalVisible = ref(false);
const list = ref([])
const confName = ref('')
const serverName = ref('')
const port = ref('')
const addModalVisible = ref(false)
const editModalVisible = ref(false)
const search = async (val) => {
try {
@@ -154,86 +150,86 @@ const search = async (val) => {
}
list.value = tempList
} catch (error) {
MessageBox.error(error.message ?? error, '获取网站列表出错!');
MessageBox.error(error.message ?? error, t('Error getting site list!'))
}
}
provide('WebsiteProvide',{
provide('WebsiteProvide', {
confName,
serverName,
port,
search,
addModalVisible,
editModalVisible,
});
editModalVisible
})
search()
const del = async (item) => {
try {
let options = {
content:t('Delete')+` ${item.serverName}:${item.port}`,
okText:t('Confirm'),
cancelText:t('Cancel'),
};
content: t('Delete') + ` ${item.serverName}:${item.port}`,
okText: t('Confirm'),
cancelText: t('Cancel')
}
if (await MessageBox.confirm(options)) {
await Website.delete(item.confName);
await Website.delete(item.confName)
}
} catch (error) {
MessageBox.error(error.message ?? error, '删除出错!');
return;
MessageBox.error(error.message ?? error, '删除出错!')
return
}
if (item.syncHosts) {
const { serverName, extraServerName } = item
syncHosts(serverName, extraServerName)
}
search();
search()
}
async function syncHosts(serverName, extraServerName) {
try {
if (!await Website.exists(serverName)) {
await Hosts.delete(serverName);
if (!(await Website.exists(serverName))) {
await Hosts.delete(serverName)
}
if (extraServerName && !await Website.exists(extraServerName)) {
await Hosts.delete(extraServerName);
if (extraServerName && !(await Website.exists(extraServerName))) {
await Hosts.delete(extraServerName)
}
} catch {
/* empty */
}
}
const showAdd = () => {
addModalVisible.value = true;
};
addModalVisible.value = true
}
const showEdit = (item) => {
editModalVisible.value = true;
confName.value = item.confName;
serverName.value = item.serverName;
port.value = item.port;
editModalVisible.value = true
confName.value = item.confName
serverName.value = item.serverName
port.value = item.port
}
const openUrl = (item) => {
Native.openUrl(`http://${item.serverName}:${item.port}`);
Native.openUrl(`http://${item.serverName}:${item.port}`)
}
const openConfFile = async (item) => {
Native.openTextFile(Website.getConfPath(item.confName));
Native.openTextFile(Website.getConfPath(item.confName))
}
const openRewriteConfFile = (item) => {
Native.openTextFile(Website.getRewriteConfPath(item.confName));
Native.openTextFile(Website.getRewriteConfPath(item.confName))
}
const openRootPath = (item) => {
let path = item.rootPath;
let path = item.rootPath
if (isWindows) {
path = path.replaceAll('/', '\\')
}
Native.openDirectory(path);
Native.openDirectory(path)
}
const openAccessLog = (item) => {
52 changes: 44 additions & 8 deletions src/shared/i18n/en.js
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ export default {
Log: 'Log',
Basic: 'Basic',
Conf: 'Conf',
Column:'Column',
Column: 'Column',
File: 'File',
Name: 'Name',
Manage: 'Manage',
@@ -83,16 +83,16 @@ export default {
Editor: 'Editor',
Initialize: 'Initialize',
Set: 'Set',
Support:'Support',
Support: 'Support',
Wrong: 'Wrong',
Network: 'Network',
Info:'Info',
Info: 'Info',
Error: 'Error',
Warning: 'Warning',
Match:'Match',
Script:'Script',
Note:'Note',
Close:'Close',
Match: 'Match',
Script: 'Script',
Note: 'Note',
Close: 'Close',
Force: 'Force',
Certificate: 'Certificate',
//Lowercase
@@ -120,7 +120,7 @@ export default {
update: 'update',
configure: 'configure',
operation: 'operation',
system:'system',
system: 'system',
version: 'version',
goToSettings: 'Go to settings',
areYouSure: 'Are you sure?',
@@ -142,4 +142,40 @@ export default {
softwareUninstallErrorTip: 'Please open the file manager and manually delete the {0} directory',
mysqlResetRootAccountPwd: 'MySQL reset password for root account',
afterOpenAppStartServer: 'After opening the app, start the server',
integratedServiceEnvironment: 'Integrated Service Environment',
pleaseChoose: 'Please choose',
'MySQL Is a Relational Database': 'MySQL is a relational database',
'Web Server': 'Web Server',
'A high-performance Key-Value database': 'A high-performance Key-Value database',
'PHP is the best programming language in the world': 'PHP is the best programming language in the world',
'PHP dependency management tool': 'PHP dependency management tool (needs to be enabled in settings)',
'cross-platform text and code editor': 'Cross-platform text and code editor',
'Text and code editors': 'Text and code editors',
'MySQL management tool on the web': 'MySQL management tool on the web',
'View and edit hosts file': 'View and edit hosts file',
'Modify or reset the password of the MySQL root account': 'Modify or reset the password of the MySQL root account',
'Check port usage': 'Check port usage',
'Error opening hosts file!': 'Error opening hosts file!',
'Your system version is too low, this function is not available!': 'Your system version is too low, this function is not available!',
'This feature is not available!': 'This feature is not available!',
'Error starting service!': 'Error starting service!',
'The service was not stopped successfully!': 'The service was not stopped successfully!',
'Error stopping service!': 'Error stopping service!',
Ready: 'Ready',
Downloading: 'Downloading',
Unzipping: 'Unzipping',
Configuring: 'Configuring',
'Error getting site list!': 'Error getting site list!',
'Delete error!': 'Delete error!',
'The IIS service has been automatically stopped': 'The IIS service has been automatically stopped',
'The setting is successful and has taken effect. There is no need to restart the terminal!':
'The setting is successful and has taken effect. There is no need to restart the terminal!',
'The website directory cannot have spaces!': 'The website directory cannot have spaces!',
'Initialization successful': 'Initialization successful',
'Initialization failed!': 'Initialization failed!',
'Error adding website!': 'Error adding website!',
'file does not exist': 'file does not exist',
'Error opening file!': 'Error opening file!',
'does not exist!': 'does not exist!',
'Please reset the text editor': 'Please reset the text editor'
}
181 changes: 181 additions & 0 deletions src/shared/i18n/fr.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
export default {
ws: ' ', // Word Separator
// Uppercase
Application: 'Application',
Add: 'Ajouter',
Delete: 'Supprimer',
Modify: 'Modifier',
Access: 'Accès',
Log: 'Journal',
Basic: 'Basque',
Conf: 'Conf',
Column: 'Colonne',
File: 'Fichier',
Name: 'Nom',
Manage: 'Gérer',
Manager: 'Gestionnaire',
Input: 'Entrée',
Initializing: 'Initialisation en cours',
Installing: 'Installation en cours',
Directory: 'Répertoire',
Home: 'Accueil',
Website: 'Site Web',
Tools: 'Boîte à outils',
AppStore: "Magasin d'applications",
Settings: 'Paramètres',
Auto: 'Auto',
About: 'À propos',
Start: 'Démarrer',
Stop: 'Arrêter',
Restart: 'Redémarrer',
Installed: 'Installé',
Server: 'Serveur',
Tool: 'Outil',
Port: 'Port',
RootPath: 'Chemin racine',
DomainName: 'Nom de domaine',
Sync: 'Synchroniser',
Icon: 'Icône',
Path: 'Chemin',
Status: 'Statut',
Operation: 'Opération',
OneClick: 'En un clic',
ShortcutActions: 'Raccourcis',
Version: 'Version',
Open: 'Ouvrir',
Exit: 'Quitter',
Save: 'Enregistrer',
Show: 'Afficher',
Install: 'Installer',
Extension: 'Extension',
Uninstall: 'Désinstaller',
Refresh: 'Actualiser',
RefreshingServer: 'Actualisation du serveur en cours',
Language: 'Langue',
Local: 'Local',
Confirm: 'Confirmer',
Submit: 'Soumettre',
Cancel: 'Annuler',
UrlRewrite: "Réécriture d'URL",
Second: 'Deuxième',
Static: 'Statique',
Current: 'Actuel',
Reset: 'Réinitialiser',
Pwd: 'Mot de passe',
Edit: 'Modifier',
New: 'Nouveau',
Please: 'Veuillez',
Select: 'Sélectionner',
Kill: 'Tuer',
Doc: 'Doc',
OfficialSite: 'Site officiel',
Desc: 'Description',
List: 'Liste',
Process: 'Processus',
Mode: 'Mode',
Theme: 'Thème',
Color: 'Couleur',
Enable: 'Activer',
Not: 'Non',
EnvironmentVariables: "Variables d'environnement",
Other: 'Autre',
Text: 'Texte',
Editor: 'Éditeur',
Initialize: 'Initialiser',
User: 'Utilisateur',
Set: 'Défini',
Support: 'Support',
Wrong: 'Errone',
Network: 'Réseau',
Info: 'Informations',
Error: 'Erreur',
Warning: 'Avertissement',
Match: 'Correspondance',
Script: 'Script',
Note: 'Remarque',
Close: 'Fermer',
Force: 'Forcer',
Certificate: 'Certificat', // Lowercase
none: 'Aucun',
auto: 'Automatique',
dark: 'Sombre',
light: 'Clair',
blue: 'Bleu',
green: 'Vert',
red: 'Rouge',
pink: 'Rose',
cyan: 'Cyan',
purple: 'Violet',
notice: 'Avis',
tool: 'Outil',
root: 'Racine',
name: 'Nom',
desc: 'Description', // Changed "desc" to "Description" for better clarity
sync: 'Synchroniser',
set: 'Définir',
uninstall: 'Désinstaller',
initializing: 'Initialisation en cours',
download: 'Télécharger',
uncompress: 'Décompresser',
update: 'Mettre à jour',
configure: 'Configurer',
operation: 'Opération',
system: 'Système',
version: 'Version',
goToSettings: 'Aller aux paramètres',
areYouSure: 'Êtes-vous sûr ?',
pleaseWait: 'Veuillez patienter...',
cannotBeEmpty: 'Ne peut pas être vide',
mysqlResetPwdTip: "Arrêtez d'abord le service MySQL, puis réinitialisez le mot de passe.",
defaultIsEmpty: 'Par défaut, il est vide.',
oneClickStartAndStop: 'Démarrer et arrêter en un clic',
successfulOperation: 'Opération réussie.',
failedOperation: "Échec de l'opération.",
errorOccurredDuring: "Une erreur s'est produite pendant {0}.",
needRestartTerminal: 'Vous devez redémarrer le terminal.',
websiteAutoRestartText: 'Après avoir enregistré le site Web, démarrez ou redémarrez automatiquement le service.',
initConfirmText: 'Cette opération réinitialisera les fichiers de configuration PHP et Server. Confirmez-vous ?',
theUserPwdForThisComputer: 'Le mot de passe utilisateur de cet ordinateur.',
thisPwdIsUsedForSudoCommands: 'Ce mot de passe est utilisé pour les commandes sudo.',
pathCannotContainSpaces: "Le chemin ne peut pas contenir d'espaces.",
installPackageDownloadUrl: "URL de téléchargement du package d'installation.",
softwareUninstallErrorTip: 'Veuillez ouvrir le gestionnaire de fichiers et supprimer manuellement le répertoire {0}.',
mysqlResetRootAccountPwd: 'Réinitialiser le mot de passe du compte root MySQL.',
afterOpenAppStartServer: "Démarrez le service après avoir ouvert l'application.",
integratedServiceEnvironment: 'Environnement de services intégrés',
pleaseChoose: "Choisissez s'il vous plaît",
'MySQL Is a Relational Database': 'MySQL is a relational database',
'Web Server': 'Serveur Web',
'A high-performance Key-Value database': 'Une base de données clé-valeur performante',
'PHP is the best programming language in the world': 'PHP est le meilleur langage de programmation au monde',
'PHP dependency management tool': 'Outil de gestion des dépendances PHP (doit être activé dans les paramètres)',
'cross-platform text and code editor': 'Éditeur de texte et de code multiplateformes',
'Text and code editors': 'Éditeur de texte et de code',
'MySQL management tool on the web': 'Outil de gestion MySQL sur le Web',
'View and edit hosts file': 'Afficher et modifier les fichiers hôtes',
'Modify or reset the password of the MySQL root account': 'Modifier et réinitialiser le mot de passe du compte root MySQL',
'Check port usage': "Vérifier l'occupation du port",
'Error opening hosts file!': "Erreur lors de l'ouverture du fichier hosts !",
'Your system version is too low, this function is not available!': "La version de votre système est trop basse et cette fonction n'est pas disponible !",
'This feature is not available!': "Cette fonctionnalité n'est pas disponible !",
'Error starting service!': 'Erreur lors du démarrage du service !',
'The service was not stopped successfully!': "Le service n'a pas été arrêté avec succès !",
'Error stopping service!': "Une erreur s'est produite lors de l'arrêt du service !",
Ready: 'Prêt',
Downloading: 'Téléchargement',
Unzipping: 'Décompression',
Configuring: 'Configuration',
'Error getting site list!': "Erreur lors de l'obtention de la liste des sites Web !",
'Delete error!': "Supprimez l'erreur !",
'The IIS service has been automatically stopped': 'Le service IIS a été automatiquement arrêté',
'The setting is successful and has taken effect. There is no need to restart the terminal!':
'Le paramétrage est réussi et a pris effet. Pas besoin de redémarrer le terminal !',
'The website directory cannot have spaces!': "Le répertoire du site Web ne peut pas contenir d'espaces !",
'Initialization successful': 'Initialisation réussie',
'Initialization failed!': "L'initialisation a échoué !",
'Error adding website!': "Erreur lors de l'ajout du site Web !",
'file does not exist': 'fichier ne existe pas',
'Error opening file!': "Erreur lors de l'ouverture du fichier !",
'does not exist!': "n'existe pas!",
'Please reset the text editor': 'Veuillez réinitialiser votre éditeur de texte'
}
51 changes: 43 additions & 8 deletions src/shared/i18n/zh.js
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ export default {
Log: '日志',
Basic: '基本',
Conf: '配置',
Column:'列',
Column: '列',
File: '文件',
Name: '名称',
Manage: '管理',
@@ -84,16 +84,16 @@ export default {
Initialize: '初始化',
User: '用户',
Set: '设置',
Support:'支持',
Support: '支持',
Wrong: '错误的',
Network: '网络',
Info:'信息',
Info: '信息',
Error: '错误',
Warning: '警告',
Match:'匹配',
Script:'脚本',
Note:'备注',
Close:'关闭',
Match: '匹配',
Script: '脚本',
Note: '备注',
Close: '关闭',
Force: '强制',
Certificate: '证书',
//Lowercase
@@ -121,7 +121,7 @@ export default {
update: '更新',
configure: '配置',
operation: '操作',
system:'系统',
system: '系统',
version: '版本',
goToSettings: '前往设置',
areYouSure: '你确定吗?',
@@ -143,4 +143,39 @@ export default {
softwareUninstallErrorTip: '请打开文件管理器,手动删除{0}目录',
mysqlResetRootAccountPwd: 'MySQL 重置 root 账户的密码',
afterOpenAppStartServer: '打开软件后,启动服务',
integratedServiceEnvironment: '打开软件后,启动服务',
pleaseChoose: '请选择',
'MySQL Is a Relational Database': 'MySQL是一种关系数据库',
'Web Server': 'Web服务器',
'A high-performance Key-Value database': '一款高性能的Key-Value数据库',
'PHP is the best programming language in the world': 'PHP是世界上最好的编程语言',
'PHP dependency management tool': 'PHP依赖管理工具(需要在设置里启用)',
'cross-platform text and code editor': '国产跨平台文本、代码编辑器',
'Text and code editors': '文本、代码编辑器',
'MySQL management tool on the web': 'Web端的MySQL管理工具',
'View and edit hosts file': '查看、编辑hosts文件',
'Modify or reset the password of the MySQL root account': '修改、重置MySQL的root账户的密码',
'Check port usage': '查看端口占用情况',
'Error opening hosts file!': '打开hosts文件出错!',
'Your system version is too low, this function is not available!': '你的系统版本过低,此功能不可用!',
'This feature is not available!': '此功能不可用!',
'Error starting service!': '启动服务出错!',
'The service was not stopped successfully!': '服务没有成功停止!',
'Error stopping service!': '停止服务出错!',
Ready: '正在开始',
Downloading: '下载中',
Unzipping: '解压中',
Configuring: '配置中',
'Error getting site list!': '获取网站列表出错!',
'Delete error!': '删除出错!',
'The IIS service has been automatically stopped': '已自动停止IIS服务',
'The setting is successful and has taken effect. There is no need to restart the terminal!': '设置成功,已生效,不需要重启终端!',
'The website directory cannot have spaces!': '网站目录不能有空格!',
'Initialization successful': '初始化成功',
'Initialization failed!': '初始化失败!',
'Error adding website!': '添加网站出错!',
'file does not exist': '文件不存在',
'Error opening file!': '打开文件出错!',
'does not exist!': '不存在!',
'Please reset the text editor': '请重新设置文本编辑器'
}

0 comments on commit 618160a

Please sign in to comment.