Skip to content
This repository was archived by the owner on Feb 21, 2023. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
lts/*
4 changes: 1 addition & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,7 @@
"gatsby-source-filesystem": "^2.1.33",
"gatsby-transformer-sharp": "^2.3.0",
"glob": "^7.1.6",
"i18next": "^19.0.2",
"i18next-xhr-backend": "^3.2.2",
"netlify-cms-app": "^2.9.7",
"netlify-cms-app": "^2.10.1",
"node-sass": "^4.13.0",
"prop-types": "^15.6.1",
"react": "^16.11.0",
Expand Down
4 changes: 4 additions & 0 deletions src/cms/cms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { ProjectPreview } from './preview-templates/ProjectPreview'
import { LocalizedStringWidget } from './widgets/LocalizedString'
import { UuidWidget } from './widgets/Uuid'
import { LOCALES } from './i18n'
import { LocalizedMarkdownWidget } from './widgets/LocalizedMarkdown'
import { LocalizedObjectWidget } from './widgets/LocalizedObject'

const config = {
backend: {
Expand All @@ -18,7 +20,9 @@ const config = {
},
}

CMS.registerWidget(LocalizedMarkdownWidget(LOCALES))
CMS.registerWidget(LocalizedStringWidget(LOCALES))
CMS.registerWidget(LocalizedObjectWidget(LOCALES))
CMS.registerWidget(UuidWidget())

CMS.registerPreviewTemplate('imprint', ImprintPreview)
Expand Down
73 changes: 73 additions & 0 deletions src/cms/localizedLeaves.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Union } from 'ts-toolbelt'
import { TranslationCollection } from './i18n-lib'

// In this file we explore the possibilty to localize a
// tree with primitive values as leaves by localiing each leaf.

type Leaf = string | number | boolean

interface Translation<T> {
locale: string
value: T
}

type LocalizedLeaf = Translation<Leaf>[]

type Tree = {
[key: string]: Leaf | Tree
}

interface LocalizedTree {
[key: string]: LocalizedLeaf | LocalizedTree
}

// Recursive types are supported only since typescript 3.7
// https://github.com/microsoft/TypeScript/pull/33050

// Fix 'Index signature is missing in type' when using 'extends Tree'
type AsLocalized<O extends object> = {
[K in keyof O]: O[K] extends object
? AsLocalized<O[K]>
: Union.Replace<O[K], Leaf, TranslationCollection<O[K]>>
}

type AsTranslated<O extends object> = {
[K in keyof O]: O[K] extends TranslationCollection<infer T>
? Union.Replace<O[K], TranslationCollection<T>, T>
: O[K] extends object
? AsTranslated<O[K]>
: never
}

// tests
interface Hero {
title: string
meta: {
description: string
keys: string
}
}

interface LocalizedHero extends AsLocalized<Hero> {
title: TranslationCollection<string>
meta: {
description: TranslationCollection<string>
keys: TranslationCollection<string>
}
}

export const hero: AsTranslated<LocalizedHero> = {
title: 'myTitle',
meta: {
description: 'hero section',
keys: 'webpage',
},
}

export const localizedHero: AsLocalized<Hero> = {
title: [{ locale: 'en', value: 'myTitle' }],
meta: {
description: [{ locale: 'en', value: 'hero section' }],
keys: [{ locale: 'en', value: 'webpage' }],
},
}
82 changes: 82 additions & 0 deletions src/cms/widgets/LocalizedMarkdown/control.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import CMS from 'netlify-cms-app'
import React from 'react'
import { isImmutable, Map, List, fromJS } from 'immutable'
import * as R from 'ramda'

import {
getTranslation,
uppendTranslation,
TranslationCollection,
} from '../../i18n-lib'
import { Locale } from '../../i18n-locales'
import { LocalePicker } from '../LocalizedWidget/LocalePicker'

const MarkdownControl = CMS.getWidget('markdown').control

// netlify CMS markdown widget properties
export interface MarkdownWidgetProps {
onChange: Function
onAddAsset: Function
getAsset: Function
classNameWrapper: string
editorControl: Function
value?: string // defaults to empty string
field: Map<string, any>
getEditorComponents?: Function
}

// actual properties with updated value field
export interface LocalizedMarkdownWidgetProps {
onChange: Function
onAddAsset?: Function // not used at all
getAsset?: Function // not used at all
classNameWrapper?: string
value?: List<Map<any, any>>
field: Map<string, any> // according to docs should support 'default' (it doesn't) and 'buttons' (working)
editorControl?: Function // JSX element used for plugins (needed when plugins enabled)
getEditorComponents: Function // returns list of plugins
// purely optional
setActiveStyle?: Function // not working
setInactiveStyle?: Function // not working
}

export const createLocalizedMarkdownControl = (locales: Locale[]) => {
const LocalizedMarkdownControl: React.FC<LocalizedMarkdownWidgetProps> = props => {
const [locale, setLocale] = React.useState<Locale>(
R.head(locales) as Locale,
)
const collection = extractAsJS(props.value)

const getValue = (locale: Locale) =>
fromJS(getTranslation<string>(locale)(collection))

const handleChange = (locale: Locale) => (newValue: any) => {
props.onChange(fromJS(uppendTranslation(locale, newValue)(collection)))
}

return (
<div>
<LocalePicker
locales={locales}
currentLocale={locale}
setLocale={setLocale}
/>
<MarkdownControl
{...props}
key={locale}
onChange={handleChange(locale)}
value={getValue(locale)}
/>
</div>
)
}
return LocalizedMarkdownControl
}

const toJS = (value: any) => value.toJS()

const extractAsJS: (value: any) => TranslationCollection<any> = R.cond([
[R.isNil, R.always([])],
[isImmutable, toJS],
[R.T, R.identity],
])
10 changes: 10 additions & 0 deletions src/cms/widgets/LocalizedMarkdown/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { createLocalizedMarkdownControl } from './control'
import { createLocalizedMarkdownPreview } from './preview'
import { Locale } from '../../i18n-locales'

export const LocalizedMarkdownWidget = (locales: Locale[], opts = {}) => ({
name: 'localizedMarkdown',
controlComponent: createLocalizedMarkdownControl(locales),
previewComponent: createLocalizedMarkdownPreview(locales),
...opts,
})
42 changes: 42 additions & 0 deletions src/cms/widgets/LocalizedMarkdown/preview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import CMS from 'netlify-cms-app'
import React, { Component } from 'react'
import { fromJS, isImmutable } from 'immutable'

import { WidgetProps } from '../mockNetlify'
import { getTranslation, uppendTranslation } from '../../i18n-lib'
import { Locale } from '../../i18n-locales'

const MarkdownPreview = CMS.getWidget('markdown').preview

export const createLocalizedMarkdownPreview = (locales: Locale[]) => {
return class LocalizedMarkdownPreview extends Component<WidgetProps> {
getWidgetState = () => {
const { value } = this.props
return !value ? [] : isImmutable(value) ? value.toJS() : value
}

getValue = (locale: Locale) =>
getTranslation<object>(locale)(this.getWidgetState())

handleChange = (locale: Locale) => (newValue: any) => {
this.props.onChange(
uppendTranslation(locale, newValue)(this.getWidgetState()),
)
}

updateProps = (locale: Locale) => ({
...this.props,
key: locale,
onChange: this.handleChange(locale),
value: fromJS(this.getValue(locale)),
})

render = () => (
<div>
{locales.map(locale => (
<MarkdownPreview {...this.updateProps(locale)} />
))}
</div>
)
}
}
40 changes: 40 additions & 0 deletions src/cms/widgets/LocalizedMarkdown/stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react'
import '../../../scss/main.scss'
import { storiesOf } from '@storybook/react'

import cn from 'classnames'
import styles from '../styles.module.scss'

import { createLocalizedMarkdownControl } from './control'
import { Map, List } from 'immutable'

interface WrapperProps {
Widget: any
}

const Wrapper: React.FC<WrapperProps> = ({ Widget }) => {
const [value, setValue] = React.useState(undefined)

return (
<div>
<div>In the widget I would see:</div>
<div>
<Widget
classNameWrapper={cn(styles.netlify)}
field={Map()}
value={value}
onChange={setValue}
//onAddAsset={() => {}}
//getAsset={() => {}}
//editorControl={() => <div></div>}
getEditorComponents={() => Map([])}
/>
</div>
<div>And in the file I would save:</div>
<div> {JSON.stringify(value, null, 2)}</div>
</div>
)
}
storiesOf('Widgets.LocalizedMarkdown', module).add('default', () => (
<Wrapper Widget={createLocalizedMarkdownControl(['de', 'en'])} />
))
45 changes: 45 additions & 0 deletions src/cms/widgets/LocalizedObject/control.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import CMS from 'netlify-cms-app'
import React, { Component } from 'react'
import { fromJS, isImmutable } from 'immutable'

import { WidgetProps } from '../mockNetlify'
import { getTranslation, uppendTranslation } from '../../i18n-lib'
import { Locale } from '../../i18n-locales'

const ObjectWidgetControl = CMS.getWidget('object').control

export const createLocalizedObjectControl = (locales: Locale[]) => {
return class LocalizedObjectControl extends Component<WidgetProps> {
getStateAsJS = () => {
const { value } = this.props
return !value ? [] : isImmutable(value) ? value.toJS() : value
}

getValue = (locale: Locale) =>
getTranslation<object>(locale)(this.getStateAsJS())

handleChange = (locale: Locale) => (fieldName: string, newValue: any) => {
this.props.onChange(
uppendTranslation(locale, {
...this.getValue(locale),
[fieldName]: newValue,
})(this.getStateAsJS()),
)
}

updateProps = (locale: Locale) => ({
...this.props,
key: locale,
onChangeObject: this.handleChange(locale),
value: fromJS(this.getValue(locale)),
})

render = () => (
<div>
{locales.map(locale => (
<ObjectWidgetControl {...this.updateProps(locale)} />
))}
</div>
)
}
}
10 changes: 10 additions & 0 deletions src/cms/widgets/LocalizedObject/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { createLocalizedObjectControl } from './control'
import { createLocalizedObjectPreview } from './preview'
import { Locale } from '../../i18n-locales'

export const LocalizedObjectWidget = (locales: Locale[], opts = {}) => ({
name: 'localizedObject',
controlComponent: createLocalizedObjectControl(locales),
previewComponent: createLocalizedObjectPreview(locales),
...opts,
})
45 changes: 45 additions & 0 deletions src/cms/widgets/LocalizedObject/preview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import CMS from 'netlify-cms-app'
import React, { Component } from 'react'
import { fromJS, isImmutable } from 'immutable'

import { WidgetProps } from '../mockNetlify'
import { getTranslation, uppendTranslation } from '../../i18n-lib'
import { Locale } from '../../i18n-locales'

const ObjectWidgetPreview = CMS.getWidget('object').preview

export const createLocalizedObjectPreview = (locales: Locale[]) => {
return class LocalizedObjectPreview extends Component<WidgetProps> {
getStateAsJS = () => {
const { value } = this.props
return !value ? [] : isImmutable(value) ? value.toJS() : value
}

getValue = (locale: Locale) =>
getTranslation<object>(locale)(this.getStateAsJS())

handleChange = (locale: Locale) => (fieldName: string, newValue: any) => {
this.props.onChange(
uppendTranslation(locale, {
...this.getValue(locale),
[fieldName]: newValue,
})(this.getStateAsJS()),
)
}

updateProps = (locale: Locale) => ({
...this.props,
key: locale,
onChangeObject: this.handleChange(locale),
value: fromJS(this.getValue(locale)),
})

render = () => (
<div>
{locales.map(locale => (
<ObjectWidgetPreview {...this.updateProps(locale)} />
))}
</div>
)
}
}
10 changes: 10 additions & 0 deletions src/cms/widgets/LocalizedObject/stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react'
import '../../../scss/main.scss'
import { storiesOf } from '@storybook/react'

import { createLocalizedObjectControl } from './control'
import { Wrapper } from '../mockNetlify'

storiesOf('Widgets.LocalizedObject', module).add('default', () => (
<Wrapper Widget={createLocalizedObjectControl(['de', 'en'])} />
))
Loading