add trans support and hook all compnents excluding preview dir

This commit is contained in:
myl7 2022-02-06 18:35:03 +08:00
parent aa848c92f8
commit 2df258f791
No known key found for this signature in database
GPG key ID: 04F1013B67177C88
10 changed files with 110 additions and 54 deletions

View file

@ -3,6 +3,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import Image from 'next/image'
import { useRouter } from 'next/router'
import { FC, useState } from 'react'
import { useTranslation } from 'next-i18next'
import { matchProtectedRoute } from '../utils/protectedRouteHandler'
import useLocalStorage from '../utils/useLocalStorage'
@ -14,16 +15,18 @@ const Auth: FC<{ redirect: string }> = ({ redirect }) => {
const [token, setToken] = useState('')
const [_, setPersistedToken] = useLocalStorage(authTokenPath, '')
const { t } = useTranslation()
return (
<div className="md:my-10 flex flex-col max-w-sm mx-auto space-y-4">
<div className="md:w-5/6 w-3/4 mx-auto">
<Image src={'/images/fabulous-wapmire-weekdays.png'} alt="authenticate" width={912} height={912} priority />
</div>
<div className="dark:text-gray-100 text-lg font-bold text-gray-900">Enter Password</div>
<div className="dark:text-gray-100 text-lg font-bold text-gray-900">{t('Enter Password')}</div>
<p className="text-sm text-gray-500 font-medium">
This route (the folder itself and the files inside) is password protected. If you know the password, please
enter it below.
{t('This route (the folder itself and the files inside) is password protected. ') +
t('If you know the password, please enter it below.')}
</p>
<div className="flex items-center space-x-2">

View file

@ -3,6 +3,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { IconProp } from '@fortawesome/fontawesome-svg-core'
import toast from 'react-hot-toast'
import { useClipboard } from 'use-clipboard-copy'
import { useTranslation } from 'next-i18next'
import Image from 'next/image'
import { useRouter } from 'next/router'
@ -63,31 +64,33 @@ const DownloadButtonGroup: React.FC<{ downloadUrl: string }> = ({ downloadUrl })
const { asPath } = useRouter()
const clipboard = useClipboard()
const { t } = useTranslation()
return (
<div className="flex flex-wrap justify-center gap-2">
<DownloadButton
onClickCallback={() => window.open(downloadUrl)}
btnColor="blue"
btnText="Download"
btnText={t('Download')}
btnIcon="file-download"
btnTitle="Download the file directly through OneDrive"
btnTitle={t('Download the file directly through OneDrive')}
/>
{/* <DownloadButton
onClickCallback={() => window.open(`/api/proxy?url=${encodeURIComponent(downloadUrl)}`)}
btnColor="teal"
btnText="Proxy download"
btnText={t('Proxy download')}
btnIcon="download"
btnTitle="Download the file with the stream proxied through Vercel Serverless"
btnTitle={t('Download the file with the stream proxied through Vercel Serverless')}
/> */}
<DownloadButton
onClickCallback={() => {
clipboard.copy(`${getBaseUrl()}/api?path=${asPath}&raw=true`)
toast.success('Copied direct link to clipboard.')
toast.success(t('Copied direct link to clipboard.'))
}}
btnColor="pink"
btnText="Copy direct link"
btnText={t('Copy direct link')}
btnIcon="copy"
btnTitle="Copy the permalink to the file to the clipboard"
btnTitle={t('Copy the permalink to the file to the clipboard')}
/>
</div>
)

View file

@ -321,7 +321,10 @@ const FileListing: FC<{ query?: ParsedUrlQuery }> = ({ query }) => {
{!onlyOnePage && (
<div className="rounded-b bg-white dark:bg-gray-900 dark:text-gray-100">
<div className="border-b border-gray-200 p-3 text-center font-mono text-sm text-gray-400 dark:border-gray-700">
- showing {size} page{size > 1 ? 's' : ''} of {isLoadingMore ? '...' : folderChildren.length} files -
{t('- showing {{count}} page(s) of {{totalFileNum}} files -', {
count: size,
totalFileNum: isLoadingMore ? '...' : folderChildren.length
})}
</div>
<button
className={`flex w-full items-center justify-center space-x-2 p-3 disabled:cursor-not-allowed ${
@ -406,7 +409,7 @@ const FileListing: FC<{ query?: ParsedUrlQuery }> = ({ query }) => {
return (
<PreviewContainer>
<FourOhFour errorMsg={`Cannot preview ${path}`} />
<FourOhFour errorMsg={t('Cannot preview {{path}}', { path })} />
</PreviewContainer>
)
}

View file

@ -4,6 +4,7 @@ import Link from 'next/link'
import { useState } from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { useClipboard } from 'use-clipboard-copy'
import { useTranslation } from 'next-i18next'
import { getBaseUrl } from '../utils/getBaseUrl'
import { formatModifiedDateTime } from '../utils/fileDetails'
@ -65,6 +66,8 @@ const FolderGridLayout = ({
}) => {
const clipboard = useClipboard()
const { t } = useTranslation()
return (
<div className="rounded bg-white dark:bg-gray-900 dark:text-gray-100">
<div className="flex items-center border-b border-gray-900/10 px-3 text-xs font-bold uppercase tracking-widest text-gray-600 dark:border-gray-500/30 dark:text-gray-400">
@ -74,13 +77,13 @@ const FolderGridLayout = ({
checked={totalSelected}
onChange={toggleTotalSelected}
indeterminate={true}
title={'Select all files'}
title={t('Select all files')}
/>
{totalGenerating ? (
<Downloading title="Downloading selected files, refresh page to cancel" />
<Downloading title={t('Downloading selected files, refresh page to cancel')} />
) : (
<button
title="Download selected files"
title={t('Download selected files')}
className="cursor-pointer rounded p-1.5 hover:bg-gray-300 disabled:cursor-not-allowed disabled:text-gray-400 disabled:hover:bg-white dark:hover:bg-gray-600 disabled:dark:text-gray-600 disabled:hover:dark:bg-gray-900"
disabled={totalSelected === 0}
onClick={handleSelectedDownload}
@ -101,20 +104,20 @@ const FolderGridLayout = ({
{c.folder ? (
<div>
<span
title="Copy folder permalink"
title={t('Copy folder permalink')}
className="cursor-pointer rounded px-1.5 py-1 hover:bg-gray-300 dark:hover:bg-gray-600"
onClick={() => {
clipboard.copy(`${getBaseUrl()}${path === '/' ? '' : path}/${encodeURIComponent(c.name)}`)
toast('Copied folder permalink.', { icon: '👌' })
toast(t('Copied folder permalink.'), { icon: '👌' })
}}
>
<FontAwesomeIcon icon={['far', 'copy']} />
</span>
{folderGenerating[c.id] ? (
<Downloading title="Downloading folder, refresh page to cancel" />
<Downloading title={t('Downloading folder, refresh page to cancel')} />
) : (
<span
title="Download folder"
title={t('Download folder')}
className="cursor-pointer rounded px-1.5 py-1 hover:bg-gray-300 dark:hover:bg-gray-600"
onClick={() => {
const p = `${path === '/' ? '' : path}/${encodeURIComponent(c.name)}`
@ -128,19 +131,19 @@ const FolderGridLayout = ({
) : (
<div>
<span
title="Copy raw file permalink"
title={t('Copy raw file permalink')}
className="cursor-pointer rounded px-1.5 py-1 hover:bg-gray-300 dark:hover:bg-gray-600"
onClick={() => {
clipboard.copy(
`${getBaseUrl()}/api?path=${path === '/' ? '' : path}/${encodeURIComponent(c.name)}&raw=true`
)
toast.success('Copied raw file permalink.')
toast.success(t('Copied raw file permalink.'))
}}
>
<FontAwesomeIcon icon={['far', 'copy']} />
</span>
<a
title="Download file"
title={t('Download file')}
className="cursor-pointer rounded px-1.5 py-1 hover:bg-gray-300 dark:hover:bg-gray-600"
href={c['@microsoft.graph.downloadUrl']}
>
@ -159,7 +162,7 @@ const FolderGridLayout = ({
<Checkbox
checked={selected[c.id] ? 2 : 0}
onChange={() => toggleItemSelected(c.id)}
title="Select file"
title={t('Select file')}
/>
)}
</div>

View file

@ -1,4 +1,5 @@
import Image from 'next/image'
import { Trans } from 'next-i18next'
const FourOhFour: React.FC<{ errorMsg: string }> = ({ errorMsg }) => {
return (
@ -8,14 +9,19 @@ const FourOhFour: React.FC<{ errorMsg: string }> = ({ errorMsg }) => {
</div>
<div className="mt-6 text-gray-500 max-w-xl mx-auto">
<div className="text-xl font-bold mb-8">
<Trans>
Oops, that&apos;s a <span className="underline decoration-wavy decoration-red-500">four-oh-four</span>.
</Trans>
</div>
<div className="font-mono border border-gray-400/20 rounded p-2 mb-4 text-xs bg-gray-50 dark:bg-gray-800">
{errorMsg}
</div>
<div className="text-sm">
<Trans>
Press{' '}
<kbd className="border-opacity-20 font-mono text-xs p-1 bg-gray-100 dark:bg-gray-800 border rounded">F12</kbd>{' '}
<kbd className="border-opacity-20 font-mono text-xs p-1 bg-gray-100 dark:bg-gray-800 border rounded">
F12
</kbd>{' '}
and open devtools for more details, or seek help at{' '}
<a
className="text-blue-600 hover:text-blue-700 hover:underline"
@ -26,6 +32,7 @@ const FourOhFour: React.FC<{ errorMsg: string }> = ({ errorMsg }) => {
onedrive-vercel-index discussions
</a>
.
</Trans>
</div>
</div>
</div>

View file

@ -47,7 +47,7 @@ const Navbar = () => {
localStorage.removeItem(r)
})
toast.success('Cleared all tokens')
toast.success(t('Cleared all tokens'))
setTimeout(() => {
router.reload()
}, 1000)
@ -95,14 +95,20 @@ const Navbar = () => {
className="flex items-center space-x-2 hover:opacity-80 dark:text-white"
>
<FontAwesomeIcon icon={['fab', l.name.toLowerCase() as IconName]} />
<span className="hidden text-sm font-medium md:inline-block">{l.name}</span>
<span className="hidden text-sm font-medium md:inline-block">
{
// Append link name comments here to add translations
// t('Weibo')
t(l.name)
}
</span>
</a>
))}
{siteConfig.email && (
<a href={siteConfig.email} className="flex items-center space-x-2 hover:opacity-80 dark:text-white">
<FontAwesomeIcon icon={['far', 'envelope']} />
<span className="hidden text-sm font-medium md:inline-block">Email</span>
<span className="hidden text-sm font-medium md:inline-block">{t('Email')}</span>
</a>
)}
@ -111,7 +117,7 @@ const Navbar = () => {
className="flex items-center space-x-2 p-2 hover:opacity-80 dark:text-white"
onClick={() => setIsOpen(true)}
>
<span className="text-sm font-medium">Logout</span>
<span className="text-sm font-medium">{t('Logout')}</span>
<FontAwesomeIcon icon="sign-out-alt" />
</button>
)}
@ -148,12 +154,12 @@ const Navbar = () => {
>
<div className="my-8 inline-block w-full max-w-md transform overflow-hidden rounded-lg bg-white p-6 text-left align-middle transition-all dark:bg-gray-900">
<Dialog.Title className="text-lg font-bold text-gray-900 dark:text-gray-100">
Clear all tokens?
{t('Clear all tokens?')}
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-gray-500">
These tokens are used to authenticate yourself into password protected folders, clearing them means
that you will need to re-enter the passwords again.
{t('These tokens are used to authenticate yourself into password protected folders, ') +
t('clearing them means that you will need to re-enter the passwords again.')}
</p>
</div>
@ -171,14 +177,14 @@ const Navbar = () => {
className="mr-3 inline-flex items-center justify-center space-x-2 rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-400 focus:outline-none focus:ring focus:ring-blue-300"
onClick={() => setIsOpen(false)}
>
Cancel
{t('Cancel')}
</button>
<button
className="inline-flex items-center justify-center space-x-2 rounded bg-red-500 px-4 py-2 text-white hover:bg-red-400 focus:outline-none focus:ring focus:ring-red-300"
onClick={() => clearTokens()}
>
<FontAwesomeIcon icon={['far', 'trash-alt']} />
<span>Clear all</span>
<span>{t('Clear all')}</span>
</button>
</div>
</div>

View file

@ -4,6 +4,7 @@ import { Dispatch, Fragment, SetStateAction, useState } from 'react'
import AwesomeDebouncePromise from 'awesome-debounce-promise'
import { useAsync } from 'react-async-hook'
import useConstant from 'use-constant'
import { useTranslation } from 'next-i18next'
import Link from 'next/link'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
@ -112,12 +113,19 @@ function SearchResultItemTemplate({
function SearchResultItemLoadRemote({ result }: { result: OdSearchResult[number] }) {
const { data, error }: SWRResponse<OdDriveItem, string> = useSWR(`/api/item?id=${result.id}`, fetcher)
const { t } = useTranslation()
if (error) {
return <SearchResultItemTemplate driveItem={result} driveItemPath={''} itemDescription={error} disabled={true} />
}
if (!data) {
return (
<SearchResultItemTemplate driveItem={result} driveItemPath={''} itemDescription={'Loading ...'} disabled={true} />
<SearchResultItemTemplate
driveItem={result}
driveItemPath={''}
itemDescription={t('Loading ...')}
disabled={true}
/>
)
}
@ -159,6 +167,8 @@ export default function SearchModal({
}) {
const { query, setQuery, results } = useDriveItemSearch()
const { t } = useTranslation()
const closeSearchBox = () => {
setSearchOpen(false)
setQuery('')
@ -199,13 +209,13 @@ export default function SearchModal({
type="text"
id="search-box"
className="w-full bg-transparent focus:outline-none focus-visible:outline-none"
placeholder="Search ..."
placeholder={t('Search ...')}
value={query}
onChange={e => setQuery(e.target.value)}
/>
<div className="px-2 py-1 rounded-lg bg-gray-200 dark:bg-gray-700 font-medium text-xs">ESC</div>
</Dialog.Title>
<div className="text-center px-4 py-12 text-sm font-medium">{t('Nothing here.')}</div>
<div
className="bg-white dark:text-white dark:bg-gray-900 max-h-[80vh] overflow-x-hidden overflow-y-scroll"
onClick={closeSearchBox}
@ -213,16 +223,18 @@ export default function SearchModal({
{results.loading && (
<div className="text-center px-4 py-12 text-sm font-medium">
<LoadingIcon className="animate-spin w-4 h-4 mr-2 inline-block svg-inline--fa" />
<span>Loading ...</span>
<span>{t('Loading ...')}</span>
</div>
)}
{results.error && (
<div className="text-center px-4 py-12 text-sm font-medium">Error: {results.error.message}</div>
<div className="text-center px-4 py-12 text-sm font-medium">
{t('Error: {{message}}', { message: results.error.message })}
</div>
)}
{results.result && (
<>
{results.result.length === 0 ? (
<div className="text-center px-4 py-12 text-sm font-medium">Nothing here.</div>
<div className="text-center px-4 py-12 text-sm font-medium">{t('Nothing here.')}</div>
) : (
results.result.map(result => <SearchResultItem key={result.id} result={result} />)
)}

View file

@ -1,4 +1,5 @@
const path = require('path')
const tsTransform = require('i18next-scanner-typescript')
const { i18n, localePath } = require('./next-i18next.config')
@ -8,8 +9,10 @@ module.exports = {
sort: true,
removeUnusedKeys: true,
func: {
list: ['t'],
extensions: ['.ts', '.tsx']
list: ['t']
},
trans: {
fallbackKey: (_ns, val) => val
},
lngs: i18n.locales,
ns: ['common'],
@ -22,5 +25,6 @@ module.exports = {
},
nsSeparator: false,
keySeparator: false
}
},
transform: tsTransform()
}

View file

@ -63,6 +63,7 @@
"eslint-config-next": "12.0.10",
"eslint-config-prettier": "^8.3.0",
"i18next-scanner": "^3.1.0",
"i18next-scanner-typescript": "^1.0.6",
"postcss": "^8.4.5",
"prettier": "^2.5.1",
"prettier-plugin-tailwindcss": "^0.1.4",

View file

@ -27,6 +27,7 @@ specifiers:
eslint-config-next: 12.0.10
eslint-config-prettier: ^8.3.0
i18next-scanner: ^3.1.0
i18next-scanner-typescript: ^1.0.6
ioredis: ^4.28.2
jszip: ^3.7.1
next: ^12.0.10
@ -110,6 +111,7 @@ devDependencies:
eslint-config-next: 12.0.10_9534215cc73b6f260bf33f1b86e3ae0e
eslint-config-prettier: 8.3.0_eslint@8.8.0
i18next-scanner: 3.1.0
i18next-scanner-typescript: 1.0.6
postcss: 8.4.6
prettier: 2.5.1
prettier-plugin-tailwindcss: 0.1.4_prettier@2.5.1
@ -2009,6 +2011,12 @@ packages:
resolution: {integrity: sha512-/MfAGMP0jHonV966uFf9PkWWuDjPYLIcsipnSO3NxpNtAgRUKLTwvm85fEmsF6hGeu0zbZiCQ3W74jwO6K9uXA==}
dev: false
/i18next-scanner-typescript/1.0.6:
resolution: {integrity: sha512-v6kULnsv32OlFM9pnWTH+wNtRXf5owmFUNBYcX78jbZxQBFnTCUo3w99JjnxuzuXA9ch4UKNQBnt2P/o94eyeQ==}
dependencies:
typescript: 3.9.10
dev: true
/i18next-scanner/3.1.0:
resolution: {integrity: sha512-dHLXUJIiF1CYJNslCkJFDYJySk5fg+dzdg9O73XXqHcdZwJ2947SWusqq8HdNFB7LpkBi8oTG6TWLZPmqbAh8Q==}
engines: {node: '>=12'}
@ -4024,6 +4032,12 @@ packages:
resolution: {integrity: sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw==}
dev: false
/typescript/3.9.10:
resolution: {integrity: sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==}
engines: {node: '>=4.2.0'}
hasBin: true
dev: true
/typescript/4.5.5:
resolution: {integrity: sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==}
engines: {node: '>=4.2.0'}