diff --git a/web/package.json b/web/package.json index 983ee7e..f13479f 100644 --- a/web/package.json +++ b/web/package.json @@ -5,16 +5,21 @@ "build": "react-scripts build", "eject": "react-scripts eject", "now-build": "yarn build", - "now-dev": "BROWSER=false react-scripts start", + "now-dev": "react-scripts start", "start": "react-scripts start", "test": "react-scripts test" }, "dependencies": { "fetch-suspense": "^1.2.0", + "i18next": "^17.0.8", + "i18next-browser-languagedetector": "^3.0.1", + "i18next-xhr-backend": "^3.0.0", "isomorphic-unfetch": "^3.0.0", "react": "^16.8.6", "react-dom": "^16.8.6", "react-ga": "^2.6.0", + "react-helmet": "^5.2.1", + "react-i18next": "^10.11.4", "react-icons": "^3.7.0", "react-scripts": "3.0.1", "react-spinners": "^0.5.13", diff --git a/web/public/locales/en/translation.json b/web/public/locales/en/translation.json new file mode 100644 index 0000000..1db3030 --- /dev/null +++ b/web/public/locales/en/translation.json @@ -0,0 +1,18 @@ +{ + "title": "name new project", + "description": "namæ saves your time searching around registries and checking if the desired name is ready for use.", + "placeholder": "search", + "providers": { + "domains": "Domains", + "github": "Github Organization", + "npm": "npm", + "pypi": "PyPI", + "rubygems": "RubyGems", + "rust": "Rust", + "homebrew": "Homebrew", + "jsorg": "js.org", + "s3": "AWS S3", + "twitter": "Twitter", + "slack": "Slack" + } +} diff --git a/web/public/locales/ja/translation.json b/web/public/locales/ja/translation.json new file mode 100644 index 0000000..84f4ac6 --- /dev/null +++ b/web/public/locales/ja/translation.json @@ -0,0 +1,18 @@ +{ + "title": "その名前、もう使われてる?", + "description": "namæ をつかって、思いついた「名前」が誰にも使われていないか調べましょう。", + "placeholder": "検索", + "providers": { + "domains": "ドメイン", + "github": "Github Organization", + "npm": "npm", + "pypi": "PyPI", + "rubygems": "RubyGems", + "rust": "Rust", + "homebrew": "Homebrew", + "jsorg": "js.org", + "s3": "AWS S3", + "twitter": "Twitter", + "slack": "Slack" + } +} diff --git a/web/src/App.js b/web/src/App.js index 300fe78..ece67ff 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -1,5 +1,7 @@ import React, { useState, useEffect, useRef } from 'react' import styled, { createGlobalStyle } from 'styled-components' +import { Helmet } from 'react-helmet' +import { useTranslation } from 'react-i18next' import Welcome from './components/Welcome' import Footer from './components/Footer' @@ -25,6 +27,7 @@ export default function App() { const [query, setQuery] = useDeferredState(1000) const [inputValue, setInputValue] = useState('') const inputRef = useRef() + const { t } = useTranslation() const queryGiven = query && query.length > 0 @@ -44,10 +47,18 @@ export default function App() { return ( <> + + namaæ — {t('title')} +
namæ - +
@@ -146,7 +157,6 @@ const Logo = styled.div` const Input = styled.input.attrs({ type: 'text', - placeholder: 'search', autocomplete: 'off', autocorrect: 'off', autocapitalize: 'off', @@ -158,6 +168,7 @@ const Input = styled.input.attrs({ text-align: center; font-family: monospace; font-size: 5rem; + line-height: 1.2em; ${mobile} { font-size: 2rem; diff --git a/web/src/components/Welcome.js b/web/src/components/Welcome.js index 532ebd1..005be19 100644 --- a/web/src/components/Welcome.js +++ b/web/src/components/Welcome.js @@ -1,5 +1,6 @@ import React from 'react' import styled from 'styled-components' +import { useTranslation } from 'react-i18next' import { FaMapSigns } from 'react-icons/fa' import { FaGithub } from 'react-icons/fa' @@ -16,48 +17,47 @@ import { FaGem } from 'react-icons/fa' import { mobile } from '../util/css' export default function Welcome() { + const { t } = useTranslation() + return ( -
name new project
- - namæ saves your time searching around registries and checking if the - desired name is ready for use. - +
{t('title')}
+ {t('description')}
- Domains + {t('providers.domains')} - GitHub Organization + {t('providers.github')} - npm + {t('providers.npm')} - PyPI + {t('providers.pypi')} - RubyGems + {t('providers.rubygems')} - Rust + {t('providers.rust')} - Homebrew + {t('providers.homebrew')} - js.org + {t('providers.jsorg')} - AWS S3 Bucket + {t('providers.s3')} - Twitter + {t('providers.twitter')} - Slack + {t('providers.slack')}
@@ -82,8 +82,12 @@ const Container = styled.div` const Header = styled.h1` font-size: 3.5em; - line-height: 0.8em; + line-height: 1em; padding-bottom: 30px; + + ${mobile} { + font-size: 3em; + } ` const Text = styled.p` diff --git a/web/src/components/cards/CratesioCard.js b/web/src/components/cards/CratesioCard.js index 7b5c6e3..ae47895 100644 --- a/web/src/components/cards/CratesioCard.js +++ b/web/src/components/cards/CratesioCard.js @@ -1,13 +1,15 @@ import React from 'react' +import { useTranslation } from 'react-i18next' import { DiRust } from 'react-icons/di' import { Card } from '../Cards' import { DedicatedAvailability } from '../Availability' export default function CratesioCard({ name }) { + const { t } = useTranslation() const lowerCase = name.toLowerCase() return ( - + {(name) => ( + {(name) => ( <> + {(name) => ( diff --git a/web/src/components/cards/PypiCard.js b/web/src/components/cards/PypiCard.js index cdf635f..9fbb038 100644 --- a/web/src/components/cards/PypiCard.js +++ b/web/src/components/cards/PypiCard.js @@ -1,13 +1,16 @@ import React from 'react' +import { useTranslation } from 'react-i18next' import { FaPython } from 'react-icons/fa' import { Card } from '../Cards' import { DedicatedAvailability } from '../Availability' import { capitalize } from '../../util/text' export default function PypiCard({ name }) { + const { t } = useTranslation() + return ( diff --git a/web/src/components/cards/RubyGemsCard.js b/web/src/components/cards/RubyGemsCard.js index 5b4567b..f4799f8 100644 --- a/web/src/components/cards/RubyGemsCard.js +++ b/web/src/components/cards/RubyGemsCard.js @@ -1,12 +1,15 @@ import React from 'react' +import { useTranslation } from 'react-i18next' import { FaGem } from 'react-icons/fa' import { Card } from '../Cards' import { DedicatedAvailability } from '../Availability' export default function RubyGemsCard({ name }) { + const { t } = useTranslation() + return ( diff --git a/web/src/components/cards/S3Card.js b/web/src/components/cards/S3Card.js index ba4ecf9..531e1b8 100644 --- a/web/src/components/cards/S3Card.js +++ b/web/src/components/cards/S3Card.js @@ -1,13 +1,15 @@ import React from 'react' +import { useTranslation } from 'react-i18next' import { FaAws } from 'react-icons/fa' import { Card } from '../Cards' import { DedicatedAvailability } from '../Availability' export default function S3Card({ name }) { + const { t } = useTranslation() const lowerCase = name.toLowerCase() return ( - + {(name) => ( + {(name) => ( see /public/locales + // learn more: https://github.com/i18next/i18next-xhr-backend + .use(Backend) + // detect user language + // learn more: https://github.com/i18next/i18next-browser-languageDetector + .use(LanguageDetector) + // pass the i18n instance to react-i18next. + .use(initReactI18next) + // init i18next + // for all options read: https://www.i18next.com/overview/configuration-options + .init({ + fallbackLng: 'en', + debug: false, + interpolation: { + escapeValue: false, // not needed for react as it escapes by default + }, + }) + +export default i18n diff --git a/web/src/index.js b/web/src/index.js index 836b201..d48b942 100644 --- a/web/src/index.js +++ b/web/src/index.js @@ -1,10 +1,34 @@ -import React from 'react' +import React, { Suspense } from 'react' +import styled from 'styled-components' import ReactDOM from 'react-dom' import ReactGA from 'react-ga' +import BarLoader from 'react-spinners/BarLoader' import App from './App' import * as serviceWorker from './serviceWorker' -ReactDOM.render(, document.getElementById('root')) +import './i18n' + +const Fallback = () => ( + + + +) + +const Container = styled.div` + width: 100vw; + height: 100vh; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +` + +ReactDOM.render( + }> + + , + document.getElementById('root') +) ReactGA.initialize('UA-28919359-15') ReactGA.pageview(window.location.pathname + window.location.search) diff --git a/yarn.lock b/yarn.lock index 95ce279..427ff7c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -881,7 +881,7 @@ dependencies: regenerator-runtime "^0.13.2" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.3", "@babel/runtime@^7.5.1", "@babel/runtime@^7.5.4", "@babel/runtime@^7.5.5": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.3", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.1", "@babel/runtime@^7.5.4", "@babel/runtime@^7.5.5": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132" integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ== @@ -4029,6 +4029,11 @@ execa@^1.0.0: signal-exit "^3.0.0" strip-eof "^1.0.0" +exenv@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d" + integrity sha1-KueOhdmJQVhnCwPUe+wfA72Ru50= + exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -4842,6 +4847,13 @@ html-minifier@^3.5.20: relateurl "0.2.x" uglify-js "3.4.x" +html-parse-stringify2@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/html-parse-stringify2/-/html-parse-stringify2-2.0.1.tgz#dc5670b7292ca158b7bc916c9a6735ac8872834a" + integrity sha1-3FZwtyksoVi3vJFsmmc1rIhyg0o= + dependencies: + void-elements "^2.0.1" + html-webpack-plugin@4.0.0-beta.5: version "4.0.0-beta.5" resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-4.0.0-beta.5.tgz#2c53083c1151bfec20479b1f8aaf0039e77b5513" @@ -4946,6 +4958,25 @@ https-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= +i18next-browser-languagedetector@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-3.0.1.tgz#a47c43176e8412c91e808afb7c6eb5367649aa8e" + integrity sha512-WFjPLNPWl62uu07AHY2g+KsC9qz0tyMq+OZEB/H7N58YKL/JLiCz9U709gaR20Mule/Ppn+uyfVx5REJJjn1HA== + +i18next-xhr-backend@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/i18next-xhr-backend/-/i18next-xhr-backend-3.0.0.tgz#75235870c920291dbfd32043505b7ede0dc87da3" + integrity sha512-Pi/X91Zk2nEqdEHTV+FG6VeMHRcMcPKRsYW/A0wlaCfKsoJc3TI7A75Tqse/d5LVGN2Ymzx0FT+R+gLag9Eb2g== + dependencies: + "@babel/runtime" "^7.4.5" + +i18next@^17.0.8: + version "17.0.8" + resolved "https://registry.yarnpkg.com/i18next/-/i18next-17.0.8.tgz#0c7113a88ad156eb37b9025d83a7684e1bbc2e18" + integrity sha512-oojOrqEPQzKo1HDMDDOl19zTM/EaDwBRPobUSD4kEjNoTi2oERvUbngK2lkIm9nOGddh55jbMGbm6fusMBeoKQ== + dependencies: + "@babel/runtime" "^7.3.1" + iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -8429,11 +8460,34 @@ react-error-overlay@^5.1.6: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-5.1.6.tgz#0cd73407c5d141f9638ae1e0c63e7b2bf7e9929d" integrity sha512-X1Y+0jR47ImDVr54Ab6V9eGk0Hnu7fVWGeHQSOXHf/C2pF9c6uy3gef8QUeuUiWlNb0i08InPSE5a/KJzNzw1Q== +react-fast-compare@^2.0.2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" + integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== + react-ga@^2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/react-ga/-/react-ga-2.6.0.tgz#c3fe830ead2ad25117e1d33280d9698de9b28496" integrity sha512-GWHBWZDFjDGMkIk1LzroIn0mNTygKw3adXuqvGvheFZvlbpqMPbHsQsTdQBIxRRdXGQM/Zq+dQLRPKbwIHMTaw== +react-helmet@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-5.2.1.tgz#16a7192fdd09951f8e0fe22ffccbf9bb3e591ffa" + integrity sha512-CnwD822LU8NDBnjCpZ4ySh8L6HYyngViTZLfBBb3NjtrpN8m49clH8hidHouq20I51Y6TpCTISCBbqiY5GamwA== + dependencies: + object-assign "^4.1.1" + prop-types "^15.5.4" + react-fast-compare "^2.0.2" + react-side-effect "^1.1.0" + +react-i18next@^10.11.4: + version "10.11.4" + resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-10.11.4.tgz#9277a609ee640fac353087a91935ee9a9eb65f40" + integrity sha512-/CWXaf3a5BLNeVnBGxzWOIZLQgSNEc2LWHX4ZaJb7ww0xgY0S5K9HRAMzJIHeHGe7jfpSraprD66VDblWb4ZXA== + dependencies: + "@babel/runtime" "^7.3.1" + html-parse-stringify2 "2.0.1" + react-icons@^3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-3.7.0.tgz#64fe46231fabfeea27895edeae6c3b78114b8c8f" @@ -8511,6 +8565,14 @@ react-scripts@3.0.1: optionalDependencies: fsevents "2.0.6" +react-side-effect@^1.1.0: + version "1.1.5" + resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-1.1.5.tgz#f26059e50ed9c626d91d661b9f3c8bb38cd0ff2d" + integrity sha512-Z2ZJE4p/jIfvUpiUMRydEVpQRf2f8GMHczT6qLcARmX7QRb28JDBTpnM2g/i5y/p7ZDEXYGHWg0RbhikE+hJRw== + dependencies: + exenv "^1.2.1" + shallowequal "^1.0.1" + react-spinners@^0.5.13: version "0.5.13" resolved "https://registry.yarnpkg.com/react-spinners/-/react-spinners-0.5.13.tgz#09da41ee6b321083ff9670cbf78e11effb3feb87" @@ -9164,6 +9226,11 @@ shallow-clone@^1.0.0: kind-of "^5.0.0" mixin-object "^2.0.1" +shallowequal@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" + integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -10243,6 +10310,11 @@ vm-browserify@^1.0.1: resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.0.tgz#bd76d6a23323e2ca8ffa12028dc04559c75f9019" integrity sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw== +void-elements@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" + integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w= + w3c-hr-time@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz#82ac2bff63d950ea9e3189a58a65625fedf19045"