1
0
Fork 0
mirror of https://github.com/voltbonn/profile-picture-generator.git synced 2024-12-21 23:35:05 +00:00

started localization

This commit is contained in:
thomasrosen 2021-01-23 19:39:01 +01:00
parent a5f000b96f
commit d31f09cc30
7 changed files with 188 additions and 2 deletions

View file

@ -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",

View file

@ -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 (
<AppLocalizationProvider key="AppLocalizationProvider" userLocales={userLocales}>
<div className="App" {...getRootProps()}>
<img src={HeaderImage} className="HeaderImage" alt="Volt Logo" />
@ -286,7 +293,7 @@ function App() {
Drop your photo here ...
</div>
<h2>Choose your Photo:</h2>
<h2><Localized id="choose_your_photo" /></h2>
<p>It should best be a square image or your face in the middle. The photo is not saved and never leaves your computer.</p>
<label className="labelButton" tabIndex="0" style={{outline:'none'}}>
@ -326,6 +333,7 @@ function App() {
<a href="https://github.com/voltbonn/profile-picture-generator">Source Code</a>
</footer>
</div>
</AppLocalizationProvider>
)
}

55
src/Localized.js Normal file
View file

@ -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 => (
<LocalizedOriginal
key={props.id}
{...props}
elems={{
br: <br />,
...props.elems,
}}
>
<React.Fragment>{props.children}</React.Fragment>
</LocalizedOriginal>
)
// 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/'
<Localized id="translation_id" />
export default withLocalization(componentName)
import Localized from '../Localized/'
<Localized id="translation_id" />
import { withLocalization } from '@fluent/react'
export default withLocalization(componentName)
*/

83
src/l10n.js Normal file
View file

@ -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 <div>Loading texts</div>
}
return (
<LocalizationProvider l10n={bundles}>
{children}
</LocalizationProvider>
)
}
}

1
src/locales/de.ftl Normal file
View file

@ -0,0 +1 @@
choose_your_photo = Wähl dein Bild:

1
src/locales/en.ftl Normal file
View file

@ -0,0 +1 @@
choose_your_photo = Choose your Photo:

View file

@ -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==