1
0
Fork 0

wip(org): Added translation support and locale picker

This commit is contained in:
Joost De Cock 2022-05-29 11:58:48 +02:00
parent 0ee9b36451
commit f1c291491f
11 changed files with 281 additions and 27 deletions

View file

@ -0,0 +1,10 @@
const DesignTeaser = ({ design=false }) => {
return (
<div>
<p>Tease {design} here</p>
</div>
)
}
export default DesignTeaser

View file

@ -2,6 +2,7 @@ import { useState, useEffect } from 'react'
import Logo from 'shared/components/logos/freesewing.js' import Logo from 'shared/components/logos/freesewing.js'
import Link from 'next/link' import Link from 'next/link'
import ThemePicker from 'shared/components/theme-picker.js' import ThemePicker from 'shared/components/theme-picker.js'
import LocalePicker from 'shared/components/locale-picker.js'
import CloseIcon from 'shared/components/icons/close.js' import CloseIcon from 'shared/components/icons/close.js'
import MenuIcon from 'shared/components/icons/menu.js' import MenuIcon from 'shared/components/icons/menu.js'
import SearchIcon from 'shared/components/icons/search.js' import SearchIcon from 'shared/components/icons/search.js'
@ -114,6 +115,7 @@ const Header = ({ app, setSearch }) => {
</div> </div>
<div className="hidden md:flex flex-row items-center"> <div className="hidden md:flex flex-row items-center">
<ThemePicker app={app} /> <ThemePicker app={app} />
<LocalePicker app={app} />
</div> </div>
</div> </div>
</div> </div>

View file

@ -0,0 +1,11 @@
import PatternDocs from './pattern-docs.js'
import PatternOptions from './pattern-options.js'
import PatternMeasurements from './pattern-measurements.js'
const components = {
PatternDocs,
PatternOptions,
PatternMeasurements,
}
export default components

View file

@ -0,0 +1,138 @@
import React from 'react'
import { capitalize } from 'shared/utils.js'
import Link from 'next/link'
import { getConfig } from 'shared/designs/index.js'
import Popout from 'shared/components/popout.js'
import { useTranslation } from 'next-i18next'
import DesignTeaser from 'site/components/designs/teaser.js'
//import PatternOptions from './pattern-options'
//import PatternMeasurements from './pattern-measurements'
const PatternDocs = ({ pattern=false }) => {
const { t } = useTranslation(['docs'])
if (!pattern) return <p>Please specify a pattern prop when using the PatternDocs component</p>
const config = getConfig(pattern)
console.log({pattern, config})
return (
<>
{config.deprecated && (
<Popout note>
<h5>{t('thingIsDeprecated', { thing: capitalize(pattern)})}</h5>
<p>
{t('weRecommendThingInstead', { thing: capitalize(config.deprecated)})}
</p>
<DesignTeaser design={pattern} />
</Popout>
)}
<pre>{JSON.stringify(config, null ,2)}</pre>
</>
)
return (
<>
{config.deprecated && (
<Popout note>
<h5>{capitalize(pattern)} is deprecated</h5>
<p>
We recommend{' '}
<Link to={`/designs/${info[props.pattern].deprecated}/`}>
{capitalize(info[props.pattern].deprecated)}
</Link>{' '}
instead.
</p>
</Popout>
)}
<div
style={{
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-between',
marginTop: '1rem',
}}
>
<p>
<FormattedMessage id={'patterns.' + props.pattern + '.description'} />
</p>
<div>
<Button
style={{ marginRight: '1rem' }}
color="primary"
variant="contained"
size="large"
href={'/create/' + props.pattern + '/'}
>
<PlayIcon style={{ marginRight: '1rem' }} />
<FormattedMessage
id="app.newThing"
values={{
thing: [capitalize(props.pattern), ' ', <FormattedMessage id={`app.pattern`} />],
}}
/>
</Button>
<p>
<Hashtag
tag={`FreeSewing${capitalize(props.pattern)}`}
title={`${capitalize(props.pattern)} Hashtag`}
/>
</p>
</div>
</div>
<h2>
<FormattedMessage id="app.patternInstructions" />
</h2>
<ul className="links">
<li>
<Link to={'/docs/patterns/' + props.pattern + '/cutting/'}>
{capitalize(props.pattern)} &raquo; <FormattedMessage id="app.cutting" />
</Link>
</li>
<li>
<Link to={'/docs/patterns/' + props.pattern + '/fabric/'}>
{capitalize(props.pattern)} &raquo; <FormattedMessage id="app.fabricOptions" />
</Link>
</li>
<li>
<Link to={'/docs/patterns/' + props.pattern + '/instructions/'}>
{capitalize(props.pattern)} &raquo; <FormattedMessage id="app.instructions" />
</Link>
</li>
<li>
<Link to={'/docs/patterns/' + props.pattern + '/needs/'}>
{capitalize(props.pattern)} &raquo; <FormattedMessage id="app.whatYouNeed" />
</Link>
</li>
</ul>
<h2>
<FormattedMessage id="app.patternOptions" />
</h2>
<PatternOptions pattern={props.pattern} />
{measurements[props.pattern].length > 0 ? (
<>
<h2>
<FormattedMessage id="app.requiredMeasurements" />
</h2>
<PatternMeasurements pattern={props.pattern} app={props.app} />
</>
) : null}
<h2>
<FormattedMessage id="app.examples" />
</h2>
<p>
<FormattedMessage id="intro.txt-showcase" />:
</p>
<ul className="links">
<li>
<Link to={'/showcase/designs/' + props.pattern}>
<FormattedMessage id="app.showcase" /> / {capitalize(props.pattern)}
</Link>
</li>
</ul>
</>
)
}
export default PatternDocs

View file

@ -0,0 +1,33 @@
const PatternMeasurements = (props) => {
return null
const intl = useIntl()
const sortMeasurements = (measurements) => {
if (typeof measurements === 'undefined') return []
let sorted = []
let translated = {}
for (let m of measurements) {
let translation = intl.messages['measurements.' + m] || m
translated[translation] = m
}
let order = Object.keys(translated)
order.sort()
for (let m of order) sorted.push(translated[m])
return sorted
}
return (
<ul className="links">
{sortMeasurements(measurements[props.pattern]).map((m) => (
<li key={m}>
<Link to={'/docs/measurements/' + m.toLowerCase()}>
<FormattedMessage id={'measurements.' + m} />
</Link>
</li>
))}
</ul>
)
}
export default PatternMeasurements

View file

@ -0,0 +1,57 @@
const PatternOptions = (props) => {
return null
const renderOptions = () => {
const groups = optionGroups[props.pattern]
const list = []
for (let l1 in groups) {
let children = []
for (let l2 of groups[l1]) {
if (typeof l2 === 'string') {
children.push(
<li key={props.pattern + l2}>
<Link to={'/docs/patterns/' + props.pattern + '/options/' + l2.toLowerCase()}>
<FormattedMessage id={'options.' + props.pattern + '.' + l2 + '.title'} />
</Link>
</li>
)
} else {
for (let l3 in l2) {
let grandchildren = []
for (let l4 of l2[l3]) {
grandchildren.push(
<li key={props.pattern + l4}>
<Link to={'/docs/patterns/' + props.pattern + '/options/' + l4.toLowerCase()}>
<FormattedMessage id={'options.' + props.pattern + '.' + l4 + '.title'} />
</Link>
</li>
)
}
children.push(
<li key={props.pattern + l3}>
<b>
<FormattedMessage id={'optiongroups.' + l3} />
</b>
<ul className="links">{grandchildren}</ul>
</li>
)
}
}
}
list.push(
<li key={props.pattern + l1}>
<b>
<FormattedMessage id={'optiongroups.' + l1} />
</b>
<ul className="links">{children}</ul>
</li>
)
}
return <ul className="links">{list}</ul>
}
return renderOptions()
}
export default PatternOptions

View file

@ -5,6 +5,7 @@ import useLocalStorage from 'shared/hooks/useLocalStorage.js'
// Prebuild navigation // Prebuild navigation
import prebuildNavigation from 'site/prebuild/navigation.js' import prebuildNavigation from 'site/prebuild/navigation.js'
// Translation // Translation
import { useRouter } from 'next/router'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
/* /*
@ -53,6 +54,7 @@ const buildNavigation = (lang, t) => {
function useApp(full = true) { function useApp(full = true) {
// Load translation method // Load translation method
const locale = useRouter().locale
const { t } = useTranslation() const { t } = useTranslation()
// User color scheme preference // User color scheme preference
@ -63,11 +65,10 @@ function useApp(full = true) {
// Persistent state // Persistent state
const [account, setAccount] = useLocalStorage('account', { username: false }) const [account, setAccount] = useLocalStorage('account', { username: false })
const [theme, setTheme] = useLocalStorage('theme', prefersDarkMode ? 'dark' : 'light') const [theme, setTheme] = useLocalStorage('theme', prefersDarkMode ? 'dark' : 'light')
const [language, setLanguage] = useLocalStorage('language', 'en')
// React State // React State
const [primaryMenu, setPrimaryMenu] = useState(false) const [primaryMenu, setPrimaryMenu] = useState(false)
const [navigation, setNavigation] = useState(buildNavigation(language, t)) const [navigation, setNavigation] = useState(buildNavigation(locale, t))
const [slug, setSlug] = useState('/') const [slug, setSlug] = useState('/')
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
@ -88,10 +89,12 @@ function useApp(full = true) {
return { return {
// Static vars // Static vars
site: 'dev', site: 'org',
// i18n
locale,
// State // State
language,
loading, loading,
navigation, navigation,
primaryMenu, primaryMenu,
@ -99,7 +102,6 @@ function useApp(full = true) {
theme, theme,
// State setters // State setters
setLanguage,
setLoading, setLoading,
setNavigation, setNavigation,
setPrimaryMenu, setPrimaryMenu,

View file

@ -1,3 +0,0 @@
import i18n from '../freesewing.shared/config/i18n.config.mjs'
export default i18n()

View file

@ -7,25 +7,25 @@ import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import Layout from 'site/components/layouts/bare' import Layout from 'site/components/layouts/bare'
import Navigation, { Icons } from 'shared/components/navigation/primary' import Navigation, { Icons } from 'shared/components/navigation/primary'
import DownIcon from 'shared/components/icons/down.js'
const HomePage = (props) => { const HomePage = (props) => {
const app = useApp() const app = useApp()
const { t, i18n } = useTranslation(['ograph']) const { t } = useTranslation(['homepage', 'ograph'])
const { language } = i18n
return ( return (
<Page app={app} title="Welcome to FreeSewing.org" layout={Layout}> <Page app={app} title="Welcome to FreeSewing.org" layout={Layout}>
<Head> <Head>
<meta property="og:title" content="FreeSewing.org" key="title" /> <meta property="og:title" content="FreeSewing.org" key="title" />
<meta property="og:type" content="article" key='type' /> <meta property="og:type" content="article" key='type' />
<meta property="og:description" content={t('og:orgDesc')} key='description' /> <meta property="og:description" content={t('ograph:orgDesc')} key='description' />
<meta property="og:article:author" content='Joost De Cock' key='author' /> <meta property="og:article:author" content='Joost De Cock' key='author' />
<meta property="og:image" content={`https://canary.backend.freesewing.org/og-img/${language}/org/`} key='image' /> <meta property="og:image" content={`https://canary.backend.freesewing.org/og-img/${app.locale}/org/`} key='image' />
<meta property="og:image:type" content="image/png" /> <meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1200" /> <meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" /> <meta property="og:image:height" content="630" />
<meta property="og:url" content="https://freesewing.org/" key='url' /> <meta property="og:url" content="https://freesewing.org/" key='url' />
<meta property="og:locale" content={language} key='locale' /> <meta property="og:locale" content={app.locale} key='locale' />
<meta property="og:site_name" content="freesewing.org" key='site' /> <meta property="og:site_name" content="freesewing.org" key='site' />
</Head> </Head>
<section <section
@ -36,8 +36,8 @@ const HomePage = (props) => {
}} }}
className="m-0 p-0 shadow drop-shadow-lg w-full mb-8" className="m-0 p-0 shadow drop-shadow-lg w-full mb-8"
> >
<div className="mx-auto px-8 flex flex-col items-center justify-around min-h-screen"> <div className="mx-auto px-8 flex flex-col items-center justify-between min-h-screen">
<span>test</span> <span>&nbsp;</span>
<div> <div>
<div className="flex flex-col items-end max-w-4xl"> <div className="flex flex-col items-end max-w-4xl">
<h1 <h1
@ -60,7 +60,7 @@ const HomePage = (props) => {
md:text-4xl md:text-4xl
lg:max-w-1/2 lg:text-4xl xl:pr-0 `} lg:max-w-1/2 lg:text-4xl xl:pr-0 `}
style={{ textShadow: '1px 1px 3px #000', color: 'white' }} style={{ textShadow: '1px 1px 3px #000', color: 'white' }}
dangerouslySetInnerHTML={{ __html: t('orgDescription')}} dangerouslySetInnerHTML={{ __html: t('ograph:orgDescription')}}
/> />
</div> </div>
<Icons app={app} active='/' <Icons app={app} active='/'
@ -72,14 +72,10 @@ const HomePage = (props) => {
flex flex-col items-center capitalize`} flex flex-col items-center capitalize`}
/> />
</div> </div>
<p className="text-neutral-content text-center mt-8"> <div className="text-neutral-content text-center mt-8 text-center">
To learn more about FreeSewing and try our platform {t('scrollDownToLearnMore')}
go to <a <DownIcon className="w-24 h-24 animate-bounce w-full m-auto mt-8"/>
href="https://freesewing.org/" </div>
title="Go to FreeSewing.org"
className="text-secondary font-bold"
>freesewing.org</a>
</p>
</div> </div>
</section> </section>
<div> <div>
@ -126,7 +122,7 @@ export default HomePage
export async function getStaticProps({ locale }) { export async function getStaticProps({ locale }) {
return { return {
props: { props: {
...(await serverSideTranslations('en')), ...(await serverSideTranslations(locale)),
} }
} }
} }

View file

@ -0,0 +1,8 @@
const Down = ({ className="h-6 w-6" }) => (
<svg xmlns="http://www.w3.org/2000/svg" className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
)
export default Down

View file

@ -1,6 +1,6 @@
/* Sourced from heroicons.com - Thanks guys! */ /* Sourced from heroicons.com - Thanks guys! */
const Left = () => ( const Left = ({ className="h-6 w-6" }) => (
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg> </svg>
) )