diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/App.js | 23 | ||||
| -rw-r--r-- | src/components/Card.js | 31 | ||||
| -rw-r--r-- | src/i18n.js | 64 | ||||
| -rw-r--r-- | src/index.js | 1 |
4 files changed, 97 insertions, 22 deletions
| @@ -1,25 +1,32 @@ | |||
| 1 | import React from 'react'; | 1 | import React from 'react'; |
| 2 | import { useTranslation } from 'react-i18next'; | ||
| 2 | import { Card } from './components/Card'; | 3 | import { Card } from './components/Card'; |
| 3 | import './style.css'; | 4 | import './style.css'; |
| 4 | import logo from '../src/images/wifi.png'; | 5 | import logo from '../src/images/wifi.png'; |
| 5 | 6 | ||
| 6 | function App() { | 7 | function App() { |
| 8 | const { t, i18n } = useTranslation(); | ||
| 9 | |||
| 7 | return ( | 10 | return ( |
| 8 | <div className="App"> | 11 | <div className="App"> |
| 9 | <h1> | 12 | <h1> |
| 10 | <img alt="icon" src={logo} width="32" height="32" /> | 13 | <img alt="icon" src={logo} width="32" height="32" /> |
| 11 | WiFi Card | 14 | {t('title')} |
| 12 | </h1> | 15 | </h1> |
| 13 | 16 | ||
| 14 | <p className="tag"> | 17 | <div> |
| 15 | Print a simple card with your WiFi login details. Tape it to the fridge, | 18 | <label>{t('select')}</label> |
| 16 | keep it in your wallet, etc. | 19 | <select onChange={(e) => i18n.changeLanguage(e.target.value)}> |
| 17 | </p> | 20 | <option value="en-US">en-US</option> |
| 21 | <option value="简体中文">简体中文</option> | ||
| 22 | </select> | ||
| 23 | </div> | ||
| 24 | |||
| 25 | <p className="tag">{t('desc.use')}</p> | ||
| 18 | 26 | ||
| 19 | <p className="tag"> | 27 | <p className="tag"> |
| 20 | Your WiFi information is never sent to the server. No tracking, | 28 | {t('desc.privacy')}{' '} |
| 21 | analytics, or fingerprinting are used on this website. View the{' '} | 29 | <a href="https://github.com/bndw/wifi-card">{t('desc.source')}</a>. |
| 22 | <a href="https://github.com/bndw/wifi-card">source code</a>. | ||
| 23 | </p> | 30 | </p> |
| 24 | 31 | ||
| 25 | <Card /> | 32 | <Card /> |
diff --git a/src/components/Card.js b/src/components/Card.js index 7409d6c..50db589 100644 --- a/src/components/Card.js +++ b/src/components/Card.js | |||
| @@ -1,5 +1,6 @@ | |||
| 1 | import QRCode from 'qrcode.react'; | 1 | import QRCode from 'qrcode.react'; |
| 2 | import { useEffect, useRef, useState } from 'react'; | 2 | import { useEffect, useRef, useState } from 'react'; |
| 3 | import { useTranslation } from 'react-i18next'; | ||
| 3 | import './style.css'; | 4 | import './style.css'; |
| 4 | 5 | ||
| 5 | export const Card = () => { | 6 | export const Card = () => { |
| @@ -12,7 +13,7 @@ export const Card = () => { | |||
| 12 | hidePassword: false, | 13 | hidePassword: false, |
| 13 | }); | 14 | }); |
| 14 | const [portrait, setPortrait] = useState(false); | 15 | const [portrait, setPortrait] = useState(false); |
| 15 | 16 | const { t } = useTranslation(); | |
| 16 | const escape = (v) => { | 17 | const escape = (v) => { |
| 17 | const needsEscape = ['"', ';', ',', ':', '\\']; | 18 | const needsEscape = ['"', ';', ',', ':', '\\']; |
| 18 | 19 | ||
| @@ -31,17 +32,17 @@ export const Card = () => { | |||
| 31 | const onPrint = () => { | 32 | const onPrint = () => { |
| 32 | if (network.ssid.length > 0) { | 33 | if (network.ssid.length > 0) { |
| 33 | if (network.password.length < 8 && network.encryptionMode === 'WPA') { | 34 | if (network.password.length < 8 && network.encryptionMode === 'WPA') { |
| 34 | alert('Password must be at least 8 characters'); | 35 | alert(t('wifi.alert.password.8')); |
| 35 | } else if ( | 36 | } else if ( |
| 36 | network.password.length < 5 && | 37 | network.password.length < 5 && |
| 37 | network.encryptionMode === 'WEP' | 38 | network.encryptionMode === 'WEP' |
| 38 | ) { | 39 | ) { |
| 39 | alert('Password must be at least 5 characters'); | 40 | alert(t('wifi.alert.password.5')); |
| 40 | } else { | 41 | } else { |
| 41 | window.print(); | 42 | window.print(); |
| 42 | } | 43 | } |
| 43 | } else { | 44 | } else { |
| 44 | alert('Network name cannot be empty'); | 45 | alert(t('wifi.alert.name')); |
| 45 | } | 46 | } |
| 46 | }; | 47 | }; |
| 47 | 48 | ||
| @@ -62,7 +63,9 @@ export const Card = () => { | |||
| 62 | id="print-area" | 63 | id="print-area" |
| 63 | style={{ maxWidth: portrait ? '350px' : '100%' }} | 64 | style={{ maxWidth: portrait ? '350px' : '100%' }} |
| 64 | > | 65 | > |
| 65 | <h1 style={{ textAlign: portrait ? 'center' : 'left' }}>WiFi Login</h1> | 66 | <h1 style={{ textAlign: portrait ? 'center' : 'left' }}> |
| 67 | {t('wifi.login')} | ||
| 68 | </h1> | ||
| 66 | 69 | ||
| 67 | <div | 70 | <div |
| 68 | className="details" | 71 | className="details" |
| @@ -76,12 +79,12 @@ export const Card = () => { | |||
| 76 | /> | 79 | /> |
| 77 | 80 | ||
| 78 | <div className="inputs"> | 81 | <div className="inputs"> |
| 79 | <label>Network name</label> | 82 | <label>{t('wifi.name')}</label> |
| 80 | <textarea | 83 | <textarea |
| 81 | id="ssid" | 84 | id="ssid" |
| 82 | type="text" | 85 | type="text" |
| 83 | maxLength="32" | 86 | maxLength="32" |
| 84 | placeholder="WiFi Network name" | 87 | placeholder={t('wifi.name.placeholder')} |
| 85 | autoComplete="off" | 88 | autoComplete="off" |
| 86 | autoCorrect="off" | 89 | autoCorrect="off" |
| 87 | autoCapitalize="none" | 90 | autoCapitalize="none" |
| @@ -95,7 +98,7 @@ export const Card = () => { | |||
| 95 | ${network.encryptionMode === 'nopass' && 'hidden'} | 98 | ${network.encryptionMode === 'nopass' && 'hidden'} |
| 96 | `} | 99 | `} |
| 97 | > | 100 | > |
| 98 | Password | 101 | {t('wifi.password')} |
| 99 | </label> | 102 | </label> |
| 100 | <textarea | 103 | <textarea |
| 101 | id="password" | 104 | id="password" |
| @@ -109,7 +112,7 @@ export const Card = () => { | |||
| 109 | portrait && network.password.length > 40 ? '5em' : 'auto', | 112 | portrait && network.password.length > 40 ? '5em' : 'auto', |
| 110 | }} | 113 | }} |
| 111 | maxLength="63" | 114 | maxLength="63" |
| 112 | placeholder="Password" | 115 | placeholder={t('wifi.password.placeholder')} |
| 113 | autoComplete="off" | 116 | autoComplete="off" |
| 114 | autoCorrect="off" | 117 | autoCorrect="off" |
| 115 | autoCapitalize="none" | 118 | autoCapitalize="none" |
| @@ -136,12 +139,12 @@ export const Card = () => { | |||
| 136 | for="hide-password-checkbox" | 139 | for="hide-password-checkbox" |
| 137 | className={network.encryptionMode === 'nopass' ? 'hidden' : ''} | 140 | className={network.encryptionMode === 'nopass' ? 'hidden' : ''} |
| 138 | > | 141 | > |
| 139 | Hide password | 142 | {t('wifi.password.hide')} |
| 140 | </label> | 143 | </label> |
| 141 | </div> | 144 | </div> |
| 142 | 145 | ||
| 143 | <div className="no-print"> | 146 | <div className="no-print"> |
| 144 | <label>Encryption:</label> | 147 | <label>{t('wifi.password.encryption')}:</label> |
| 145 | <input | 148 | <input |
| 146 | type="radio" | 149 | type="radio" |
| 147 | name="encrypt-select" | 150 | name="encrypt-select" |
| @@ -181,16 +184,16 @@ export const Card = () => { | |||
| 181 | <span role="img" aria-label="mobile-phone"> | 184 | <span role="img" aria-label="mobile-phone"> |
| 182 | 📸📱 | 185 | 📸📱 |
| 183 | </span> | 186 | </span> |
| 184 | Point your phone's camera at the QR Code to connect automatically | 187 | {t('wifi.tip')} |
| 185 | </p> | 188 | </p> |
| 186 | </fieldset> | 189 | </fieldset> |
| 187 | 190 | ||
| 188 | <div className="buttons"> | 191 | <div className="buttons"> |
| 189 | <button id="rotate" onClick={() => setPortrait(!portrait)}> | 192 | <button id="rotate" onClick={() => setPortrait(!portrait)}> |
| 190 | Rotate | 193 | {t('button.rotate')} |
| 191 | </button> | 194 | </button> |
| 192 | <button id="print" onClick={onPrint}> | 195 | <button id="print" onClick={onPrint}> |
| 193 | 196 | {t('button.print')} | |
| 194 | </button> | 197 | </button> |
| 195 | </div> | 198 | </div> |
| 196 | </div> | 199 | </div> |
diff --git a/src/i18n.js b/src/i18n.js new file mode 100644 index 0000000..2990dcb --- /dev/null +++ b/src/i18n.js | |||
| @@ -0,0 +1,64 @@ | |||
| 1 | import i18n from 'i18next'; | ||
| 2 | import { initReactI18next } from 'react-i18next'; | ||
| 3 | |||
| 4 | const resources = { | ||
| 5 | 'en-US': { | ||
| 6 | translation: { | ||
| 7 | title: 'WiFi Card', | ||
| 8 | 'desc.use': | ||
| 9 | 'Print a simple card with your WiFi login details. Tape it to the fridge, keep it in your wallet, etc.', | ||
| 10 | 'desc.privacy': | ||
| 11 | 'Your WiFi information is never sent to the server. No tracking, analytics, or fingerprinting are used on this website. View the', | ||
| 12 | 'desc.source': 'source code', | ||
| 13 | 'wifi.login': 'WiFi Login', | ||
| 14 | 'wifi.name': 'Network name', | ||
| 15 | 'wifi.name.placeholder': 'WiFi Network name', | ||
| 16 | 'wifi.password': 'Password', | ||
| 17 | 'wifi.password.placeholder': 'Password', | ||
| 18 | 'wifi.password.hide': 'Hide password field before printing', | ||
| 19 | 'wifi.password.encryption': 'Encryption', | ||
| 20 | 'wifi.tip': | ||
| 21 | "Point your phone's camera at the QR Code to connect automatically", | ||
| 22 | 'wifi.alert.name': 'Network name cannot be empty', | ||
| 23 | 'wifi.alert.password.length.5': 'Password must be at least 5 characters', | ||
| 24 | 'wifi.alert.password.8': 'Password must be at least 8 characters', | ||
| 25 | 'button.rotate': 'Rotate', | ||
| 26 | 'button.print': 'Print', | ||
| 27 | select: 'Select Language', | ||
| 28 | }, | ||
| 29 | }, | ||
| 30 | 简体中文: { | ||
| 31 | translation: { | ||
| 32 | title: 'WiFi 连接卡', | ||
| 33 | 'desc.use': | ||
| 34 | '打印一张带有 WiFi 详细信息的登录卡片,把它贴到冰箱上、放到你的钱包里...', | ||
| 35 | 'desc.privacy': | ||
| 36 | '您的 WiFi 信息永远不会发送到服务端。本网站不使用追踪、分析或指纹识别。查看', | ||
| 37 | 'desc.source': '源码', | ||
| 38 | 'wifi.login': '连接 WiFi', | ||
| 39 | 'wifi.name': '网络名称', | ||
| 40 | 'wifi.name.placeholder': 'WiFi 网络名称', | ||
| 41 | 'wifi.password': '密码', | ||
| 42 | 'wifi.password.placeholder': '密码', | ||
| 43 | 'wifi.password.hide': '打印前隐藏密码字段', | ||
| 44 | 'wifi.password.encryption': '加密', | ||
| 45 | 'wifi.tip': '将手机摄像头对准二维码即可自动连接', | ||
| 46 | 'wifi.alert.name': '网络名称不能为空', | ||
| 47 | 'wifi.alert.password.length.5': '密码至少 5 个字符', | ||
| 48 | 'wifi.alert.password.8': '密码至少 8 个字符', | ||
| 49 | 'button.rotate': '翻转', | ||
| 50 | 'button.print': '打印', | ||
| 51 | select: '选择语言', | ||
| 52 | }, | ||
| 53 | }, | ||
| 54 | }; | ||
| 55 | |||
| 56 | i18n.use(initReactI18next).init({ | ||
| 57 | resources, | ||
| 58 | lng: 'en-US', | ||
| 59 | interpolation: { | ||
| 60 | escapeValue: false, | ||
| 61 | }, | ||
| 62 | }); | ||
| 63 | |||
| 64 | export default i18n; | ||
diff --git a/src/index.js b/src/index.js index c1f31c5..30091ec 100644 --- a/src/index.js +++ b/src/index.js | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | import React from 'react'; | 1 | import React from 'react'; |
| 2 | import ReactDOM from 'react-dom'; | 2 | import ReactDOM from 'react-dom'; |
| 3 | import App from './App'; | 3 | import App from './App'; |
| 4 | import './i18n'; | ||
| 4 | 5 | ||
| 5 | ReactDOM.render( | 6 | ReactDOM.render( |
| 6 | <React.StrictMode> | 7 | <React.StrictMode> |
