More translations, fix tests, modify build process
This commit is contained in:
parent
e530ce5555
commit
b05f5b8eb7
|
@ -0,0 +1,32 @@
|
||||||
|
module.exports = function (api) {
|
||||||
|
const isTest = api.env('test')
|
||||||
|
const isProduction = api.env('NODE_ENV') == 'production'
|
||||||
|
|
||||||
|
let presets = ['@babel/preset-react']
|
||||||
|
let plugins = []
|
||||||
|
|
||||||
|
if (isTest) {
|
||||||
|
presets.push('@babel/preset-env')
|
||||||
|
|
||||||
|
plugins.push('@babel/plugin-transform-runtime')
|
||||||
|
plugins.push('@babel/plugin-transform-modules-commonjs')
|
||||||
|
} else {
|
||||||
|
plugins.push(['styled-components', { pure: true }])
|
||||||
|
plugins.push('graphql-tag')
|
||||||
|
if (!isProduction) {
|
||||||
|
plugins.push([
|
||||||
|
'i18next-extract',
|
||||||
|
{
|
||||||
|
locales: ['en', 'da'],
|
||||||
|
discardOldKeys: true,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
presets: presets,
|
||||||
|
plugins: plugins,
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,15 +0,0 @@
|
||||||
{
|
|
||||||
"presets": ["@babel/preset-react"],
|
|
||||||
"plugins": [
|
|
||||||
"styled-components",
|
|
||||||
"graphql-tag",
|
|
||||||
[
|
|
||||||
"i18next-extract",
|
|
||||||
{
|
|
||||||
"locales": ["en", "da"],
|
|
||||||
"discardOldKeys": true,
|
|
||||||
"defaultValue": null
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
|
20
ui/build.mjs
20
ui/build.mjs
|
@ -72,11 +72,19 @@ if (watchMode) {
|
||||||
bs.reload(args)
|
bs.reload(args)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
esbuild.build(esbuildOptions).then(() => console.log('esbuild done'))
|
const esbuildPromise = esbuild
|
||||||
|
.build(esbuildOptions)
|
||||||
|
.then(() => console.log('esbuild done'))
|
||||||
|
|
||||||
workboxBuild.generateSW({
|
const workboxPromise = workboxBuild
|
||||||
globDirectory: 'dist/',
|
.generateSW({
|
||||||
globPatterns: ['**/*.{png,svg,woff2,ttf,eot,woff,js,ico,html,json,css}'],
|
globDirectory: 'dist/',
|
||||||
swDest: 'dist/service-worker.js',
|
globPatterns: ['**/*.{png,svg,woff2,ttf,eot,woff,js,ico,html,json,css}'],
|
||||||
})
|
swDest: 'dist/service-worker.js',
|
||||||
|
})
|
||||||
|
.then(() => console.log('workbox done'))
|
||||||
|
|
||||||
|
Promise.all([esbuildPromise, workboxPromise]).then(() =>
|
||||||
|
console.log('build complete')
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,8 +21,16 @@
|
||||||
"save": "Gem"
|
"save": "Gem"
|
||||||
},
|
},
|
||||||
"loading": {
|
"loading": {
|
||||||
|
"album": "Loader album",
|
||||||
"default": "Loader...",
|
"default": "Loader...",
|
||||||
"shares": null
|
"media": "Loader medier",
|
||||||
|
"page": "Loader side",
|
||||||
|
"paginate": {
|
||||||
|
"faces": "Loader flere personer",
|
||||||
|
"media": "Loader flere medier"
|
||||||
|
},
|
||||||
|
"shares": "Loader delinger...",
|
||||||
|
"timeline": "Loader tidslinje"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
|
@ -36,11 +44,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"loading": {
|
|
||||||
"paginate": {
|
|
||||||
"media": "Loader flere medier"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"login_page": {
|
"login_page": {
|
||||||
"field": {
|
"field": {
|
||||||
"password": "Adgangskode",
|
"password": "Adgangskode",
|
||||||
|
@ -59,6 +62,16 @@
|
||||||
},
|
},
|
||||||
"welcome": "Velkommen til Photoview"
|
"welcome": "Velkommen til Photoview"
|
||||||
},
|
},
|
||||||
|
"people_page": {
|
||||||
|
"face_group": {
|
||||||
|
"label_placeholder": "Navn",
|
||||||
|
"unlabeled": "Ikke navngivet"
|
||||||
|
},
|
||||||
|
"recognize_unlabeled_faces_button": "Genkend ikke navngivede ansigter"
|
||||||
|
},
|
||||||
|
"routes": {
|
||||||
|
"page_not_found": "Side ikke fundet"
|
||||||
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"concurrent_workers": {
|
"concurrent_workers": {
|
||||||
"description": "Det maksimale antal medier som må skannes samtidig",
|
"description": "Det maksimale antal medier som må skannes samtidig",
|
||||||
|
@ -122,6 +135,15 @@
|
||||||
"title": "Brugere"
|
"title": "Brugere"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"share_page": {
|
||||||
|
"protected_share": {
|
||||||
|
"description": "Denne deling er låst med en adgangskode.",
|
||||||
|
"title": "Beskyttet deling"
|
||||||
|
},
|
||||||
|
"share_not_found": "Deling blev ikke fundet",
|
||||||
|
"share_not_found_description": "Måske er delingen udløbet eller blevet slettet.",
|
||||||
|
"wrong_password": "Forkert adgangskode, prøv venligst igen."
|
||||||
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
"album": {
|
"album": {
|
||||||
"title": "Album indstillinger"
|
"title": "Album indstillinger"
|
||||||
|
@ -202,6 +224,7 @@
|
||||||
},
|
},
|
||||||
"title": {
|
"title": {
|
||||||
"loading_album": "Loader album",
|
"loading_album": "Loader album",
|
||||||
|
"people": "Personer",
|
||||||
"settings": "Indstillinger"
|
"settings": "Indstillinger"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,8 +21,16 @@
|
||||||
"save": "Save"
|
"save": "Save"
|
||||||
},
|
},
|
||||||
"loading": {
|
"loading": {
|
||||||
|
"album": "Loading album",
|
||||||
"default": "Loading...",
|
"default": "Loading...",
|
||||||
"shares": "Loading shares..."
|
"media": "Loading media",
|
||||||
|
"page": "Loading page",
|
||||||
|
"paginate": {
|
||||||
|
"faces": "Loading more people",
|
||||||
|
"media": "Loading more media"
|
||||||
|
},
|
||||||
|
"shares": "Loading shares...",
|
||||||
|
"timeline": "Loading timeline"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
|
@ -36,11 +44,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"loading": {
|
|
||||||
"paginate": {
|
|
||||||
"media": "Loading more media"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"login_page": {
|
"login_page": {
|
||||||
"field": {
|
"field": {
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
|
@ -59,6 +62,16 @@
|
||||||
},
|
},
|
||||||
"welcome": "Welcome to Photoview"
|
"welcome": "Welcome to Photoview"
|
||||||
},
|
},
|
||||||
|
"people_page": {
|
||||||
|
"face_group": {
|
||||||
|
"label_placeholder": "Label",
|
||||||
|
"unlabeled": "Unlabeled"
|
||||||
|
},
|
||||||
|
"recognize_unlabeled_faces_button": "Recognize unlabeled faces"
|
||||||
|
},
|
||||||
|
"routes": {
|
||||||
|
"page_not_found": "Page not found"
|
||||||
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"concurrent_workers": {
|
"concurrent_workers": {
|
||||||
"description": "The maximum amount of scanner jobs that is allowed to run at once",
|
"description": "The maximum amount of scanner jobs that is allowed to run at once",
|
||||||
|
@ -89,7 +102,7 @@
|
||||||
"submit": "Add user"
|
"submit": "Add user"
|
||||||
},
|
},
|
||||||
"confirm_delete_user": {
|
"confirm_delete_user": {
|
||||||
"action": "Delete {user}",
|
"action": "Delete {{user}}",
|
||||||
"description": "<0>Are you sure, you want to delete <1></1>?</0><p>This action cannot be undone</p>",
|
"description": "<0>Are you sure, you want to delete <1></1>?</0><p>This action cannot be undone</p>",
|
||||||
"title": "Delete user"
|
"title": "Delete user"
|
||||||
},
|
},
|
||||||
|
@ -122,6 +135,15 @@
|
||||||
"title": "Users"
|
"title": "Users"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"share_page": {
|
||||||
|
"protected_share": {
|
||||||
|
"description": "This share is protected with a password.",
|
||||||
|
"title": "Protected share"
|
||||||
|
},
|
||||||
|
"share_not_found": "Share not found",
|
||||||
|
"share_not_found_description": "Maybe the share has expired or has been deleted.",
|
||||||
|
"wrong_password": "Wrong password, please try again."
|
||||||
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
"album": {
|
"album": {
|
||||||
"title": "Album options"
|
"title": "Album options"
|
||||||
|
@ -202,6 +224,7 @@
|
||||||
},
|
},
|
||||||
"title": {
|
"title": {
|
||||||
"loading_album": "Loading album",
|
"loading_album": "Loading album",
|
||||||
|
"people": "People",
|
||||||
"settings": "Settings"
|
"settings": "Settings"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,8 +53,8 @@
|
||||||
"workbox-build": "^6.1.2"
|
"workbox-build": "^6.1.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node build.mjs watch",
|
"start": "node --experimental-modules build.mjs watch",
|
||||||
"build": "NODE_ENV=production node build.mjs",
|
"build": "NODE_ENV=production node --experimental-modules build.mjs",
|
||||||
"test": "npm run lint && npm run jest",
|
"test": "npm run lint && npm run jest",
|
||||||
"lint": "eslint ./src --max-warnings 0 --cache",
|
"lint": "eslint ./src --max-warnings 0 --cache",
|
||||||
"jest": "jest",
|
"jest": "jest",
|
||||||
|
|
|
@ -9,6 +9,8 @@ import { MemoryRouter } from 'react-router-dom'
|
||||||
|
|
||||||
import * as authentication from './helpers/authentication'
|
import * as authentication from './helpers/authentication'
|
||||||
|
|
||||||
|
require('./localization').default()
|
||||||
|
|
||||||
jest.mock('./helpers/authentication.js')
|
jest.mock('./helpers/authentication.js')
|
||||||
|
|
||||||
test('Layout component', async () => {
|
test('Layout component', async () => {
|
||||||
|
|
|
@ -161,7 +161,7 @@ function AlbumPage({ match }) {
|
||||||
/>
|
/>
|
||||||
<PaginateLoader
|
<PaginateLoader
|
||||||
active={!finishedLoadingMore && !loading}
|
active={!finishedLoadingMore && !loading}
|
||||||
text={t('loading.paginate.media', 'Loading more media')}
|
text={t('general.loading.paginate.media', 'Loading more media')}
|
||||||
/>
|
/>
|
||||||
</Layout>
|
</Layout>
|
||||||
)
|
)
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { Button, Icon, Input } from 'semantic-ui-react'
|
||||||
import FaceCircleImage from './FaceCircleImage'
|
import FaceCircleImage from './FaceCircleImage'
|
||||||
import useScrollPagination from '../../hooks/useScrollPagination'
|
import useScrollPagination from '../../hooks/useScrollPagination'
|
||||||
import PaginateLoader from '../../components/PaginateLoader'
|
import PaginateLoader from '../../components/PaginateLoader'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export const MY_FACES_QUERY = gql`
|
export const MY_FACES_QUERY = gql`
|
||||||
query myFaces($limit: Int, $offset: Int) {
|
query myFaces($limit: Int, $offset: Int) {
|
||||||
|
@ -73,6 +74,7 @@ const FaceDetailsButton = styled.button`
|
||||||
const FaceLabel = styled.span``
|
const FaceLabel = styled.span``
|
||||||
|
|
||||||
const FaceDetails = ({ group }) => {
|
const FaceDetails = ({ group }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const [editLabel, setEditLabel] = useState(false)
|
const [editLabel, setEditLabel] = useState(false)
|
||||||
const [inputValue, setInputValue] = useState(group.label ?? '')
|
const [inputValue, setInputValue] = useState(group.label ?? '')
|
||||||
const inputRef = createRef()
|
const inputRef = createRef()
|
||||||
|
@ -124,7 +126,9 @@ const FaceDetails = ({ group }) => {
|
||||||
onClick={() => setEditLabel(true)}
|
onClick={() => setEditLabel(true)}
|
||||||
>
|
>
|
||||||
<FaceImagesCount>{group.imageFaceCount}</FaceImagesCount>
|
<FaceImagesCount>{group.imageFaceCount}</FaceImagesCount>
|
||||||
<FaceLabel>{group.label ?? 'Unlabeled'}</FaceLabel>
|
<FaceLabel>
|
||||||
|
{group.label ?? t('people_page.face_group.unlabeled', 'Unlabeled')}
|
||||||
|
</FaceLabel>
|
||||||
<EditIcon name="pencil" />
|
<EditIcon name="pencil" />
|
||||||
</FaceDetailsButton>
|
</FaceDetailsButton>
|
||||||
)
|
)
|
||||||
|
@ -135,7 +139,7 @@ const FaceDetails = ({ group }) => {
|
||||||
loading={loading}
|
loading={loading}
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
size="mini"
|
size="mini"
|
||||||
placeholder="Label"
|
placeholder={t('people_page.face_group.label_placeholder', 'Label')}
|
||||||
icon="arrow right"
|
icon="arrow right"
|
||||||
value={inputValue}
|
value={inputValue}
|
||||||
onKeyUp={onKeyUp}
|
onKeyUp={onKeyUp}
|
||||||
|
@ -199,6 +203,7 @@ const FaceGroupsWrapper = styled.div`
|
||||||
`
|
`
|
||||||
|
|
||||||
const PeopleGallery = () => {
|
const PeopleGallery = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { data, error, loading, fetchMore } = useQuery(MY_FACES_QUERY, {
|
const { data, error, loading, fetchMore } = useQuery(MY_FACES_QUERY, {
|
||||||
variables: {
|
variables: {
|
||||||
limit: 50,
|
limit: 50,
|
||||||
|
@ -230,7 +235,7 @@ const PeopleGallery = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout title={'People'}>
|
<Layout title={t('title.people', 'People')}>
|
||||||
<Button
|
<Button
|
||||||
loading={recognizeUnlabeledLoading}
|
loading={recognizeUnlabeledLoading}
|
||||||
disabled={recognizeUnlabeledLoading}
|
disabled={recognizeUnlabeledLoading}
|
||||||
|
@ -239,12 +244,15 @@ const PeopleGallery = () => {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon name="sync" />
|
<Icon name="sync" />
|
||||||
Recognize unlabeled faces
|
{t(
|
||||||
|
'people_page.recognize_unlabeled_faces_button',
|
||||||
|
'Recognize unlabeled faces'
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
<FaceGroupsWrapper ref={containerElem}>{faces}</FaceGroupsWrapper>
|
<FaceGroupsWrapper ref={containerElem}>{faces}</FaceGroupsWrapper>
|
||||||
<PaginateLoader
|
<PaginateLoader
|
||||||
active={!finishedLoadingMore && !loading}
|
active={!finishedLoadingMore && !loading}
|
||||||
text="Loading more people"
|
text={t('general.loading.paginate.faces', 'Loading more people')}
|
||||||
/>
|
/>
|
||||||
</Layout>
|
</Layout>
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,6 +4,7 @@ import Layout from '../../Layout'
|
||||||
import AlbumGallery from '../../components/albumGallery/AlbumGallery'
|
import AlbumGallery from '../../components/albumGallery/AlbumGallery'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import { gql, useQuery } from '@apollo/client'
|
import { gql, useQuery } from '@apollo/client'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export const SHARE_ALBUM_QUERY = gql`
|
export const SHARE_ALBUM_QUERY = gql`
|
||||||
query shareAlbumQuery(
|
query shareAlbumQuery(
|
||||||
|
@ -73,6 +74,7 @@ const AlbumSharePageWrapper = styled.div`
|
||||||
`
|
`
|
||||||
|
|
||||||
const AlbumSharePage = ({ albumID, token, password }) => {
|
const AlbumSharePage = ({ albumID, token, password }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { data, loading, error } = useQuery(SHARE_ALBUM_QUERY, {
|
const { data, loading, error } = useQuery(SHARE_ALBUM_QUERY, {
|
||||||
variables: {
|
variables: {
|
||||||
id: albumID,
|
id: albumID,
|
||||||
|
@ -88,14 +90,18 @@ const AlbumSharePage = ({ albumID, token, password }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return 'Loading...'
|
return t('general.loading.default', 'Loading...')
|
||||||
}
|
}
|
||||||
|
|
||||||
const album = data.album
|
const album = data.album
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AlbumSharePageWrapper data-testid="AlbumSharePage">
|
<AlbumSharePageWrapper data-testid="AlbumSharePage">
|
||||||
<Layout title={album ? album.title : 'Loading album'}>
|
<Layout
|
||||||
|
title={
|
||||||
|
album ? album.title : t('general.loading.album', 'Loading album')
|
||||||
|
}
|
||||||
|
>
|
||||||
<AlbumGallery
|
<AlbumGallery
|
||||||
album={album}
|
album={album}
|
||||||
customAlbumLink={albumId => `/share/${token}/${albumId}`}
|
customAlbumLink={albumId => `/share/${token}/${albumId}`}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
} from '../../helpers/authentication'
|
} from '../../helpers/authentication'
|
||||||
import AlbumSharePage from './AlbumSharePage'
|
import AlbumSharePage from './AlbumSharePage'
|
||||||
import MediaSharePage from './MediaSharePage'
|
import MediaSharePage from './MediaSharePage'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export const SHARE_TOKEN_QUERY = gql`
|
export const SHARE_TOKEN_QUERY = gql`
|
||||||
query SharePageToken($token: String!, $password: String) {
|
query SharePageToken($token: String!, $password: String) {
|
||||||
|
@ -71,6 +72,8 @@ export const VALIDATE_TOKEN_PASSWORD_QUERY = gql`
|
||||||
`
|
`
|
||||||
|
|
||||||
const AuthorizedTokenRoute = ({ match }) => {
|
const AuthorizedTokenRoute = ({ match }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const token = match.params.token
|
const token = match.params.token
|
||||||
const password = getSharePassword(token)
|
const password = getSharePassword(token)
|
||||||
|
|
||||||
|
@ -122,7 +125,7 @@ const AuthorizedTokenRoute = ({ match }) => {
|
||||||
return <MediaSharePage media={data.shareToken.media} />
|
return <MediaSharePage media={data.shareToken.media} />
|
||||||
}
|
}
|
||||||
|
|
||||||
return <h1>Share not found</h1>
|
return <h1>{t('share_page.share_not_found', 'Share not found')}</h1>
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthorizedTokenRoute.propTypes = {
|
AuthorizedTokenRoute.propTypes = {
|
||||||
|
@ -138,6 +141,8 @@ const ProtectedTokenEnterPassword = ({
|
||||||
refetchWithPassword,
|
refetchWithPassword,
|
||||||
loading = false,
|
loading = false,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const [passwordValue, setPasswordValue] = useState('')
|
const [passwordValue, setPasswordValue] = useState('')
|
||||||
const [invalidPassword, setInvalidPassword] = useState(false)
|
const [invalidPassword, setInvalidPassword] = useState(false)
|
||||||
|
|
||||||
|
@ -150,7 +155,9 @@ const ProtectedTokenEnterPassword = ({
|
||||||
if (invalidPassword && !loading) {
|
if (invalidPassword && !loading) {
|
||||||
errorMessage = (
|
errorMessage = (
|
||||||
<Message negative>
|
<Message negative>
|
||||||
<Message.Content>Wrong password, please try again.</Message.Content>
|
<Message.Content>
|
||||||
|
{t('share_page.wrong_password', 'Wrong password, please try again.')}
|
||||||
|
</Message.Content>
|
||||||
</Message>
|
</Message>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -158,18 +165,23 @@ const ProtectedTokenEnterPassword = ({
|
||||||
return (
|
return (
|
||||||
<MessageContainer>
|
<MessageContainer>
|
||||||
<Header as="h1" style={{ fontWeight: 400 }}>
|
<Header as="h1" style={{ fontWeight: 400 }}>
|
||||||
Protected share
|
{t('share_page.protected_share.title', 'Protected share')}
|
||||||
</Header>
|
</Header>
|
||||||
<p>This share is protected with a password.</p>
|
<p>
|
||||||
|
{t(
|
||||||
|
'share_page.protected_share.description',
|
||||||
|
'This share is protected with a password.'
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
<Form>
|
<Form>
|
||||||
<Form.Field>
|
<Form.Field>
|
||||||
<label>Password</label>
|
<label>{t('login_page.field.password', 'Password')}</label>
|
||||||
<Input
|
<Input
|
||||||
loading={loading}
|
loading={loading}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
onKeyUp={event => event.key == 'Enter' && onSubmit()}
|
onKeyUp={event => event.key == 'Enter' && onSubmit()}
|
||||||
onChange={e => setPasswordValue(e.target.value)}
|
onChange={e => setPasswordValue(e.target.value)}
|
||||||
placeholder="Password"
|
placeholder={t('login_page.field.password', 'Password')}
|
||||||
type="password"
|
type="password"
|
||||||
icon={<Icon onClick={onSubmit} link name="arrow right" />}
|
icon={<Icon onClick={onSubmit} link name="arrow right" />}
|
||||||
/>
|
/>
|
||||||
|
@ -186,6 +198,8 @@ ProtectedTokenEnterPassword.propTypes = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const TokenRoute = ({ match }) => {
|
const TokenRoute = ({ match }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const token = match.params.token
|
const token = match.params.token
|
||||||
|
|
||||||
const { loading, error, data, refetch } = useQuery(
|
const { loading, error, data, refetch } = useQuery(
|
||||||
|
@ -203,8 +217,13 @@ const TokenRoute = ({ match }) => {
|
||||||
if (error.message == 'GraphQL error: share not found') {
|
if (error.message == 'GraphQL error: share not found') {
|
||||||
return (
|
return (
|
||||||
<MessageContainer>
|
<MessageContainer>
|
||||||
<h1>Share not found</h1>
|
<h1>{t('share_page.share_not_found', 'Share not found')}</h1>
|
||||||
<p>Maybe the share has expired or has been deleted.</p>
|
<p>
|
||||||
|
{t(
|
||||||
|
'share_page.share_not_found_description',
|
||||||
|
'Maybe the share has expired or has been deleted.'
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
</MessageContainer>
|
</MessageContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -225,7 +244,7 @@ const TokenRoute = ({ match }) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loading) return 'Loading...'
|
if (loading) return t('general.loading.default', 'Loading...')
|
||||||
|
|
||||||
return <AuthorizedTokenRoute match={match} />
|
return <AuthorizedTokenRoute match={match} />
|
||||||
}
|
}
|
||||||
|
@ -234,16 +253,20 @@ TokenRoute.propTypes = {
|
||||||
match: PropTypes.object.isRequired,
|
match: PropTypes.object.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
const SharePage = ({ match }) => (
|
const SharePage = ({ match }) => {
|
||||||
<Switch>
|
const { t } = useTranslation()
|
||||||
<Route path={`${match.url}/:token`}>
|
|
||||||
{({ match }) => {
|
return (
|
||||||
return <TokenRoute match={match} />
|
<Switch>
|
||||||
}}
|
<Route path={`${match.url}/:token`}>
|
||||||
</Route>
|
{({ match }) => {
|
||||||
<Route path="/">Route not found</Route>
|
return <TokenRoute match={match} />
|
||||||
</Switch>
|
}}
|
||||||
)
|
</Route>
|
||||||
|
<Route path="/">{t('routes.page_not_found', 'Page not found')}</Route>
|
||||||
|
</Switch>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
SharePage.propTypes = {
|
SharePage.propTypes = {
|
||||||
...RouterProps,
|
...RouterProps,
|
||||||
|
|
|
@ -18,6 +18,8 @@ import SharePage, {
|
||||||
import { SIDEBAR_DOWNLOAD_QUERY } from '../../components/sidebar/SidebarDownload'
|
import { SIDEBAR_DOWNLOAD_QUERY } from '../../components/sidebar/SidebarDownload'
|
||||||
import { SHARE_ALBUM_QUERY } from './AlbumSharePage'
|
import { SHARE_ALBUM_QUERY } from './AlbumSharePage'
|
||||||
|
|
||||||
|
require('../../localization').default()
|
||||||
|
|
||||||
describe('load correct share page, based on graphql query', () => {
|
describe('load correct share page, based on graphql query', () => {
|
||||||
const token = 'TOKEN123'
|
const token = 'TOKEN123'
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import PresentView from './presentView/PresentView'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { SidebarContext } from '../sidebar/Sidebar'
|
import { SidebarContext } from '../sidebar/Sidebar'
|
||||||
import MediaSidebar from '../sidebar/MediaSidebar'
|
import MediaSidebar from '../sidebar/MediaSidebar'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
const Gallery = styled.div`
|
const Gallery = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -41,6 +42,7 @@ const PhotoGallery = ({
|
||||||
previousImage,
|
previousImage,
|
||||||
onFavorite,
|
onFavorite,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { updateSidebar } = useContext(SidebarContext)
|
const { updateSidebar } = useContext(SidebarContext)
|
||||||
|
|
||||||
const activeImage = media && activeIndex != -1 && media[activeIndex]
|
const activeImage = media && activeIndex != -1 && media[activeIndex]
|
||||||
|
@ -80,7 +82,9 @@ const PhotoGallery = ({
|
||||||
return (
|
return (
|
||||||
<ClearWrap>
|
<ClearWrap>
|
||||||
<Gallery>
|
<Gallery>
|
||||||
<Loader active={loading}>Loading images</Loader>
|
<Loader active={loading}>
|
||||||
|
{t('general.loading.media', 'Loading media')}
|
||||||
|
</Loader>
|
||||||
{getPhotoElements(updateSidebar)}
|
{getPhotoElements(updateSidebar)}
|
||||||
<PhotoFiller />
|
<PhotoFiller />
|
||||||
</Gallery>
|
</Gallery>
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { Route, Switch, Redirect } from 'react-router-dom'
|
||||||
import { Loader } from 'semantic-ui-react'
|
import { Loader } from 'semantic-ui-react'
|
||||||
import Layout from '../../Layout'
|
import Layout from '../../Layout'
|
||||||
import { clearTokenCookie } from '../../helpers/authentication'
|
import { clearTokenCookie } from '../../helpers/authentication'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
const AuthorizedRoute = React.lazy(() => import('./AuthorizedRoute'))
|
const AuthorizedRoute = React.lazy(() => import('./AuthorizedRoute'))
|
||||||
|
|
||||||
|
@ -26,11 +27,13 @@ const SettingsPage = React.lazy(() =>
|
||||||
)
|
)
|
||||||
|
|
||||||
const Routes = () => {
|
const Routes = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Suspense
|
<React.Suspense
|
||||||
fallback={
|
fallback={
|
||||||
<Layout>
|
<Layout>
|
||||||
<Loader active>Loading page</Loader>
|
<Loader active>{t('general.loading.page', 'Loading page')}</Loader>
|
||||||
</Layout>
|
</Layout>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -51,7 +54,11 @@ const Routes = () => {
|
||||||
<AuthorizedRoute path="/people/:person?" component={PeoplePage} />
|
<AuthorizedRoute path="/people/:person?" component={PeoplePage} />
|
||||||
<AuthorizedRoute admin path="/settings" component={SettingsPage} />
|
<AuthorizedRoute admin path="/settings" component={SettingsPage} />
|
||||||
<Route path="/" exact render={() => <Redirect to="/photos" />} />
|
<Route path="/" exact render={() => <Redirect to="/photos" />} />
|
||||||
<Route render={() => <div>Page not found</div>} />
|
<Route
|
||||||
|
render={() => (
|
||||||
|
<div>{t('routes.page_not_found', 'Page not found')}</div>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</Switch>
|
</Switch>
|
||||||
</React.Suspense>
|
</React.Suspense>
|
||||||
)
|
)
|
||||||
|
|
|
@ -10,6 +10,8 @@ import {
|
||||||
} from '@testing-library/react'
|
} from '@testing-library/react'
|
||||||
import { MemoryRouter } from 'react-router-dom'
|
import { MemoryRouter } from 'react-router-dom'
|
||||||
|
|
||||||
|
require('../../localization').default()
|
||||||
|
|
||||||
describe('routes', () => {
|
describe('routes', () => {
|
||||||
test('unauthorized root path should navigate to login page', async () => {
|
test('unauthorized root path should navigate to login page', async () => {
|
||||||
jest.mock('../../Pages/LoginPage/LoginPage.js', () => () => (
|
jest.mock('../../Pages/LoginPage/LoginPage.js', () => () => (
|
||||||
|
|
|
@ -169,6 +169,7 @@ export const MetadataInfo = ({ media }) => {
|
||||||
exif.focalLength = `${exif.focalLength}mm`
|
exif.focalLength = `${exif.focalLength}mm`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const flash = flashLookup(t)
|
||||||
if (!isNil(exif.flash) && flash[exif.flash]) {
|
if (!isNil(exif.flash) && flash[exif.flash]) {
|
||||||
exif.flash = flash[exif.flash]
|
exif.flash = flash[exif.flash]
|
||||||
}
|
}
|
||||||
|
@ -249,7 +250,7 @@ const exposureProgramsLookup = t => ({
|
||||||
})
|
})
|
||||||
|
|
||||||
// From https://exiftool.org/TagNames/EXIF.html#Flash
|
// From https://exiftool.org/TagNames/EXIF.html#Flash
|
||||||
const flash = t => {
|
const flashLookup = t => {
|
||||||
const values = {
|
const values = {
|
||||||
no_flash: t('sidebar.media.exif.flash.no_flash', 'No Flash'),
|
no_flash: t('sidebar.media.exif.flash.no_flash', 'No Flash'),
|
||||||
fired: t('sidebar.media.exif.flash.fired', 'Fired'),
|
fired: t('sidebar.media.exif.flash.fired', 'Fired'),
|
||||||
|
|
|
@ -4,6 +4,8 @@ import React from 'react'
|
||||||
import { render, screen } from '@testing-library/react'
|
import { render, screen } from '@testing-library/react'
|
||||||
import { MetadataInfo } from './MediaSidebar'
|
import { MetadataInfo } from './MediaSidebar'
|
||||||
|
|
||||||
|
require('../../localization').default()
|
||||||
|
|
||||||
describe('MetadataInfo', () => {
|
describe('MetadataInfo', () => {
|
||||||
test('without EXIF information', async () => {
|
test('without EXIF information', async () => {
|
||||||
const media = {
|
const media = {
|
||||||
|
@ -33,11 +35,11 @@ describe('MetadataInfo', () => {
|
||||||
expect(screen.queryByText('Maker')).not.toBeInTheDocument()
|
expect(screen.queryByText('Maker')).not.toBeInTheDocument()
|
||||||
expect(screen.queryByText('Lens')).not.toBeInTheDocument()
|
expect(screen.queryByText('Lens')).not.toBeInTheDocument()
|
||||||
expect(screen.queryByText('Program')).not.toBeInTheDocument()
|
expect(screen.queryByText('Program')).not.toBeInTheDocument()
|
||||||
expect(screen.queryByText('Date Shot')).not.toBeInTheDocument()
|
expect(screen.queryByText('Date shot')).not.toBeInTheDocument()
|
||||||
expect(screen.queryByText('Exposure')).not.toBeInTheDocument()
|
expect(screen.queryByText('Exposure')).not.toBeInTheDocument()
|
||||||
expect(screen.queryByText('Aperture')).not.toBeInTheDocument()
|
expect(screen.queryByText('Aperture')).not.toBeInTheDocument()
|
||||||
expect(screen.queryByText('ISO')).not.toBeInTheDocument()
|
expect(screen.queryByText('ISO')).not.toBeInTheDocument()
|
||||||
expect(screen.queryByText('Focal Length')).not.toBeInTheDocument()
|
expect(screen.queryByText('Focal length')).not.toBeInTheDocument()
|
||||||
expect(screen.queryByText('Flash')).not.toBeInTheDocument()
|
expect(screen.queryByText('Flash')).not.toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -77,7 +79,7 @@ describe('MetadataInfo', () => {
|
||||||
expect(screen.getByText('Program')).toBeInTheDocument()
|
expect(screen.getByText('Program')).toBeInTheDocument()
|
||||||
expect(screen.getByText('Canon EOS R')).toBeInTheDocument()
|
expect(screen.getByText('Canon EOS R')).toBeInTheDocument()
|
||||||
|
|
||||||
expect(screen.getByText('Date Shot')).toBeInTheDocument()
|
expect(screen.getByText('Date shot')).toBeInTheDocument()
|
||||||
|
|
||||||
expect(screen.getByText('Exposure')).toBeInTheDocument()
|
expect(screen.getByText('Exposure')).toBeInTheDocument()
|
||||||
expect(screen.getByText('1/60')).toBeInTheDocument()
|
expect(screen.getByText('1/60')).toBeInTheDocument()
|
||||||
|
@ -91,7 +93,7 @@ describe('MetadataInfo', () => {
|
||||||
expect(screen.getByText('ISO')).toBeInTheDocument()
|
expect(screen.getByText('ISO')).toBeInTheDocument()
|
||||||
expect(screen.getByText('100')).toBeInTheDocument()
|
expect(screen.getByText('100')).toBeInTheDocument()
|
||||||
|
|
||||||
expect(screen.getByText('Focal Length')).toBeInTheDocument()
|
expect(screen.getByText('Focal length')).toBeInTheDocument()
|
||||||
expect(screen.getByText('24mm')).toBeInTheDocument()
|
expect(screen.getByText('24mm')).toBeInTheDocument()
|
||||||
|
|
||||||
expect(screen.getByText('Flash')).toBeInTheDocument()
|
expect(screen.getByText('Flash')).toBeInTheDocument()
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { FavoritesCheckbox } from '../AlbumFilter'
|
||||||
import useScrollPagination from '../../hooks/useScrollPagination'
|
import useScrollPagination from '../../hooks/useScrollPagination'
|
||||||
import PaginateLoader from '../PaginateLoader'
|
import PaginateLoader from '../PaginateLoader'
|
||||||
import LazyLoad from '../../helpers/LazyLoad'
|
import LazyLoad from '../../helpers/LazyLoad'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
const MY_TIMELINE_QUERY = gql`
|
const MY_TIMELINE_QUERY = gql`
|
||||||
query myTimeline($onlyFavorites: Boolean, $limit: Int, $offset: Int) {
|
query myTimeline($onlyFavorites: Boolean, $limit: Int, $offset: Int) {
|
||||||
|
@ -54,6 +55,7 @@ const GalleryWrapper = styled.div`
|
||||||
`
|
`
|
||||||
|
|
||||||
const TimelineGallery = () => {
|
const TimelineGallery = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const [activeIndex, setActiveIndex] = useState({
|
const [activeIndex, setActiveIndex] = useState({
|
||||||
dateGroup: -1,
|
dateGroup: -1,
|
||||||
albumGroup: -1,
|
albumGroup: -1,
|
||||||
|
@ -212,7 +214,9 @@ const TimelineGallery = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Loader active={loading}>Loading timeline</Loader>
|
<Loader active={loading}>
|
||||||
|
{t('general.loading.timeline', 'Loading timeline')}
|
||||||
|
</Loader>
|
||||||
<FavoritesCheckbox
|
<FavoritesCheckbox
|
||||||
onlyFavorites={onlyFavorites}
|
onlyFavorites={onlyFavorites}
|
||||||
setOnlyFavorites={setOnlyFavorites}
|
setOnlyFavorites={setOnlyFavorites}
|
||||||
|
@ -220,7 +224,7 @@ const TimelineGallery = () => {
|
||||||
<GalleryWrapper ref={containerElem}>{timelineGroups}</GalleryWrapper>
|
<GalleryWrapper ref={containerElem}>{timelineGroups}</GalleryWrapper>
|
||||||
<PaginateLoader
|
<PaginateLoader
|
||||||
active={!finishedLoadingMore && !loading}
|
active={!finishedLoadingMore && !loading}
|
||||||
text="Loading more media"
|
text={t('general.loading.paginate.media', 'Loading more media')}
|
||||||
/>
|
/>
|
||||||
{presenting && (
|
{presenting && (
|
||||||
<PresentView
|
<PresentView
|
||||||
|
|
|
@ -8,24 +8,9 @@ import client from './apolloClient'
|
||||||
import { ApolloProvider } from '@apollo/client'
|
import { ApolloProvider } from '@apollo/client'
|
||||||
import { BrowserRouter as Router } from 'react-router-dom'
|
import { BrowserRouter as Router } from 'react-router-dom'
|
||||||
import i18n from 'i18next'
|
import i18n from 'i18next'
|
||||||
import { initReactI18next } from 'react-i18next'
|
import setupLocalization from './localization'
|
||||||
|
|
||||||
i18n.use(initReactI18next).init({
|
setupLocalization()
|
||||||
resources: {
|
|
||||||
en: {
|
|
||||||
translation: {
|
|
||||||
'Welcome to React': 'Welcome to React and react-i18next',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lng: 'en',
|
|
||||||
fallbackLng: 'en',
|
|
||||||
returnNull: false,
|
|
||||||
|
|
||||||
interpolation: {
|
|
||||||
escapeValue: false,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
import('../extractedTranslations/da/translation.json').then(danish => {
|
import('../extractedTranslations/da/translation.json').then(danish => {
|
||||||
i18n.addResourceBundle('da', 'translation', danish)
|
i18n.addResourceBundle('da', 'translation', danish)
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
import i18n from 'i18next'
|
||||||
|
import { initReactI18next } from 'react-i18next'
|
||||||
|
|
||||||
|
export default function setupLocalization() {
|
||||||
|
i18n.use(initReactI18next).init({
|
||||||
|
resources: {
|
||||||
|
en: {
|
||||||
|
translation: {
|
||||||
|
'Welcome to React': 'Welcome to React and react-i18next',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
lng: 'en',
|
||||||
|
fallbackLng: 'en',
|
||||||
|
returnNull: false,
|
||||||
|
|
||||||
|
interpolation: {
|
||||||
|
escapeValue: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in New Issue