diff --git a/package.json b/package.json
index 1a3b161..344dce6 100644
--- a/package.json
+++ b/package.json
@@ -4,11 +4,15 @@
"private": true,
"homepage": "http://profile_generator.volt-bonn.de/",
"dependencies": {
+ "@fluent/bundle": "^0.16.0",
+ "@fluent/langneg": "^0.5.0",
+ "@fluent/react": "^0.13.0",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"hammerjs": "^2.0.8",
"hamsterjs": "^1.1.3",
+ "intl-pluralrules": "^1.2.2",
"merge-images": "^2.0.0",
"react": "^17.0.1",
"react-dom": "^17.0.1",
diff --git a/src/App.js b/src/App.js
index b3233ce..0689055 100644
--- a/src/App.js
+++ b/src/App.js
@@ -9,6 +9,12 @@ import HeaderImage from './HeaderImage.svg'
import purpleBG from './purpleBG.png'
import empty_1x1 from './empty_1x1.png'
+import 'intl-pluralrules'
+import { AppLocalizationProvider } from './l10n.js'
+import { Localized } from './Localized.js'
+
+// const userLocales = ['de'] || navigator.languages
+const userLocales = navigator.languages
const frameSize = 1080
@@ -279,6 +285,7 @@ function App() {
return (
+
@@ -286,7 +293,7 @@ function App() {
Drop your photo here ...
- Choose your Photo:
+
It should best be a square image or your face in the middle. The photo is not saved and never leaves your computer.
)
}
diff --git a/src/Localized.js b/src/Localized.js
new file mode 100644
index 0000000..74e9519
--- /dev/null
+++ b/src/Localized.js
@@ -0,0 +1,55 @@
+import React from 'react'
+import {
+ Localized as LocalizedOriginal,
+ // withLocalization,
+} from '@fluent/react'
+
+import { FluentContext } from '../node_modules/@fluent/react/esm/context.js'
+
+const Localized = props => (
+ ,
+ ...props.elems,
+ }}
+ >
+ {props.children}
+
+)
+
+// A custom withLocalization to have an empty fallback.
+// It is nearly identical to the original.
+function withLocalization(Inner) {
+ function WithLocalization(props) {
+ const l10n = React.useContext(FluentContext)
+
+ const getString = (id, args, fallback) => l10n.getString(id, args, fallback || ' ')
+
+ return React.createElement(Inner, { getString, ...props })
+ }
+ return WithLocalization
+}
+
+export {
+ withLocalization,
+ Localized,
+ Localized as default,
+}
+
+/*
+
+import { Localized, withLocalization } from '../Localized/'
+
+
+export default withLocalization(componentName)
+
+
+import Localized from '../Localized/'
+
+
+import { withLocalization } from '@fluent/react'
+export default withLocalization(componentName)
+
+*/
diff --git a/src/l10n.js b/src/l10n.js
new file mode 100644
index 0000000..bf09342
--- /dev/null
+++ b/src/l10n.js
@@ -0,0 +1,83 @@
+import React from 'react'
+
+// https://projectfluent.org/play/
+
+// import {LocalizationProvider,Localized} from '@fluent/react' // '@fluent/react/compat'
+import { ReactLocalization, LocalizationProvider } from '@fluent/react'
+import { FluentBundle, FluentResource } from '@fluent/bundle'
+import { negotiateLanguages } from '@fluent/langneg'
+
+const _supportedLocales_ = [
+ 'de',
+ 'en',
+]
+const _defaultLocale_ = 'en'
+
+
+async function fetchMessages(locale) {
+ const path = await import('./locales/' + locale + '.ftl')
+
+ const response = await fetch(path.default)
+ const messages = await response.text()
+
+ return { [locale]: new FluentResource(messages) }
+}
+
+function getDefaultBundles() {
+ const bundle = new FluentBundle('')
+ bundle.addResource(new FluentResource(''))
+ return new ReactLocalization([bundle])
+}
+
+async function createMessagesGenerator(currentLocales) {
+ const fetched = await Promise.all(
+ currentLocales.map(fetchMessages)
+ )
+ const messages = fetched.reduce(
+ (obj, cur) => Object.assign(obj, cur)
+ )
+
+ return function* generateBundles() {
+ for (const locale of currentLocales) {
+ const bundle = new FluentBundle(locale)
+ bundle.addResource(messages[locale])
+ yield bundle
+ }
+ }
+}
+
+export class AppLocalizationProvider extends React.Component {
+ constructor(props) {
+ super(props)
+ this.state = {
+ bundles: getDefaultBundles(),
+ }
+ }
+
+ async componentDidMount() {
+ const currentLocales = negotiateLanguages(
+ this.props.userLocales,
+ _supportedLocales_,
+ { defaultLocale: _defaultLocale_ }
+ )
+
+ const generateBundles = await createMessagesGenerator(currentLocales)
+ this.setState({ bundles: new ReactLocalization(generateBundles()) })
+ }
+
+ render() {
+ const { children } = this.props
+ const { bundles } = this.state
+
+ if (!bundles) {
+ // Show a loader.
+ return
Loading texts…
+ }
+
+ return (
+
+ {children}
+
+ )
+ }
+}
diff --git a/src/locales/de.ftl b/src/locales/de.ftl
new file mode 100644
index 0000000..9f6779a
--- /dev/null
+++ b/src/locales/de.ftl
@@ -0,0 +1 @@
+choose_your_photo = Wähl dein Bild:
diff --git a/src/locales/en.ftl b/src/locales/en.ftl
new file mode 100644
index 0000000..50aa551
--- /dev/null
+++ b/src/locales/en.ftl
@@ -0,0 +1 @@
+choose_your_photo = Choose your Photo:
diff --git a/yarn.lock b/yarn.lock
index b347c0a..72c99f8 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1164,6 +1164,30 @@
minimatch "^3.0.4"
strip-json-comments "^3.1.1"
+"@fluent/bundle@^0.16.0":
+ version "0.16.0"
+ resolved "https://registry.yarnpkg.com/@fluent/bundle/-/bundle-0.16.0.tgz#e0cab75ba6ce9d9147ace3cb6ec61fd90f31ec1f"
+ integrity sha512-kUEAUePhb/y2BCcNpKOnjCs+WJkDczVpUUAQ+cDl0xvBGqL0Kv0Yog2oHSuv/Ou22c6KdXbvfCl3We0bIZnrmg==
+
+"@fluent/langneg@^0.5.0":
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/@fluent/langneg/-/langneg-0.5.0.tgz#de448070efa16c8fb6cc4af1629663f01e10689b"
+ integrity sha512-jv0g3YO5byz29HXEE6DBzAog60q726mwV2nIoekEX590JVh+mbd6/ZXT5/l4mN2BMlrelzyscCTffKI4XScVtg==
+
+"@fluent/react@^0.13.0":
+ version "0.13.0"
+ resolved "https://registry.yarnpkg.com/@fluent/react/-/react-0.13.0.tgz#cf2652d56fe22072dfeb30dcd2e03650fb913f88"
+ integrity sha512-NdITFI7eqecpb2Ty+I9g2BKxbzhdBKoKm8eU3/vnmBhGDeFgkS/aACSHF3gV2rL87JW9A+h4Ih1ympWp8OqqxQ==
+ dependencies:
+ "@fluent/sequence" "0.5.0"
+ cached-iterable "^0.2.1"
+ prop-types "^15.6.0"
+
+"@fluent/sequence@0.5.0":
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/@fluent/sequence/-/sequence-0.5.0.tgz#6349b614711df2d8ed256598fa31a757fe032539"
+ integrity sha512-70LhnbPO/sO2rI2vHws66QwKq9a7PKiEN0hrc65xY3LzWq3AlATZnt+EmFG1ihmPI+5mqGPnJMAdpp3qze3X2Q==
+
"@hapi/address@2.x.x":
version "2.1.4"
resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5"
@@ -2989,6 +3013,11 @@ cache-base@^1.0.1:
union-value "^1.0.0"
unset-value "^1.0.0"
+cached-iterable@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/cached-iterable/-/cached-iterable-0.2.1.tgz#723958f5e7adc74c96bedb10b426bdfd95f2fe6d"
+ integrity sha512-8zAVjMjdn/S/QXJaOnqsko0+ZJzXT2Dum2u9TMGg5YR9fxONPrUjuO9VYqnb1AoldXeYVAcNJLgT5Q8WaIJSgA==
+
call-bind@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.0.tgz#24127054bb3f9bdcb4b1fb82418186072f77b8ce"
@@ -5756,6 +5785,11 @@ internal-slot@^1.0.2:
has "^1.0.3"
side-channel "^1.0.2"
+intl-pluralrules@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/intl-pluralrules/-/intl-pluralrules-1.2.2.tgz#2b73542a9502a8a3a742cdd917f3d969fb5482fe"
+ integrity sha512-SBdlNCJAhTA0I0uHg2dn7I+c6BCvSVk6zJ/01ozjwJK7BvKms9RH3w3Sd/Ag24KffZ/Yx6KJRCKAc7eE8TZLNg==
+
ip-regex@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
@@ -8754,7 +8788,7 @@ prompts@2.4.0, prompts@^2.0.1:
kleur "^3.0.3"
sisteransi "^1.0.5"
-prop-types@^15.7.2:
+prop-types@^15.6.0, prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==