wip(org): Added translation support and locale picker
This commit is contained in:
parent
0ee9b36451
commit
f1c291491f
11 changed files with 281 additions and 27 deletions
10
packages/freesewing.org/components/designs/teaser.js
Normal file
10
packages/freesewing.org/components/designs/teaser.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
const DesignTeaser = ({ design=false }) => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p>Tease {design} here</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DesignTeaser
|
|
@ -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>
|
||||||
|
|
11
packages/freesewing.org/components/mdx/index.js
Normal file
11
packages/freesewing.org/components/mdx/index.js
Normal 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
|
138
packages/freesewing.org/components/mdx/pattern-docs.js
Normal file
138
packages/freesewing.org/components/mdx/pattern-docs.js
Normal 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)} » <FormattedMessage id="app.cutting" />
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link to={'/docs/patterns/' + props.pattern + '/fabric/'}>
|
||||||
|
{capitalize(props.pattern)} » <FormattedMessage id="app.fabricOptions" />
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link to={'/docs/patterns/' + props.pattern + '/instructions/'}>
|
||||||
|
{capitalize(props.pattern)} » <FormattedMessage id="app.instructions" />
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link to={'/docs/patterns/' + props.pattern + '/needs/'}>
|
||||||
|
{capitalize(props.pattern)} » <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
|
|
@ -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
|
57
packages/freesewing.org/components/mdx/pattern-options.js
Normal file
57
packages/freesewing.org/components/mdx/pattern-options.js
Normal 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
|
|
@ -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,
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
import i18n from '../freesewing.shared/config/i18n.config.mjs'
|
|
||||||
|
|
||||||
export default i18n()
|
|
|
@ -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> </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)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
8
packages/freesewing.shared/components/icons/down.js
Normal file
8
packages/freesewing.shared/components/icons/down.js
Normal 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
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue