mirror of
https://github.com/strapi/strapi.git
synced 2025-11-02 19:04:38 +00:00
Translate admin panel
This commit is contained in:
parent
560e965747
commit
a570d3f174
@ -5,11 +5,12 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import messages from './messages';
|
||||
import styles from './styles.scss';
|
||||
import LocaleToggle from 'containers/LocaleToggle';
|
||||
import messages from './messages.json';
|
||||
import { define } from '../../i18n';
|
||||
define(messages);
|
||||
|
||||
class LeftMenuFooter extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
||||
render() {
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
/*
|
||||
* LeftMenuFooter Messages
|
||||
*
|
||||
* This contains all the text for the LeftMenuFooter component.
|
||||
*/
|
||||
import { defineMessages } from 'react-intl';
|
||||
|
||||
export default defineMessages({
|
||||
poweredBy: {
|
||||
id: 'app.components.LeftMenuFooter.poweredBy',
|
||||
defaultMessage: 'Proudly powered by ',
|
||||
},
|
||||
});
|
||||
6
public/app/components/LeftMenuFooter/messages.json
Normal file
6
public/app/components/LeftMenuFooter/messages.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"poweredBy": {
|
||||
"id": "app.components.LeftMenuFooter.poweredBy",
|
||||
"defaultMessage": "Proudly powered by "
|
||||
}
|
||||
}
|
||||
@ -9,13 +9,16 @@ import { injectIntl, intlShape } from 'react-intl';
|
||||
|
||||
const ToggleOption = ({ value, message, intl }) => (
|
||||
<option value={value}>
|
||||
{intl.formatMessage(message).toUpperCase()}
|
||||
{typeof message === 'string' ? message : intl.formatMessage(message).toUpperCase()}
|
||||
</option>
|
||||
);
|
||||
|
||||
ToggleOption.propTypes = {
|
||||
value: React.PropTypes.string.isRequired,
|
||||
message: React.PropTypes.object.isRequired,
|
||||
message: React.PropTypes.oneOfType([
|
||||
React.PropTypes.object,
|
||||
React.PropTypes.string,
|
||||
]),
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
/*
|
||||
* HomePage Messages
|
||||
*
|
||||
* This contains all the text for the HomePage component.
|
||||
*/
|
||||
import { defineMessages } from 'react-intl';
|
||||
|
||||
export default defineMessages({
|
||||
header: {
|
||||
id: 'app.components.HomePage.header',
|
||||
defaultMessage: 'This is HomePage components !',
|
||||
},
|
||||
});
|
||||
@ -11,11 +11,13 @@ import { changeLocale } from '../LanguageProvider/actions';
|
||||
import { appLocales } from '../../i18n';
|
||||
import { createSelector } from 'reselect';
|
||||
import styles from './styles.scss';
|
||||
import messages from './messages';
|
||||
import Toggle from 'components/Toggle';
|
||||
|
||||
export class LocaleToggle extends React.Component { // eslint-disable-line
|
||||
render() {
|
||||
const messages = {};
|
||||
appLocales.forEach(locale => { messages[locale] = locale.toUpperCase(); });
|
||||
|
||||
return (
|
||||
<div className={styles.localeToggle}>
|
||||
<Toggle values={appLocales} messages={messages} onToggle={this.props.onLocaleToggle} />
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
/*
|
||||
* LocaleToggle Messages
|
||||
*
|
||||
* This contains all the text for the LanguageToggle component.
|
||||
*/
|
||||
import { defineMessages } from 'react-intl';
|
||||
import { appLocales } from '../../i18n';
|
||||
|
||||
export function getLocaleMessages(locales) {
|
||||
return locales.reduce((messages, locale) =>
|
||||
Object.assign(messages, {
|
||||
[locale]: {
|
||||
id: `app.components.LocaleToggle.${locale}`,
|
||||
defaultMessage: `${locale}`,
|
||||
},
|
||||
}), {});
|
||||
}
|
||||
|
||||
export default defineMessages(
|
||||
getLocaleMessages(appLocales)
|
||||
);
|
||||
@ -1,21 +0,0 @@
|
||||
import assert from 'assert';
|
||||
import { getLocaleMessages } from '../messages';
|
||||
|
||||
describe('getLocaleMessages', () => {
|
||||
it('should create i18n messages for all locales', () => {
|
||||
const expected = {
|
||||
en: {
|
||||
id: 'app.components.LocaleToggle.en',
|
||||
defaultMessage: 'en',
|
||||
},
|
||||
fr: {
|
||||
id: 'app.components.LocaleToggle.fr',
|
||||
defaultMessage: 'fr',
|
||||
},
|
||||
};
|
||||
|
||||
const actual = getLocaleMessages(['en', 'fr']);
|
||||
|
||||
assert.deepEqual(expected, actual);
|
||||
});
|
||||
});
|
||||
@ -11,9 +11,11 @@
|
||||
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import messages from './messages';
|
||||
import styles from './styles.scss';
|
||||
import { Link } from 'react-router';
|
||||
import messages from './messages.json';
|
||||
import { define } from '../../i18n';
|
||||
define(messages);
|
||||
|
||||
export default class NotFound extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
||||
|
||||
@ -21,7 +23,7 @@ export default class NotFound extends React.Component { // eslint-disable-line r
|
||||
return (
|
||||
<div className={styles.notFound}>
|
||||
<h1 className={styles.notFoundTitle}>
|
||||
<FormattedMessage {...messages.header} />
|
||||
404
|
||||
</h1>
|
||||
<h2 className={styles.notFoundDescription}>
|
||||
<FormattedMessage {...messages.description} />
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
/*
|
||||
* NotFoundPage Messages
|
||||
*
|
||||
* This contains all the text for the NotFoundPage component.
|
||||
*/
|
||||
import { defineMessages } from 'react-intl';
|
||||
|
||||
export default defineMessages({
|
||||
header: {
|
||||
id: 'app.components.NotFoundPage.header',
|
||||
defaultMessage: '404',
|
||||
},
|
||||
description: {
|
||||
id: 'app.components.NotFoundPage.description',
|
||||
defaultMessage: 'Page not found.',
|
||||
},
|
||||
});
|
||||
6
public/app/containers/NotFoundPage/messages.json
Normal file
6
public/app/containers/NotFoundPage/messages.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"description": {
|
||||
"id": "app.components.NotFoundPage.description",
|
||||
"defaultMessage": "Page not found."
|
||||
}
|
||||
}
|
||||
@ -1,39 +1,38 @@
|
||||
/**
|
||||
* i18n.js
|
||||
*
|
||||
* This will setup the i18n language files and locale data for your app.
|
||||
* This will setup the i18n language files and locale data for your plugin.
|
||||
*
|
||||
*/
|
||||
import { addLocaleData } from 'react-intl';
|
||||
|
||||
import enLocaleData from 'react-intl/locale-data/en';
|
||||
import frLocaleData from 'react-intl/locale-data/fr';
|
||||
|
||||
export const appLocales = [
|
||||
'en',
|
||||
'fr',
|
||||
];
|
||||
import { addLocaleData, defineMessages } from 'react-intl';
|
||||
|
||||
import enTranslationMessages from './translations/en.json';
|
||||
import frTranslationMessages from './translations/fr.json';
|
||||
|
||||
import enLocaleData from 'react-intl/locale-data/en';
|
||||
import frLocaleData from 'react-intl/locale-data/fr';
|
||||
|
||||
|
||||
addLocaleData(enLocaleData);
|
||||
addLocaleData(frLocaleData);
|
||||
|
||||
const formatTranslationMessages = (messages) => {
|
||||
const formattedMessages = {};
|
||||
for (const message of messages) {
|
||||
formattedMessages[message.id] = message.message || message.defaultMessage;
|
||||
}
|
||||
|
||||
return formattedMessages;
|
||||
};
|
||||
const appLocales = [
|
||||
'en',
|
||||
'fr',
|
||||
];
|
||||
|
||||
const translationMessages = {
|
||||
en: formatTranslationMessages(enTranslationMessages),
|
||||
fr: formatTranslationMessages(frTranslationMessages),
|
||||
en: enTranslationMessages,
|
||||
fr: frTranslationMessages,
|
||||
};
|
||||
|
||||
const define = messages => {
|
||||
defineMessages(messages);
|
||||
};
|
||||
|
||||
export {
|
||||
appLocales,
|
||||
define,
|
||||
translationMessages,
|
||||
};
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
[
|
||||
{
|
||||
"id": "app.components.LeftMenuFooter.poweredBy",
|
||||
"defaultMessage": "Powered by "
|
||||
}
|
||||
]
|
||||
{
|
||||
"app.components.LeftMenuFooter.poweredBy": "",
|
||||
"app.components.NotFoundPage.description": ""
|
||||
}
|
||||
@ -1,6 +1,4 @@
|
||||
[
|
||||
{
|
||||
"id": "app.components.LeftMenuFooter.poweredBy",
|
||||
"defaultMessage": "Propulsé par"
|
||||
}
|
||||
]
|
||||
{
|
||||
"app.components.LeftMenuFooter.poweredBy": "Propulsé par",
|
||||
"app.components.NotFoundPage.description": "Page introuvable"
|
||||
}
|
||||
@ -9,7 +9,7 @@ https://github.com/yahoo/react-intl/wiki
|
||||
|
||||
## Usage
|
||||
|
||||
Below we see a `messages.js` file for the `Footer` component example. A `messages.js` file should be included in any simple or container component that wants to use internationalization. You can add this support when you scaffold your component using this boilerplates scaffolding `plop` system.
|
||||
Below we see a `messages.json` file for the `Footer` component example. A `messages.json` file should be included in any simple or container component that wants to use internationalization. You can add this support when you scaffold your component using this boilerplates scaffolding `plop` system.
|
||||
|
||||
All default English text for the component is contained here (e.g. `This project is licensed under the MIT license.`), and is tagged with an ID (e.g. `boilerplate.components.Footer.license.message`) in addition to it's object definition id (e.g. `licenseMessage`).
|
||||
|
||||
@ -39,7 +39,7 @@ export default defineMessages({
|
||||
});
|
||||
```
|
||||
|
||||
Below is the example `Footer` component. Here we see the component including the `messages.js` file, which contains all the default component text, organized with ids (and optionally descriptions). We are also importing the `FormattedMessage` component, which will display a given message from the `messages.js` file in the selected language.
|
||||
Below is the example `Footer` component. Here we see the component including the `messages.json` file, which contains all the default component text, organized with ids (and optionally descriptions). We are also importing the `FormattedMessage` component, which will display a given message from the `messages.json` file in the selected language.
|
||||
|
||||
You will also notice a more complex use of `FormattedMessage` for the author message where alternate or variable values (i.e. `author: <A href="https://twitter.com/mxstbr">Max Stoiber</A>,`) are being injected, in this case it's a react component.
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@ const cheerio = require('cheerio');
|
||||
const pkg = require(path.resolve(process.cwd(), 'package.json'));
|
||||
const dllPlugin = pkg.dllPlugin;
|
||||
const argv = require('minimist')(process.argv.slice(2));
|
||||
const _ = require('lodash');
|
||||
|
||||
// PostCSS plugins
|
||||
const cssnext = require('postcss-cssnext');
|
||||
@ -66,6 +67,9 @@ module.exports = require('./webpack.base.babel')({
|
||||
|
||||
// Emit a source map for easier debugging
|
||||
devtool: 'cheap-module-eval-source-map',
|
||||
|
||||
// Generate translations files
|
||||
generateTranslationFiles: generateTranslationFiles(),
|
||||
});
|
||||
|
||||
/**
|
||||
@ -157,3 +161,160 @@ function templateContent() {
|
||||
|
||||
return doc.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate translation files according
|
||||
* to `messages.json` included in the app.
|
||||
*/
|
||||
function generateTranslationFiles() {
|
||||
// App directory
|
||||
const appDirectory = path.resolve(process.cwd(), 'app');
|
||||
|
||||
// Find `message.json` files in the app
|
||||
findMessagesFiles(appDirectory, (err, messageFiles) => {
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Format messages found
|
||||
const messagesFormatted = formatMessages(messageFiles);
|
||||
|
||||
// Get the list of languages supported by this plugin
|
||||
const pluginLanguages = getPluginLanguages();
|
||||
|
||||
// Get current translations values
|
||||
const currentTranslationsValues = getCurrentTranslationsValues(pluginLanguages);
|
||||
|
||||
// Update translations values
|
||||
const updatedTranslationValues = getUpdatedTranslationValues(currentTranslationsValues, messagesFormatted);
|
||||
|
||||
// Write files according to updated translations values
|
||||
writeTranslationFiles(pluginLanguages, updatedTranslationValues);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Find `message.json` files in the app.
|
||||
*
|
||||
* @param cb {Function} Callback
|
||||
*/
|
||||
function findMessagesFiles(appDir, cb) {
|
||||
// Name of the messages files
|
||||
const messagesFileName = 'messages.json';
|
||||
|
||||
// App directory
|
||||
// const dir = path.resolve(process.cwd(), 'app');
|
||||
|
||||
// Results
|
||||
let results = {};
|
||||
|
||||
// Parallel search
|
||||
fs.readdir(appDir, (err, list) => {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
let pending = list.length;
|
||||
if (!pending) {
|
||||
return cb(null, results);
|
||||
}
|
||||
return list.forEach(fileName => {
|
||||
const filePath = path.resolve(appDir, fileName);
|
||||
fs.stat(filePath, (errStat, stat) => {
|
||||
if (stat && stat.isDirectory()) {
|
||||
findMessagesFiles(filePath, (errFind, res) => {
|
||||
// Merge with the previous results
|
||||
results = _.merge(results, res);
|
||||
if (!--pending) {
|
||||
cb(null, results);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (fileName === messagesFileName) {
|
||||
results[filePath] = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||
}
|
||||
if (!--pending) {
|
||||
cb(null, results);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of languages supported by the plugin.
|
||||
*
|
||||
* @returns {Array} List of languages support by the plugin
|
||||
*/
|
||||
function getPluginLanguages() {
|
||||
return fs.readdirSync(path.resolve(process.cwd(), 'app', 'translations')).map(fileName => (fileName.replace('.json', '')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of current translations values.
|
||||
*
|
||||
* @param languages {Array} List of languages support by the plugin
|
||||
* @returns {Object} Current translation values
|
||||
*/
|
||||
function getCurrentTranslationsValues(languages) {
|
||||
const currentTranslationsValues = {};
|
||||
|
||||
_.forEach(languages, language => {
|
||||
currentTranslationsValues[language] = JSON.parse(fs.readFileSync((path.resolve(process.cwd(), 'app', 'translations', `${language}.json`)), 'utf8'));
|
||||
});
|
||||
|
||||
return currentTranslationsValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format messages.
|
||||
*
|
||||
* @param messageFiles {Object}
|
||||
* @returns {Object} Messages formatted
|
||||
*/
|
||||
function formatMessages(messageFiles) {
|
||||
const messagesFormatted = {};
|
||||
|
||||
_.forEach(messageFiles, messageFile => {
|
||||
_.forEach(messageFile, message => {
|
||||
messagesFormatted[message.id] = message.defaultMessage;
|
||||
});
|
||||
});
|
||||
|
||||
return messagesFormatted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge current translations with new ones.
|
||||
*
|
||||
* @param currentTranslationsValues {Object} Current translations value
|
||||
* @param messagesFormatted {Object} Messages formatted from `messages.json` files
|
||||
* @returns {Object} Translations values updated
|
||||
*/
|
||||
function getUpdatedTranslationValues(currentTranslationsValues, messagesFormatted) {
|
||||
const updatedTranslationValues = {};
|
||||
|
||||
_.forEach(currentTranslationsValues, (value, language) => {
|
||||
updatedTranslationValues[language] = {};
|
||||
|
||||
// Sort the messages and assigns the values
|
||||
Object.keys(messagesFormatted).sort().forEach(id => {
|
||||
updatedTranslationValues[language][id] = currentTranslationsValues[language][id] || '';
|
||||
});
|
||||
});
|
||||
|
||||
return updatedTranslationValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrite translations files according to updated values
|
||||
*
|
||||
* @param languages {Array} The list of languages supported by the plugin
|
||||
* @param updatedTranslationValues {Object} Translations values updated
|
||||
*/
|
||||
function writeTranslationFiles(languages, updatedTranslationValues) {
|
||||
_.forEach(languages, (language) => {
|
||||
fs.writeFileSync(path.resolve(path.resolve(process.cwd(), 'app', 'translations', `${language}.json`)), JSON.stringify(updatedTranslationValues[language], null, 2));
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user