From 25c3ad685a1af13c11cb02f5cedc967ea620ee54 Mon Sep 17 00:00:00 2001 From: kevin Date: Tue, 7 Jul 2026 15:05:53 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=89=8D=E7=AB=AF=E5=A4=9A=E8=AF=AD?= =?UTF-8?q?=E8=A8=80=E6=94=AF=E6=8C=81=EF=BC=88=E4=B8=AD/=E8=8B=B1?= =?UTF-8?q?=EF=BC=89=E4=B8=8E=E5=93=81=E7=89=8C=20Logo=20=E6=8E=A5?= =?UTF-8?q?=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 多语言: - 新增 vue-i18n,创建 src/locales/zh.ts、en.ts 语言包,按功能模块组织 key - 新增 src/i18n.ts,语言检测优先级:localStorage > navigator.language(zh→中文,其他→英文)> 兜底英文 - 导航栏新增语言切换按钮(中/EN 一键切换),选择持久化到 localStorage - 全部 View/Component 硬编码中文替换为 t() 调用,stores/auth.ts 错误消息同样国际化 - index.html lang 属性运行时动态更新 Logo: - 替换 Vue 默认 logo 为 LmVPN 品牌 Logo(盾牌+网络节点) - Header 导航栏:Logo 图标 + "LmVPN" 文字 - 首页标题区:大尺寸 Logo 展示 - Favicon 替换为 SVG 格式 --- frontend/index.html | 4 +- frontend/package-lock.json | 89 +++++++++++++ frontend/package.json | 1 + frontend/public/favicon.svg | 18 +++ frontend/src/App.vue | 28 +++-- frontend/src/assets/logo.svg | 19 ++- frontend/src/components/HelloWorld.vue | 12 +- frontend/src/components/TheWelcome.vue | 19 +-- frontend/src/i18n.ts | 32 +++++ frontend/src/locales/en.ts | 167 +++++++++++++++++++++++++ frontend/src/locales/index.ts | 11 ++ frontend/src/locales/zh.ts | 163 ++++++++++++++++++++++++ frontend/src/main.ts | 2 + frontend/src/stores/auth.ts | 3 +- frontend/src/views/AboutView.vue | 11 +- frontend/src/views/AdminView.vue | 30 ++--- frontend/src/views/LoginView.vue | 18 +-- frontend/src/views/ProfileView.vue | 32 ++--- frontend/src/views/UserManageView.vue | 98 ++++++++------- frontend/src/views/VpnView.vue | 149 +++++++++++----------- 20 files changed, 721 insertions(+), 185 deletions(-) create mode 100644 frontend/public/favicon.svg create mode 100644 frontend/src/i18n.ts create mode 100644 frontend/src/locales/en.ts create mode 100644 frontend/src/locales/index.ts create mode 100644 frontend/src/locales/zh.ts diff --git a/frontend/index.html b/frontend/index.html index cde5cd8..b05ed99 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,8 +1,8 @@ - + - + LmVPN diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7d96d56..8c68359 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "pinia": "^3.0.4", "vue": "^3.5.38", + "vue-i18n": "^11.4.6", "vue-router": "^5.1.0" }, "devDependencies": { @@ -518,6 +519,67 @@ "tslib": "^2.4.0" } }, + "node_modules/@intlify/core-base": { + "version": "11.4.6", + "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.4.6.tgz", + "integrity": "sha512-EOeHO95XESK9IFHgHeZXunsM/WBAoCA0DlaWODvx14vKmetAuS97t+l6Xe9hTUqntPpF93vtVSjjUDafw3wXMw==", + "license": "MIT", + "dependencies": { + "@intlify/devtools-types": "11.4.6", + "@intlify/message-compiler": "11.4.6", + "@intlify/shared": "11.4.6" + }, + "engines": { + "node": ">= 22" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/devtools-types": { + "version": "11.4.6", + "resolved": "https://registry.npmjs.org/@intlify/devtools-types/-/devtools-types-11.4.6.tgz", + "integrity": "sha512-wowQPpNem56b2d43IJmqbrzG2FeBKe5f/kUGlpNuBmXs6OSqncF8m1+1lxHuW8ISZJF0ma2RkW3iLkw0g0G4VA==", + "license": "MIT", + "dependencies": { + "@intlify/core-base": "11.4.6", + "@intlify/shared": "11.4.6" + }, + "engines": { + "node": ">= 22" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/message-compiler": { + "version": "11.4.6", + "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-11.4.6.tgz", + "integrity": "sha512-5nj3jULqeTAC1WovwMs1LQWgatTa2pM/rXN9T3XW8rdOtXW9ZF6/GLSNFTKDQmPLwclhPdgUWLJ/4w3fMeeC/Q==", + "license": "MIT", + "dependencies": { + "@intlify/shared": "11.4.6", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 22" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/shared": { + "version": "11.4.6", + "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-11.4.6.tgz", + "integrity": "sha512-m1p1HHAMLhqSpTRH7VnXdrN0CQ4y+9vunFkpLkbD8soIuBsnQdawZXqMCgvwI2UVF9Ww7sVaw7g9tV2VO7shoA==", + "license": "MIT", + "engines": { + "node": ">= 22" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -3343,6 +3405,33 @@ } } }, + "node_modules/vue-i18n": { + "version": "11.4.6", + "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.4.6.tgz", + "integrity": "sha512-l0gE7Rfy0phCa5ChKYkOq543Wgd39BCK6hkktfr1Ed4D99oRkgPK9ffShASZdeC8OJxGfdWmpYoAaAH6iLEuIg==", + "license": "MIT", + "dependencies": { + "@intlify/core-base": "11.4.6", + "@intlify/devtools-types": "11.4.6", + "@intlify/shared": "11.4.6", + "@vue/devtools-api": "^6.5.0" + }, + "engines": { + "node": ">= 22" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/vue-i18n/node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, "node_modules/vue-router": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.1.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 6c6de0e..0fdc870 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,6 +13,7 @@ "dependencies": { "pinia": "^3.0.4", "vue": "^3.5.38", + "vue-i18n": "^11.4.6", "vue-router": "^5.1.0" }, "devDependencies": { diff --git a/frontend/public/favicon.svg b/frontend/public/favicon.svg new file mode 100644 index 0000000..d93c953 --- /dev/null +++ b/frontend/public/favicon.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 7eb0bd9..125c9a8 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1,9 +1,13 @@ @@ -20,7 +24,8 @@ const navLinks = [
- + + LmVPN LmVPN
diff --git a/frontend/src/assets/logo.svg b/frontend/src/assets/logo.svg index 7565660..d93c953 100644 --- a/frontend/src/assets/logo.svg +++ b/frontend/src/assets/logo.svg @@ -1 +1,18 @@ - + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/components/HelloWorld.vue b/frontend/src/components/HelloWorld.vue index 2b0adac..cf34c28 100644 --- a/frontend/src/components/HelloWorld.vue +++ b/frontend/src/components/HelloWorld.vue @@ -1,17 +1,19 @@ - - 同时连接多台设备,手机、电脑、路由器全平台覆盖。 + + {{ t('home.multiDeviceDesc') }} @@ -30,8 +33,8 @@ import WelcomeItem from './WelcomeItem.vue' - - 实时流量统计与带宽分析,随时掌握网络使用状况。 + + {{ t('home.trafficMonitorDesc') }} @@ -41,8 +44,8 @@ import WelcomeItem from './WelcomeItem.vue' - - 一键部署、自动配置,无需复杂网络知识即可上线。 + + {{ t('home.easyConfigDesc') }}
diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts new file mode 100644 index 0000000..fb53384 --- /dev/null +++ b/frontend/src/i18n.ts @@ -0,0 +1,32 @@ +import { createI18n } from 'vue-i18n' +import { messages, type Locale } from './locales' + +const STORAGE_KEY = 'lang' + +function detectLocale(): Locale { + const saved = localStorage.getItem(STORAGE_KEY) + if (saved === 'zh' || saved === 'en') return saved + const navLang = navigator.language?.toLowerCase() ?? '' + return navLang.startsWith('zh') ? 'zh' : 'en' +} + +const i18n = createI18n({ + legacy: false, + locale: detectLocale(), + fallbackLocale: 'en', + messages, +}) + +export function setLocale(locale: Locale) { + i18n.global.locale.value = locale + localStorage.setItem(STORAGE_KEY, locale) + document.documentElement.lang = locale === 'zh' ? 'zh-CN' : 'en' +} + +export function toggleLocale() { + setLocale(i18n.global.locale.value === 'zh' ? 'en' : 'zh') +} + +document.documentElement.lang = i18n.global.locale.value === 'zh' ? 'zh-CN' : 'en' + +export default i18n diff --git a/frontend/src/locales/en.ts b/frontend/src/locales/en.ts new file mode 100644 index 0000000..ca03005 --- /dev/null +++ b/frontend/src/locales/en.ts @@ -0,0 +1,167 @@ +export default { + common: { + cancel: 'Cancel', + confirm: 'OK', + save: 'Save', + saving: 'Saving...', + delete: 'Delete', + deleting: 'Deleting...', + edit: 'Edit', + loading: 'Loading...', + loadFailed: 'Failed to load', + saveFailed: 'Failed to save', + createFailed: 'Failed to create', + deleteFailed: 'Failed to delete', + role: 'Role', + status: 'Status', + admin: 'Admin', + normalUser: 'User', + enabled: 'Enabled', + disabled: 'Disabled', + username: 'Username', + password: 'Password', + createdAt: 'Created At', + actions: 'Actions', + id: 'ID', + }, + nav: { + home: 'Home', + about: 'About', + profile: 'Profile', + admin: 'Admin', + logout: 'Logout', + login: 'Login', + }, + language: { + label: 'Language', + zh: '中文', + en: 'English', + }, + login: { + title: 'LmVPN Login', + enterUsernamePassword: 'Please enter username and password', + enterUsername: 'Enter username', + enterPassword: 'Enter password', + loginFailed: 'Login failed', + loggingIn: 'Logging in...', + loginButton: 'Login', + }, + profile: { + title: 'Profile', + basicInfo: 'Basic Info', + usernameLabel: 'Username: ', + roleLabel: 'Role: ', + changePassword: 'Change Password', + oldPassword: 'Current Password', + newPassword: 'New Password', + confirmNewPassword: 'Confirm New Password', + enterOldAndNewPassword: 'Please enter current and new password', + passwordsDoNotMatch: 'The two passwords do not match', + passwordChangeFailed: 'Failed to change password', + }, + about: { + title: 'About LmVPN', + description: + 'LmVPN is a lightweight VPN tunnel management system built with Go + Vue 3. It supports multiple VPN protocols and provides secure and reliable point-to-point and point-to-site networking solutions.', + }, + admin: { + title: 'Admin Dashboard', + uptime: 'Uptime', + activeDevices: 'Active Devices', + todayTraffic: "Today's Traffic", + onlineNodes: 'Online Nodes', + totalUsers: 'Total Users', + vpnManage: 'VPN Management', + config: 'Config', + userInfo: 'User Info', + usernameLabel: 'Username: ', + roleLabel: 'Role: ', + logoutButton: 'Logout', + }, + userManage: { + title: 'User Management', + addUser: 'Add User', + fillUsernamePassword: 'Please enter username and password', + creating: 'Creating...', + editUser: 'Edit User - {username}', + newPasswordOptional: 'New Password (leave empty to skip)', + newPasswordPlaceholder: 'Leave empty to keep current password', + confirmDelete: 'Confirm Delete', + confirmDeleteMessage: + 'Are you sure you want to delete user {username}? This action cannot be undone.', + confirmDeleteButton: 'Confirm Delete', + }, + vpn: { + title: 'VPN Management', + refresh: 'Refresh', + serviceStatus: 'Service Status', + running: 'Running', + stopped: 'Stopped', + onlineClients: 'Online Clients', + ipUsage: 'IP Usage', + systemCheck: 'System Diagnostics', + tunDevice: 'TUN Device', + tunRunning: 'Running', + rootPermission: 'Root Permission', + runningAsRoot: 'Running as root', + notRunningAsRoot: 'Not running as root', + capNetAdmin: 'CAP_NET_ADMIN', + authorized: 'Authorized', + unauthorized: 'Not authorized', + ipForward: 'IP Forward (ip_forward)', + notEnabled: 'Not enabled', + natMasqueradeV4: 'NAT MASQUERADE (IPv4)', + configured: 'Configured', + notConfigured: 'Not configured', + ipv6Forward: 'IPv6 Forward (forwarding)', + natMasqueradeV6: 'NAT MASQUERADE (IPv6)', + platform: 'Platform', + nonLinuxNote: + 'Note: CAP_NET_ADMIN / ip_forward / MASQUERADE checks only apply to Linux. For other platforms, verify network configuration manually.', + tunnelSettings: 'Tunnel Settings', + enableVpn: 'Enable VPN Service', + enable: 'Enable', + stop: 'Stop', + subnet: 'Subnet (CIDR)', + ipv6Subnet: 'IPv6 Subnet (CIDR, optional)', + mtu: 'MTU', + interfaceName: 'Interface Name (auto if empty)', + allowClientToClient: 'Allow Client-to-Client', + allow: 'Allow', + deny: 'Deny', + serverConfigTunIp: 'Server Configures TUN IP', + autoConfig: 'Auto', + manual: 'Manual', + saveSettings: 'Save Settings', + saveSuccess: 'Saved successfully', + user: 'User', + ipv4: 'IPv4', + ipv6: 'IPv6', + connectTime: 'Connected At', + noOnlineClients: 'No online clients', + staticIpReservation: 'Static IP Reservation', + addReservation: 'Add Reservation', + noReservations: 'No reservations', + addIpReservation: 'Add IP Reservation', + selectUser: 'Select user', + ipv4Address: 'IPv4 Address (optional)', + ipv6Address: 'IPv6 Address (optional)', + selectUserAndIp: 'Please select a user and fill in at least one IP address', + confirmDeleteReservation: 'Delete this reservation?', + }, + home: { + tagline: 'Secure, fast, and reliable VPN tunnel management system', + secureTunnel: 'Secure Tunnel', + secureTunnelDesc: + 'Based on WireGuard/OpenVPN protocols with end-to-end encryption to keep your data safe.', + multiDevice: 'Multi-Device Support', + multiDeviceDesc: + 'Connect multiple devices simultaneously — phones, computers, and routers all supported.', + trafficMonitor: 'Traffic Monitoring', + trafficMonitorDesc: + 'Real-time traffic statistics and bandwidth analysis to stay on top of network usage.', + easyConfig: 'Easy Configuration', + easyConfigDesc: + 'One-click deployment with auto-configuration — no advanced networking knowledge required.', + }, +} diff --git a/frontend/src/locales/index.ts b/frontend/src/locales/index.ts new file mode 100644 index 0000000..65941b6 --- /dev/null +++ b/frontend/src/locales/index.ts @@ -0,0 +1,11 @@ +import zh from './zh' +import en from './en' + +export type MessageSchema = typeof zh + +export const messages = { + zh, + en, +} as const + +export type Locale = keyof typeof messages diff --git a/frontend/src/locales/zh.ts b/frontend/src/locales/zh.ts new file mode 100644 index 0000000..cc95872 --- /dev/null +++ b/frontend/src/locales/zh.ts @@ -0,0 +1,163 @@ +export default { + common: { + cancel: '取消', + confirm: '确定', + save: '保存', + saving: '保存中...', + delete: '删除', + deleting: '删除中...', + edit: '编辑', + loading: '加载中...', + loadFailed: '加载失败', + saveFailed: '保存失败', + createFailed: '创建失败', + deleteFailed: '删除失败', + role: '角色', + status: '状态', + admin: '管理员', + normalUser: '普通用户', + enabled: '启用', + disabled: '禁用', + username: '用户名', + password: '密码', + createdAt: '创建时间', + actions: '操作', + id: 'ID', + }, + nav: { + home: '首页', + about: '关于', + profile: '用户信息', + admin: '管理后台', + logout: '退出', + login: '登录', + }, + language: { + label: '语言', + zh: '中文', + en: 'English', + }, + login: { + title: 'LmVPN 登录', + enterUsernamePassword: '请输入用户名和密码', + enterUsername: '请输入用户名', + enterPassword: '请输入密码', + loginFailed: '登录失败', + loggingIn: '登录中...', + loginButton: '登录', + }, + profile: { + title: '用户信息', + basicInfo: '基本信息', + usernameLabel: '用户名:', + roleLabel: '角色:', + changePassword: '修改密码', + oldPassword: '原密码', + newPassword: '新密码', + confirmNewPassword: '确认新密码', + enterOldAndNewPassword: '请填写原密码和新密码', + passwordsDoNotMatch: '两次输入的新密码不一致', + passwordChangeFailed: '密码修改失败', + }, + about: { + title: '关于 LmVPN', + description: + 'LmVPN 是一款轻量级 VPN 隧道管理系统,采用 Go + Vue 3 技术栈构建。支持多种 VPN 协议,提供安全可靠的点对点与点对站点网络连接方案。', + }, + admin: { + title: '管理后台', + uptime: '运行时长', + activeDevices: '活跃设备', + todayTraffic: '今日流量', + onlineNodes: '在线节点', + totalUsers: '用户总数', + vpnManage: 'VPN 管理', + config: '配置', + userInfo: '用户信息', + usernameLabel: '用户名:', + roleLabel: '角色:', + logoutButton: '退出登录', + }, + userManage: { + title: '用户管理', + addUser: '新增用户', + fillUsernamePassword: '请填写用户名和密码', + creating: '创建中...', + editUser: '编辑用户 - {username}', + newPasswordOptional: '新密码(留空不修改)', + newPasswordPlaceholder: '留空则不修改密码', + confirmDelete: '确认删除', + confirmDeleteMessage: '确定要删除用户 {username} 吗?此操作不可撤销。', + confirmDeleteButton: '确认删除', + }, + vpn: { + title: 'VPN 管理', + refresh: '刷新', + serviceStatus: '服务状态', + running: '运行中', + stopped: '已停止', + onlineClients: '在线客户端', + ipUsage: 'IP 用量', + systemCheck: '系统环境检测', + tunDevice: 'TUN 设备', + tunRunning: '运行中', + rootPermission: 'Root 权限', + runningAsRoot: '以 root 运行', + notRunningAsRoot: '非 root 运行', + capNetAdmin: 'CAP_NET_ADMIN', + authorized: '已授权', + unauthorized: '未授权', + ipForward: 'IP 转发 (ip_forward)', + notEnabled: '未开启', + natMasqueradeV4: 'NAT MASQUERADE (IPv4)', + configured: '已配置', + notConfigured: '未配置', + ipv6Forward: 'IPv6 转发 (forwarding)', + natMasqueradeV6: 'NAT MASQUERADE (IPv6)', + platform: '平台', + nonLinuxNote: + '注:CAP_NET_ADMIN / ip_forward / MASQUERADE 检测仅适用于 Linux,其他平台需手动确认网络配置。', + tunnelSettings: '隧道设置', + enableVpn: '启用 VPN 服务', + enable: '启用', + stop: '停止', + subnet: '子网 (CIDR)', + ipv6Subnet: 'IPv6 子网 (CIDR, 可选)', + mtu: 'MTU', + interfaceName: '接口名 (留空自动)', + allowClientToClient: '允许客户端互通', + allow: '允许', + deny: '禁止', + serverConfigTunIp: '服务端配置 TUN IP', + autoConfig: '自动配置', + manual: '手动', + saveSettings: '保存设置', + saveSuccess: '保存成功', + user: '用户', + ipv4: 'IPv4', + ipv6: 'IPv6', + connectTime: '连接时间', + noOnlineClients: '暂无在线客户端', + staticIpReservation: '静态 IP 预留', + addReservation: '新增预留', + noReservations: '暂无预留', + addIpReservation: '新增 IP 预留', + selectUser: '选择用户', + ipv4Address: 'IPv4 地址 (可选)', + ipv6Address: 'IPv6 地址 (可选)', + selectUserAndIp: '请选择用户并至少填写一个 IP 地址', + confirmDeleteReservation: '确认删除该预留?', + }, + home: { + tagline: '安全、快速、可靠的 VPN 隧道管理系统', + secureTunnel: '安全隧道', + secureTunnelDesc: + '基于 WireGuard/OpenVPN 协议,端到端加密传输,保障数据安全无泄漏。', + multiDevice: '多设备支持', + multiDeviceDesc: '同时连接多台设备,手机、电脑、路由器全平台覆盖。', + trafficMonitor: '流量监控', + trafficMonitorDesc: '实时流量统计与带宽分析,随时掌握网络使用状况。', + easyConfig: '简易配置', + easyConfigDesc: '一键部署、自动配置,无需复杂网络知识即可上线。', + }, +} diff --git a/frontend/src/main.ts b/frontend/src/main.ts index 5dcad83..12de954 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -5,10 +5,12 @@ import { createPinia } from 'pinia' import App from './App.vue' import router from './router' +import i18n from './i18n' const app = createApp(App) app.use(createPinia()) +app.use(i18n) app.use(router) app.mount('#app') diff --git a/frontend/src/stores/auth.ts b/frontend/src/stores/auth.ts index 111656b..907dc20 100644 --- a/frontend/src/stores/auth.ts +++ b/frontend/src/stores/auth.ts @@ -1,5 +1,6 @@ import { ref, computed } from 'vue' import { defineStore } from 'pinia' +import i18n from '@/i18n' interface UserInfo { id: number @@ -33,7 +34,7 @@ export const useAuthStore = defineStore('auth', () => { }) if (!res.ok) { const data = await res.json() - throw new Error(data.error || '登录失败') + throw new Error(data.error || i18n.global.t('login.loginFailed')) } const data = await res.json() setAuth(data.token, data.user) diff --git a/frontend/src/views/AboutView.vue b/frontend/src/views/AboutView.vue index 606ee78..0b6f2f9 100644 --- a/frontend/src/views/AboutView.vue +++ b/frontend/src/views/AboutView.vue @@ -1,9 +1,14 @@ + + diff --git a/frontend/src/views/AdminView.vue b/frontend/src/views/AdminView.vue index 7517ad3..c46f914 100644 --- a/frontend/src/views/AdminView.vue +++ b/frontend/src/views/AdminView.vue @@ -1,17 +1,19 @@