Generic template with i18next

This commit is contained in:
vjrj 2017-11-13 21:24:35 +01:00
parent 5b311446dc
commit 403a108dba
15 changed files with 242 additions and 77 deletions

View file

@ -4,13 +4,13 @@ import getOAuthProfile from '../../../modules/get-oauth-profile';
export default (options, user) => {
const OAuthProfile = getOAuthProfile(options, user);
const applicationName = 'Application Name';
const applicationName = '¡Tod@s contra el Fuego!';
const firstName = OAuthProfile ? OAuthProfile.name.first : options.profile.name.first;
const emailAddress = OAuthProfile ? OAuthProfile.email : options.email;
return sendEmail({
to: emailAddress,
from: `${applicationName} <support@application.com>`,
from: `${applicationName} <noreply@comunes.org>`,
subject: `[${applicationName}] Welcome, ${firstName}!`,
template: 'welcome',
templateVars: {

View file

@ -0,0 +1,85 @@
import i18n from 'i18next';
import backend from 'i18next-xhr-backend';
import LngDetector from 'i18next-browser-languagedetector';
import Cache from 'i18next-localstorage-cache';
// Adapted from: https://github.com/appigram/ryfma-boilerplate/blob/44c1eabfb9928b5623afab36a23997969e5beb02/imports/startup/client/i18n.js
const detectorOptions = {
// order and from where user language should be detected
order: ['querystring', 'cookie', 'localStorage', 'navigator', 'htmlTag'],
// keys or params to lookup language from
lookupQuerystring: 'lng',
lookupCookie: 'i18next',
lookupLocalStorage: 'i18nextLng',
// cache user language on
caches: ['localStorage', 'cookie'],
excludeCacheFor: ['cimode'], // languages to not persist (cookie, localStorage)
};
const cacheOptions = {
// turn on or off
enabled: false,
// prefix for stored languages
prefix: 'i18next_res_',
// expiration
expirationTime: 7 * 24 * 60 * 60 * 1000,
// language versions
versions: {},
};
var backOpts = {
// path where resources get loaded from
loadPath: '/locales/{{lng}}/{{ns}}.json',
// path to post missing resources
addPath: '/locales/{{lng}}/{{ns}}.missing.json',
// jsonIndent to use when storing json files
jsonIndent: 2
};
i18n.use(backend)
.use(LngDetector)
.use(Cache)
.init({
backend: backOpts,
lng: 'es',
//fallbackLng: 'es',
fallbackLng: {
'en-US': ['en'],
'en-GB': ['en'],
'pt-BR': ['pt'],
'default': ['es']
},
interpolation: {
escapeValue: false, // not needed for react!!
},
whitelist: false,
// whitelist: ['es', 'en'], // allowed languages
load: 'all', // es-ES -> es, en-US -> en
debug: true,
ns: 'common',
defaultNS: 'common',
saveMissing: true, // if true seems it's fails to getResourceBundle
saveMissingTo: 'es',
keySeparator: 'ß',
nsSeparator: 'ð',
pluralSeparator: 'đ',
cache: cacheOptions,
detection: detectorOptions,
react: {
wait: true,
// https://react.i18next.com/components/i18next-instance.ht
/* bindI18n: 'languageChanged loaded',
bindStore: 'added removed',
nsMode: 'default' */
}
}, function(err, t) {
// initialized and ready to
console.log(t('AppName'));
});
export default i18n;

View file

@ -4,8 +4,8 @@ import getPrivateFile from '../../../modules/server/get-private-file';
import templateToHTML from '../../../modules/server/handlebars-email-to-html';
import templateToText from '../../../modules/server/handlebars-email-to-text';
const name = 'Application Name';
const email = '<support@application.com>';
const name = '¡Tod@s contra el Fuego!';
const email = '<noreply@comunes.org>';
const from = `${name} ${email}`;
const emailTemplates = Accounts.emailTemplates;

View file

@ -2,6 +2,7 @@ import React from 'react';
import { year } from '@cleverbeagle/dates';
import { Link } from 'react-router-dom';
import { Grid } from 'react-bootstrap';
import { translate } from 'react-i18next';
import './Footer.scss';
@ -10,18 +11,21 @@ const copyrightYear = () => {
return currentYear === '2017' ? '2017' : `2017-${currentYear}`;
};
const Footer = () => (
const Footer = (props) => {
const { t } = props;
return (
<div className="Footer">
<Grid>
<p className="pull-left">&copy; {copyrightYear()} Application Name</p>
<p className="pull-left">&copy; Copyleft {copyrightYear()} {t('OrgNameFull')}</p>
<ul className="pull-right">
<li><Link to="/terms">Terms<span className="hidden-xs"> of Service</span></Link></li>
<li><Link to="/privacy">Privacy<span className="hidden-xs"> Policy</span></Link></li>
<li><Link to="/terms"> {t('Términos')}<span className="hidden-xs"> {t('de Servicio')}</span></Link></li>
<li><Link to="/privacidad">{t('Política')}<span className="hidden-xs"> {t('de Privacidad')}</span></Link></li>
</ul>
</Grid>
</div>
);
);
};
Footer.propTypes = {};
export default Footer;
export default translate([], { wait: true })(Footer);

View file

@ -4,6 +4,7 @@ import { Navbar } from 'react-bootstrap';
import { Link } from 'react-router-dom';
import PublicNavigation from '../PublicNavigation/PublicNavigation';
import AuthenticatedNavigation from '../AuthenticatedNavigation/AuthenticatedNavigation';
import { translate } from 'react-i18next';
import './Navigation.scss';
@ -11,7 +12,7 @@ const Navigation = props => (
<Navbar>
<Navbar.Header>
<Navbar.Brand>
<Link to="/">Pup</Link>
<Link to="/">{props.t('AppName')}</Link>
</Navbar.Brand>
<Navbar.Toggle />
</Navbar.Header>
@ -29,4 +30,6 @@ Navigation.propTypes = {
authenticated: PropTypes.bool.isRequired,
};
export default Navigation;
// export default Navigation;
export default translate([], { wait: true })(Navigation);

View file

@ -28,6 +28,11 @@ import Footer from '../../components/Footer/Footer';
import Terms from '../../pages/Terms/Terms';
import Privacy from '../../pages/Privacy/Privacy';
import ExamplePage from '../../pages/ExamplePage/ExamplePage';
// i18n
import { I18nextProvider } from 'react-i18next';
import i18n from '/imports/startup/client/i18n';
//https://react.i18next.com/components/i18nextprovider.html
import './App.scss';
@ -41,7 +46,10 @@ const handleResendVerificationEmail = (emailAddress) => {
});
};
const App = props => (
<I18nextProvider i18n={i18n}>
<Router>
{!props.loading ? <div className="App">
{props.userId && !props.emailVerified ? <Alert className="verify-email text-center"><p>Hey friend! Can you <strong>verify your email address</strong> ({props.emailAddress}) for us? <Button bsStyle="link" onClick={() => handleResendVerificationEmail(props.emailAddress)} href="#">Re-send verification email</Button></p></Alert> : ''}
@ -61,7 +69,7 @@ const App = props => (
<Route name="recover-password" path="/recover-password" component={RecoverPassword} />
<Route name="reset-password" path="/reset-password/:token" component={ResetPassword} />
<Route name="terms" path="/terms" component={Terms} />
<Route name="privacy" path="/privacy" component={Privacy} />
<Route name="privacy" path="/privacidad" component={Privacy} />
<Route name="examplePage" path="/example-page" component={ExamplePage} />
<Route component={NotFound} />
</Switch>
@ -69,6 +77,7 @@ const App = props => (
<Footer />
</div> : ''}
</Router>
</I18nextProvider>
);
App.defaultProps = {

View file

@ -21,3 +21,11 @@
padding: 0;
}
}
body {
background: url(/home-fire.jpg) no-repeat center center fixed;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
}

View file

@ -1,15 +1,14 @@
import React from 'react';
import { Button } from 'react-bootstrap';
import { translate } from 'react-i18next';
import './Index.scss';
const Index = () => (
const Index = (props) => {
const { t } = props;
return (
<div className="Index">
<img
src="https://s3-us-west-2.amazonaws.com/cleverbeagle-assets/graphics/email-icon.png"
alt="Clever Beagle"
/>
<h1>Pup</h1>
<h1>{props.t('AppName')}</h1>
<p>A boilerplate for products.</p>
<div>
<Button href="http://cleverbeagle.com/pup">Read the Docs</Button>
@ -19,6 +18,7 @@ const Index = () => (
<p>Need help and want to stay accountable building your product? <a href="http://cleverbeagle.com?utm_source=pupappindex&utm_campaign=oss">Check out Clever Beagle</a>.</p>
</footer>
</div>
);
);
};
export default Index;
export default translate([], { wait: true })(Index);

View file

@ -3,10 +3,12 @@
.Index {
padding: 20px;
background: $cb-blue;
background: lighten($todos-back1, 75%);
text-align: center;
border-radius: 3px;
color: #fff;
color: $todos-font1;
opacity: 0.5;
filter: alpha(opacity=50);
img {
width: 100px;
@ -14,7 +16,9 @@
}
h1 {
font-size: 28px;
font-size: 86px;
text-transform: uppercase;
font-family: 'Roboto';
}
p {

View file

@ -1,3 +1,4 @@
@import './colors';
@import './forms';
@import './bootstrap-overrides';
@import 'web-fonts';

View file

@ -18,3 +18,8 @@ $cb-blue: #4285F4;
$cb-green: #00D490;
$cb-yellow: #FFCF50;
$cb-red: #DA5847;
$todos-back1: #AC9393;
$todos-back2: #FFFFFF;
$todos-font1: #280B0B;
$todos-font2: #501616;

View file