Merge pull request #305 from mewtaylor/feature/266-componentize-locale-strings

GH-266: view-ify locale strings
This commit is contained in:
Matthew Taylor 2016-01-14 10:10:02 -05:00
commit 9e58efc7e2
30 changed files with 426 additions and 329 deletions

1
.gitignore vendored
View file

@ -10,6 +10,7 @@ npm-*
# Locales
/locales
/intl
# Elastic Beanstalk Files
.elasticbeanstalk/*

View file

@ -18,8 +18,9 @@ build:
clean:
rm -rf ./build
rm -rf ./intl
mkdir -p build
mkdir -p locales
mkdir -p intl
deploy:
@ -38,7 +39,7 @@ tag:
echo $(GIT_VERSION) > ./build/version.txt
translations:
./lib/bin/build-locales locales/translations.json
./bin/build-locales intl
webpack:
$(WEBPACK) --bail
@ -63,7 +64,6 @@ lint:
$(ESLINT) ./*.js
$(ESLINT) ./server/*.js
$(ESLINT) ./src/*.js
$(ESLINT) ./src/*.jsx
$(ESLINT) ./src/mixins/*.jsx
$(ESLINT) ./src/views/**/*.jsx
$(ESLINT) ./src/components/**/*.jsx

161
bin/build-locales Executable file
View file

@ -0,0 +1,161 @@
#!/usr/bin/env node
/*
Converts the existing .po translation files in the module to JSON files.
Requires po2json in order to work. Takes as input a directory
in which to store the resulting json translation files.
Takes in as an argument an output directory to put translation files.
Searches for files named `l10n.json` in the `src/views/` directory to get
template english strings (as well as the general template at `src/l10n.json`).
It compiles the template strings into a flat object that is compared against the
translations in the .po files from the `scratchr2_translations` dependency, using
an md5 of the template string without whitespace, and an md5 of the .po msgid string
without whitespace.
The output files are javascript files that declare objects by locale. Each locale
has a sub-object with FormattedMessage ids as keys, and translated strings as
values. If no translation was found for a string, the default english will be the
value.
Output Example:
'''
var message = {
en: {
'general.inAWorld': 'In a world, where bears are invisible...',
'general.question': 'Are there bears here?',
'general.answer': 'I dunno, but there could be...'
},
es: {
'general.inAWorld': 'En un mundo, donde hay osos invisibles',
'general.question': 'Are there bears here?',
'general.answer': 'No sé, pero es posible...'
}
}
'''
*/
var fs = require('fs');
var glob = require('glob');
var merge = require('lodash.merge');
var path = require('path');
var po2icu = require('po2icu');
var localeCompare = require('./lib/locale-compare');
// -----------------------------------------------------------------------------
// Main script
// -----------------------------------------------------------------------------
var args = process.argv.slice(2);
if (!args.length) {
process.stdout.write('A destination directory must be specified.');
process.exit(1);
}
var verbose = false;
if (args.length > 1) {
verbose = (args[1] === '-v') ? true : false;
}
var poUiDir = path.resolve(__dirname, '../node_modules/scratchr2_translations/ui');
var outputDir = path.resolve(__dirname, '../', args[0]);
try {
fs.accessSync(outputDir, fs.F_OK);
} catch (err) {
// Doesn't exist - create it.
fs.mkdirSync(outputDir);
}
// get global locale strings first.
var globalTemplateFile = path.resolve(__dirname, '../src/l10n.json');
// message key with english string values (i.e. default values)
var generalIds = JSON.parse(fs.readFileSync(globalTemplateFile, 'utf8'));
var viewLocales = {};
var generalLocales = {
en: generalIds
};
// FormattedMessage id with english string as value. Use for default values in translations
// Sample structure: { 'general-general.blah': 'blah', 'about-about.blah': 'blahblah' }
var idsWithICU = {};
// reverse (i.e. english string with message key as the value) object for searching po files.
// Sample structure: { 'blah': 'general-general.blah', 'blahblah': 'about-about.blah' }
var icuWithIds = {};
for (var id in generalIds) {
idsWithICU['general-' + id] = generalIds[id];
icuWithIds[generalIds[id]] = 'general-' + id;
}
// get view-specific locale strings.
var files = glob.sync(path.resolve(__dirname, '../src/views/**/l10n.json'));
files.forEach(function (file) {
var dirPath = file.split('/');
dirPath.pop();
var view = dirPath.pop();
var viewIds = JSON.parse(fs.readFileSync(file, 'utf8'));
viewLocales[view] = {
en: viewIds
};
for (var id in viewIds) {
idsWithICU[view + '-' + id] = viewIds[id];
icuWithIds[viewIds[id]] = view + '-' + id; // add viewName to identifier for later
}
});
// md5 of english strings with message key as the value for searching po files.
// Sample structure: { 'sdfas43534sdfasdf': 'general-general.blah', 'lkjfasdf4t342asdfa': 'about-about.blah' }
var md5WithIds = localeCompare.getMD5Map(icuWithIds);
// Get ui localization strings first
glob(poUiDir + '/*', function (err, files) {
if (err) throw new Error(err);
files.forEach(function (file) {
var lang = file.split('/').pop();
var jsFile = path.resolve(file, 'LC_MESSAGES/djangojs.po');
var pyFile = path.resolve(file, 'LC_MESSAGES/django.po');
var translations = {};
try {
var jsTranslations = po2icu.poFileToICUSync(lang, jsFile);
translations = localeCompare.mergeNewTranslations(translations, jsTranslations, idsWithICU, md5WithIds);
} catch (err) {
if (verbose) process.stdout.write(lang + ': ' + err + '\n');
}
try {
var pyTranslations = po2icu.poFileToICUSync(lang, pyFile);
translations = localeCompare.mergeNewTranslations(translations, pyTranslations, idsWithICU, md5WithIds);
} catch (err) {
if (verbose) process.stdout.write(lang + ': ' + err + '\n');
}
// add new translations to locale object
for (var id in translations) {
var ids = id.split('-'); // [viewName, stringId]
var viewName = ids[0];
var stringId = ids[1];
if (viewLocales.hasOwnProperty(viewName)) {
if (!viewLocales[viewName].hasOwnProperty(lang)) viewLocales[viewName][lang] = {};
viewLocales[viewName][lang][stringId] = translations[id];
} else {
// default to general
if (!generalLocales.hasOwnProperty(lang)) generalLocales[lang] = {};
generalLocales[lang][stringId] = translations[id];
}
}
});
for (var view in viewLocales) {
var viewTranslations = merge(viewLocales[view], generalLocales);
var objectString = JSON.stringify(viewTranslations);
var fileString = 'window._messages = ' + objectString + ';';
fs.writeFileSync(outputDir + '/' + view + '.intl.js', fileString);
}
});

133
en.json
View file

@ -1,133 +0,0 @@
{
"general.accountSettings": "Account settings",
"general.about": "About",
"general.aboutScratch": "About Scratch",
"general.donate": "Donate",
"general.collaborators": "Collaborators",
"general.community": "Community",
"general.contactUs": "Contact Us",
"general.copyright": "Scratch is a project of the Lifelong Kindergarten Group at the MIT Media Lab",
"general.create": "Create",
"general.credits": "Credits",
"general.discuss": "Discuss",
"general.dmca": "DMCA",
"general.explore": "Explore",
"general.faq": "FAQ",
"general.forParents": "For Parents",
"general.forEducators": "For Educators",
"general.guidelines": "Community Guidelines",
"general.help": "Help",
"general.jobs": "Jobs",
"general.joinScratch": "Join Scratch",
"general.legal": "Legal",
"general.learnMore": "Learn More",
"general.messages": "Messages",
"general.myClasses": "My Classes",
"general.myStuff": "My Stuff",
"general.offlineEditor": "Offline Editor",
"general.profile": "Profile",
"general.scratchConference": "Scratch Conference",
"general.scratchday": "Scratch Day",
"general.scratchEd": "ScratchEd",
"general.scratchFoundation": "Scratch Foundation",
"general.scratchJr": "ScratchJr",
"general.search": "Search",
"general.signIn": "Sign in",
"general.statistics": "Statistics",
"general.support": "Support",
"general.tipsWindow": "Tips Window",
"general.tipsAnimateYourNameTitle": "Animate Your Name",
"general.tipsBearstack": "Bearstack Story",
"general.tipsDanceTitle": "Dance, Dance, Dance",
"general.tipsGetStarted": "Getting Started",
"general.tipsHideAndSeekTitle": "Hide-and-Seek Game",
"general.tipsPongGame": "Create a Pong Game",
"general.termsOfUse": "Terms of Use",
"general.username": "Username",
"general.viewAll": "View All",
"general.whatsHappening": "What's Happening?",
"general.wiki": "Scratch Wiki",
"about.introOne": "With Scratch, you can program your own interactive stories, games, and animations — and share your creations with others in the online community.",
"about.introTwo": "Scratch helps young people learn to think creatively, reason systematically, and work collaboratively — essential skills for life in the 21st century.",
"about.introThree": "Scratch is a project of the Lifelong Kindergarten Group at the MIT Media Lab. It is provided free of charge.",
"about.introParents": "Info for parents",
"about.introEducators": "Info for educators",
"about.whoUsesScratch": "Who Uses Scratch?",
"about.whoUsesScratchDescription": "Scratch is designed especially for ages 8 to 16, but is used by people of all ages. Millions of people are creating Scratch projects in a wide variety of settings, including homes, schools, museums, libraries, and community centers.",
"about.aroundTheWorld": "Around the World",
"about.aroundTheWorldDescription": "Scratch is used in more than 150 different countries and available in more than 40 languages. To change languages, click the menu at the bottom of the page. Or, in the Project Editor, click the globe at the top of the page. To add or improve a translation, see the <a href=\"http://wiki.scratch.mit.edu/wiki/How_to_Translate_Scratch\">translation</a> page.",
"about.quotes": "Quotes",
"about.quotesDescription": "The Scratch Team has received many emails from youth, parents, and educators expressing thanks for Scratch. Want to see what people are saying? You can read a collection of the <a href=\"/info/quotes\">quotes</a> we&#39;ve received.",
"about.learnMore": "Learn More About Scratch",
"about.learnMoreHelp": "Scratch Help Page",
"about.learnMoreFaq": "Frequently Asked Questions",
"about.learnMoreParents": "Information for Parents",
"about.learnMoreCredits": "Scratch Credits",
"about.literacy": "Learn to Code, Code to Learn",
"about.literacyDescription": "The ability to code computer programs is an important part of literacy in todays society. When people learn to code in Scratch, they learn important strategies for solving problems, designing projects, and communicating ideas.",
"about.schools": "Scratch in Schools",
"about.schoolsDescription": "Students are learning with Scratch at all levels (from elementary school to college) and across disciplines (such as math, computer science, language arts, social studies). Educators share stories, exchange resources, ask questions, and find people on the <a href=\"http://scratched.gse.harvard.edu/\">ScratchEd website</a>.",
"about.research": "Research",
"about.researchDescription": "The MIT Scratch Team and collaborators are researching how people use and learn with Scratch (for an introduction, see <a href=\"http://web.media.mit.edu/~mres/papers/Scratch-CACM-final.pdf\">Scratch: Programming for All</a>). Find out more about Scratch <a href=\"/info/research\">research</a> and <a href=\"/statistics\">statistics</a> about Scratch.",
"about.support": "Support and Funding",
"about.supportDescription": "The Scratch project, initiated in 2003, has received generous support from the National Science Foundation (grants 0325828, 1002713, 1027848, 1019396), Intel Foundation, Microsoft, MacArthur Foundation, LEGO Foundation, Code-to-Learn Foundation, Google, Dell, Fastly, Inversoft, and MIT Media Lab research consortia. If you&#39;d like to support Scratch, please see our <a href=\"https://secure.donationpay.org/scratchfoundation/\">donate page</a>, or contact us at donate@scratch.mit.edu.",
"footer.about": "About Scratch",
"footer.discuss": "Discussion Forums",
"footer.help": "Help Page",
"footer.scratchFamily": "Scratch Family",
"hoc.activityCards": "Activity Cards",
"hoc.activityCardsHeader": "Activity Cards and Guides",
"hoc.activityCardsInfo1": "Want tips and ideas for these Hour of Code&trade; activities? Use the activity cards to get ideas for creating with Scratch. Facilitator guides can help you plan a group activity.",
"hoc.addToStudios": "Add Your Projects to Studios",
"hoc.addToStudiosDescription": "These studios include projects created by young people around the world. Take a look at the studios to get inspired - or submit your own projects to the studios!",
"hoc.facilitatorGuide": "Facilitator Guide",
"hoc.findOutMore": "Find out more",
"hoc.helpScratch": "Help with Scratch",
"hoc.helpScratchDescription": "You can find tutorials and helpful hints in the <a href=\"/projects/editor/?tip_bar=home\">Tips Window</a>. For more resources, see <a href=\"/help\">Scratch Help</a>",
"hoc.moreActivities": "Want More Activities?",
"hoc.moreDescription": "Check out these other tutorials. Or remix one of our <a href=\"/starter_projects\">Starter Projects</a>",
"hoc.officialNotice": "The \"Hour of Code&trade;\" is a nationwide initiative by <a href=\"http://csedweek.org\">Computer Science Education Week</a> and <a href=\"http://code.org\">Code.org</a> to introduce millions of students to one hour of computer science and computer programming.",
"hoc.studioAlice": "Alice in Wonderland Studio",
"hoc.studioWeBareBears": "We Bare Bears Studio",
"hoc.subTitle": "With Scratch, you can program your own stories, games, and animations — and share them online.",
"hoc.tipsDescription": "Need help getting started? Looking for ideas?&nbsp; You can find tutorials and helpful hints in the <a href=\"/projects/editor/?tip_bar=home\">Tips Window</a>",
"hoc.title": "Get Creative with Coding",
"intro.aboutScratch": "ABOUT SCRATCH",
"intro.forEducators": "FOR EDUCATORS",
"intro.forParents": "FOR PARENTS",
"intro.joinScratch": "JOIN SCRATCH",
"intro.seeExamples": "SEE EXAMPLES",
"intro.tagLine": "Create stories, games, and animations<br /> Share with others around the world",
"intro.tryItOut": "TRY IT OUT",
"login.forgotPassword": "Forgot Password?",
"navigation.signOut": "Sign out",
"news.scratchNews": "Scratch News",
"parents.FaqAgeRangeA": "While Scratch is primarily designed for 8 to 16 year olds, it is also used by people of all ages, including younger children with their parents.",
"parents.FaqAgeRangeQ": "What is the age range for Scratch?",
"parents.FaqResourcesQ": "What resources are available for learning Scratch?",
"parents.introDescription": "Scratch is a programming language and an online community where children can program and share interactive media such as stories, games, and animation with people from all over the world. As children create with Scratch, they learn to think creatively, work collaboratively, and reason systematically. Scratch is designed and maintained by the Lifelong Kindergarten group at the MIT Media Lab.",
"splash.featuredProjects": "Featured Projects",
"splash.featuredStudios": "Featured Studios",
"splash.projectsCuratedBy": "Projects Curated by",
"splash.scratchDesignStudioTitle": "Scratch Design Studio",
"splash.visitTheStudio": "Visit the studio",
"splash.recentlySharedProjects": "Recently Shared Projects",
"splash.projectsByScratchersFollowing": "Projects by Scratchers I'm Following",
"splash.projectsLovedByScratchersFollowing": "Projects Loved by Scratchers I'm Following",
"splash.projectsInStudiosFollowing": "Projects in Studios I'm Following",
"splash.communityRemixing": "What the Community is Remixing",
"splash.communityLoving": "What the Community is Loving",
"welcome.welcomeToScratch": "Welcome to Scratch!",
"welcome.learn": "Learn how to make a project in Scratch",
"welcome.tryOut": "Try out starter projects",
"welcome.connect": "Connect with other Scratchers"
}

View file

@ -1,78 +0,0 @@
#!/usr/bin/env node
/*
Converts the existing .po translation files in the module to JSON files.
Requires po2json in order to work. Takes as input a directory
in which to store the resulting json translation files.
*/
var fs = require('fs');
var glob = require('glob');
var path = require('path');
var po2icu = require('po2icu');
var localeCompare = require('../locale-compare');
// -----------------------------------------------------------------------------
// Main script
// -----------------------------------------------------------------------------
var args = process.argv.slice(2);
if (!args.length) {
process.stdout.write('A destination directory must be specified.');
process.exit(1);
}
var poUiDir = path.resolve(__dirname, '../../node_modules/scratchr2_translations/ui');
var outputFile = path.resolve(__dirname, '../../', args[0]);
// Create the directory if it doesn't exist.
var fileInfo = path.parse(outputFile);
try {
fs.accessSync(fileInfo.dir, fs.F_OK);
} catch (err) {
// Doesn't exist create it.
fs.mkdirSync(fileInfo.dir);
}
var icuTemplateFile = path.resolve(__dirname, '../../en.json');
var idsWithICU = JSON.parse(fs.readFileSync(icuTemplateFile, 'utf8'));
var locales = {
en: idsWithICU
};
var icuWithIds = {};
for (var id in idsWithICU) {
icuWithIds[idsWithICU[id]] = id;
}
var md5WithIds = localeCompare.getMD5Map(icuWithIds);
// Get ui localization strings first
glob(poUiDir + '/*', function (err, files) {
if (err) throw new Error(err);
files.forEach(function (file) {
var lang = file.split('/').pop();
var jsFile = path.resolve(file, 'LC_MESSAGES/djangojs.po');
var pyFile = path.resolve(file, 'LC_MESSAGES/django.po');
var translations = {};
try {
var jsTranslations = po2icu.poFileToICUSync(lang, jsFile);
translations = localeCompare.mergeNewTranslations(translations, jsTranslations, idsWithICU, md5WithIds);
} catch (err) {
process.stdout.write(lang + ': ' + err + '\n');
}
try {
var pyTranslations = po2icu.poFileToICUSync(lang, pyFile);
translations = localeCompare.mergeNewTranslations(translations, pyTranslations, idsWithICU, md5WithIds);
} catch (err) {
process.stdout.write(lang + ': ' + err + '\n');
}
locales[lang] = translations;
});
fs.writeFileSync(outputFile, JSON.stringify(locales, null, 4));
});

View file

@ -46,6 +46,7 @@
"jsx-loader": "0.13.2",
"lodash.clone": "3.0.3",
"lodash.defaultsdeep": "3.10.0",
"lodash.merge": "3.3.2",
"lodash.omit": "3.1.0",
"lodash.range": "3.0.1",
"minilog": "2.0.8",

View file

@ -37,7 +37,7 @@ app.use(log());
app.use(compression());
if (isProduction) {
app.use(express.static(path.resolve(__dirname, '../build'), {
lastModified: true,
etag: 'strong',
maxAge: '1y'
}));
}

View file

@ -51,7 +51,7 @@
<script src="/js/lib/react-intl-with-locales{{min}}.js"></script>
<script src="/js/lib/raven.min.js"></script>
<script src="/js/main.bundle.js"></script>
<script src="/js/{{view}}.intl.js"></script>
<script src="/js/{{view}}.bundle.js"></script>
<!-- Error logging (Sentry) -->

View file

@ -1,8 +1,5 @@
var omit = require('lodash.omit');
var React = require('react');
var ReactIntl = require('react-intl');
var FormattedMessage = ReactIntl.FormattedMessage;
var FormattedHTMLMessage = ReactIntl.FormattedHTMLMessage;
var Modal = require('../modal/modal.jsx');
var Registration = require('../registration/registration.jsx');
@ -18,7 +15,16 @@ var Intro = React.createClass({
},
getDefaultProps: function () {
return {
projectCount: 10569070
projectCount: 10569070,
messages: {
'intro.aboutScratch': 'ABOUT SCRATCH',
'intro.forEducators': 'FOR EDUCATORS',
'intro.forParents': 'FOR PARENTS',
'intro.joinScratch': 'JOIN SCRATCH',
'intro.seeExamples': 'SEE EXAMPLES',
'intro.tagLine': 'Create stories, games, and animations<br /> Share with others around the world',
'intro.tryItOut': 'TRY IT OUT'
}
};
},
getInitialState: function () {
@ -52,13 +58,7 @@ var Intro = React.createClass({
return (
<div className="intro">
<div className="content">
<h1>
<FormattedHTMLMessage
id='intro.tagLine'
defaultMessage={
'Create stories, games, and animations<br /> ' +
'Share with others around the world'
} />
<h1 dangerouslySetInnerHTML={{__html: this.props.messages['intro.tagLine']}}>
</h1>
<div className="sprites">
<a className="sprite sprite-1" href="/projects/editor/?tip_bar=getStarted">
@ -70,9 +70,7 @@ var Intro = React.createClass({
src="//cdn.scratch.mit.edu/scratchr2/static/images/cat-b.png" />
<div className="circle"></div>
<div className="text">
<FormattedMessage
id='intro.tryItOut'
defaultMessage='TRY IT OUT' />
{this.props.messages['intro.tryItOut']}
</div>
</a>
<a className="sprite sprite-2" href="/starter_projects/">
@ -84,9 +82,7 @@ var Intro = React.createClass({
src="//cdn.scratch.mit.edu/scratchr2/static/images/tera-b.png" />
<div className="circle"></div>
<div className="text">
<FormattedMessage
id='intro.seeExamples'
defaultMessage='SEE EXAMPLES' />
{this.props.messages['intro.seeExamples']}
</div>
</a>
<a className="sprite sprite-3" href="#" onClick={this.handleJoinClick}>
@ -98,9 +94,7 @@ var Intro = React.createClass({
src="//cdn.scratch.mit.edu/scratchr2/static/images/gobo-b.png" />
<div className="circle"></div>
<div className="text">
<FormattedMessage
id='intro.joinScratch'
defaultMessage='JOIN SCRATCH' />
{this.props.messages['intro.joinScratch']}
</div>
<div className="text subtext">( it&rsquo;s free )</div>
</a>
@ -116,19 +110,13 @@ var Intro = React.createClass({
</div>
<div className="links">
<a href="/about/">
<FormattedMessage
id='intro.aboutScratch'
defaultMessage='ABOUT SCRATCH' />
{this.props.messages['intro.aboutScratch']}
</a>
<a href="/educators/">
<FormattedMessage
id='intro.forEducators'
defaultMessage='FOR EDUCATORS' />
{this.props.messages['intro.forEducators']}
</a>
<a className="last" href="/parents/">
<FormattedMessage
id='intro.forParents'
defaultMessage='FOR PARENTS' />
{this.props.messages['intro.forParents']}
</a>
</div>
</div>

View file

@ -1,8 +1,8 @@
var classNames = require('classnames');
var React = require('react');
var ReactDOM = require('react-dom');
var Api = require('../../mixins/api.jsx');
var jar = require('../../lib/jar.js');
var languages = require('../../../languages.json');
var Select = require('../forms/select.jsx');
@ -13,20 +13,16 @@ var LanguageChooser = React.createClass({
mixins: [
Api
],
getInitialState: function () {
return {
choice: window._locale
};
},
getDefaultProps: function () {
return {
languages: languages
languages: languages,
locale: window._locale
};
},
onSetLanguage: function (e) {
e.preventDefault();
this.setState({'choice': e.target.value});
ReactDOM.findDOMNode(this.refs.languageForm).submit();
jar.set('scratchlanguage', e.target.value);
window.location.reload();
},
render: function () {
var classes = classNames(
@ -35,15 +31,15 @@ var LanguageChooser = React.createClass({
);
return (
<form ref="languageForm" className={classes} action="/i18n/setlang/" method="POST">
<Select name="language" defaultValue={this.state.choice} onChange={this.onSetLanguage}>
<div className={classes}>
<Select name="language" defaultValue={this.props.locale} onChange={this.onSetLanguage}>
{Object.keys(this.props.languages).map(function (value) {
return <option value={value} key={value}>
{this.props.languages[value]}
</option>;
}.bind(this))}
</Select>
</form>
</div>
);
}
});

View file

@ -1,23 +1,9 @@
var React = require('react');
var ReactIntl = require('react-intl');
var defineMessages = ReactIntl.defineMessages;
var injectIntl = ReactIntl.injectIntl;
var Box = require('../box/box.jsx');
require('./news.scss');
var defaultMessages = defineMessages({
scratchNews: {
id: 'news.scratchNews',
defaultMessage: 'Scratch News'
},
viewAll: {
id: 'general.viewAll',
defaultMessage: 'View All'
}
});
var News = React.createClass({
type: 'News',
propTypes: {
@ -25,16 +11,19 @@ var News = React.createClass({
},
getDefaultProps: function () {
return {
items: require('./news.json')
items: require('./news.json'),
messages: {
'general.viewAll': 'View All',
'news.scratchNews': 'Scratch News'
}
};
},
render: function () {
var formatMessage = this.props.intl.formatMessage;
return (
<Box
className="news"
title={formatMessage(defaultMessages.scratchNews)}
moreTitle={formatMessage(defaultMessages.viewAll)}
title={this.props.messages['news.scratchNews']}
moreTitle={this.props.messages['general.viewAll']}
moreHref="/discuss/5/">
<ul>
@ -55,4 +44,4 @@ var News = React.createClass({
}
});
module.exports = injectIntl(News);
module.exports = News;

View file

@ -1,8 +1,4 @@
var React = require('react');
var ReactIntl = require('react-intl');
var injectIntl = ReactIntl.injectIntl;
var FormattedMessage = ReactIntl.FormattedMessage;
var Box = require('../box/box.jsx');
@ -13,10 +9,19 @@ var Welcome = React.createClass({
propTypes: {
onDismiss: React.PropTypes.func
},
getDefaultProps: function () {
return {
messages: {
'welcome.welcomeToScratch': 'Welcome to Scratch!',
'welcome.learn': 'Learn how to make a project in Scratch',
'welcome.tryOut': 'Try out starter projects',
'welcome.connect': 'Connect with other Scratchers'
}
};
},
render: function () {
var formatMessage = this.props.intl.formatMessage;
return (
<Box title={formatMessage({id: 'welcome.welcomeToScratch', defaultMessage: 'Welcome to Scratch!'})}
<Box title={this.props.messages['welcome.welcomeToScratch']}
className="welcome"
moreTitle="x"
moreHref="#"
@ -29,9 +34,7 @@ var Welcome = React.createClass({
<div className="welcome-col blue">
<h4>
<a href="/projects/editor/?tip_bar=getStarted">
<FormattedMessage
id="welcome.learn"
defaultMessage="Learn how to make a project in Scratch" />
{this.props.messages['welcome.learn']}
</a>
</h4>
<a href="/projects/editor/?tip_bar=getStarted">
@ -41,9 +44,7 @@ var Welcome = React.createClass({
<div className="welcome-col green">
<h4>
<a href="/starter_projects/">
<FormattedMessage
id="welcome.tryOut"
defaultMessage="Try out starter projects" />
{this.props.messages['welcome.tryOut']}
</a>
</h4>
<a href="/starter_projects/">
@ -53,9 +54,7 @@ var Welcome = React.createClass({
<div className="welcome-col pink">
<h4>
<a href="/studios/146521/">
<FormattedMessage
id="welcome.connect"
defaultMessage="Connect with other Scratchers" />
{this.props.messages['welcome.connect']}
</a>
</h4>
<a href="/studios/146521/">
@ -67,4 +66,4 @@ var Welcome = React.createClass({
}
});
module.exports = injectIntl(Welcome);
module.exports = Welcome;

View file

@ -1,8 +1,6 @@
var api = require('./mixins/api.jsx').api;
var jar = require('./lib/jar');
var translations = require('../locales/translations.json');
/**
* -----------------------------------------------------------------------------
* Session
@ -72,17 +70,8 @@ var translations = require('../locales/translations.json');
obj = 'pt-br'; // default Portuguese users to Brazilian Portuguese due to our user base. Added in 2.2.5.
}
}
if (typeof translations[obj] === 'undefined') {
// Fall back on the split
obj = obj.split('-')[0];
}
if (typeof translations[obj] === 'undefined') {
// Language appears to not be supported return `null`
obj = null;
}
return obj;
}
window._locale = updateLocale() || 'en';
window._translations = translations;
window._locale = updateLocale();
})();

64
src/l10n.json Normal file
View file

@ -0,0 +1,64 @@
{
"general.accountSettings": "Account settings",
"general.about": "About",
"general.aboutScratch": "About Scratch",
"general.donate": "Donate",
"general.collaborators": "Collaborators",
"general.community": "Community",
"general.contactUs": "Contact Us",
"general.copyright": "Scratch is a project of the Lifelong Kindergarten Group at the MIT Media Lab",
"general.create": "Create",
"general.credits": "Credits",
"general.discuss": "Discuss",
"general.dmca": "DMCA",
"general.explore": "Explore",
"general.faq": "FAQ",
"general.forParents": "For Parents",
"general.forEducators": "For Educators",
"general.guidelines": "Community Guidelines",
"general.help": "Help",
"general.jobs": "Jobs",
"general.joinScratch": "Join Scratch",
"general.legal": "Legal",
"general.learnMore": "Learn More",
"general.messages": "Messages",
"general.myClasses": "My Classes",
"general.myStuff": "My Stuff",
"general.offlineEditor": "Offline Editor",
"general.profile": "Profile",
"general.scratchConference": "Scratch Conference",
"general.scratchday": "Scratch Day",
"general.scratchEd": "ScratchEd",
"general.scratchFoundation": "Scratch Foundation",
"general.scratchJr": "ScratchJr",
"general.search": "Search",
"general.signIn": "Sign in",
"general.statistics": "Statistics",
"general.support": "Support",
"general.tipsWindow": "Tips Window",
"general.tipsAnimateYourNameTitle": "Animate Your Name",
"general.tipsBearstack": "Bearstack Story",
"general.tipsDanceTitle": "Dance, Dance, Dance",
"general.tipsGetStarted": "Getting Started",
"general.tipsHideAndSeekTitle": "Hide-and-Seek Game",
"general.tipsPongGame": "Create a Pong Game",
"general.termsOfUse": "Terms of Use",
"general.username": "Username",
"general.viewAll": "View All",
"general.whatsHappening": "What's Happening?",
"general.wiki": "Scratch Wiki",
"footer.about": "About Scratch",
"footer.discuss": "Discussion Forums",
"footer.help": "Help Page",
"footer.scratchFamily": "Scratch Family",
"login.forgotPassword": "Forgot Password?",
"navigation.signOut": "Sign out",
"parents.FaqAgeRangeA": "While Scratch is primarily designed for 8 to 16 year olds, it is also used by people of all ages, including younger children with their parents.",
"parents.FaqAgeRangeQ": "What is the age range for Scratch?",
"parents.FaqResourcesQ": "What resources are available for learning Scratch?",
"parents.introDescription": "Scratch is a programming language and an online community where children can program and share interactive media such as stories, games, and animation with people from all over the world. As children create with Scratch, they learn to think creatively, work collaboratively, and reason systematically. Scratch is designed and maintained by the Lifelong Kindergarten group at the MIT Media Lab."
}

View file

@ -3,12 +3,45 @@ var ReactDOM = require('react-dom');
var ReactIntl = require('./intl.jsx');
var IntlProvider = ReactIntl.IntlProvider;
var Navigation = require('../components/navigation/navigation.jsx');
var Footer = require('../components/footer/footer.jsx');
var render = function (jsx, element) {
// Get locale and messages from global namespace (see "init.js")
var locale = window._locale;
var messages = window._translations[locale];
var locale = window._locale || 'en';
if (typeof window._messages[locale] === 'undefined') {
// Fall back on the split
locale = locale.split('-')[0];
}
if (typeof window._messages[locale] === 'undefined') {
// Language appears to not be supported fall back to 'en'
locale = 'en';
}
var messages = window._messages[locale];
// Render component
// Render nav and footer for page.
var nav = ReactDOM.render(
<IntlProvider locale={locale} messages={messages}>
<Navigation />
</IntlProvider>,
document.getElementById('navigation')
);
var footer = ReactDOM.render(
<IntlProvider locale={locale} messages={messages}>
<Footer />
</IntlProvider>,
document.getElementById('footer')
);
// Provide list of rendered components
window._renderedComponents = window._renderedComponents || [];
window._renderedComponents.push(nav);
window._renderedComponents.push(footer);
// Render view component
var component = ReactDOM.render(
<IntlProvider locale={locale} messages={messages}>
{jsx}
@ -16,8 +49,6 @@ var render = function (jsx, element) {
element
);
// Provide list of rendered components
window._renderedComponents = window._renderedComponents || [];
window._renderedComponents.push(component);
};

View file

@ -1,9 +0,0 @@
var render = require('./lib/render.jsx');
require('./main.scss');
var Navigation = require('./components/navigation/navigation.jsx');
var Footer = require('./components/footer/footer.jsx');
render(<Navigation />, document.getElementById('navigation'));
render(<Footer />, document.getElementById('footer'));

View file

@ -3,6 +3,7 @@ var FormattedHTMLMessage = require('react-intl').FormattedHTMLMessage;
var FormattedMessage = require('react-intl').FormattedMessage;
var render = require('../../lib/render.jsx');
require('../../main.scss');
require('./about.scss');
var About = React.createClass({

26
src/views/about/l10n.json Normal file
View file

@ -0,0 +1,26 @@
{
"about.introOne": "With Scratch, you can program your own interactive stories, games, and animations — and share your creations with others in the online community.",
"about.introTwo": "Scratch helps young people learn to think creatively, reason systematically, and work collaboratively — essential skills for life in the 21st century.",
"about.introThree": "Scratch is a project of the Lifelong Kindergarten Group at the MIT Media Lab. It is provided free of charge.",
"about.introParents": "Info for parents",
"about.introEducators": "Info for educators",
"about.whoUsesScratch": "Who Uses Scratch?",
"about.whoUsesScratchDescription": "Scratch is designed especially for ages 8 to 16, but is used by people of all ages. Millions of people are creating Scratch projects in a wide variety of settings, including homes, schools, museums, libraries, and community centers.",
"about.aroundTheWorld": "Around the World",
"about.aroundTheWorldDescription": "Scratch is used in more than 150 different countries and available in more than 40 languages. To change languages, click the menu at the bottom of the page. Or, in the Project Editor, click the globe at the top of the page. To add or improve a translation, see the <a href=\"http://wiki.scratch.mit.edu/wiki/How_to_Translate_Scratch\">translation</a> page.",
"about.quotes": "Quotes",
"about.quotesDescription": "The Scratch Team has received many emails from youth, parents, and educators expressing thanks for Scratch. Want to see what people are saying? You can read a collection of the <a href=\"/info/quotes\">quotes</a> we&#39;ve received.",
"about.learnMore": "Learn More About Scratch",
"about.learnMoreHelp": "Scratch Help Page",
"about.learnMoreFaq": "Frequently Asked Questions",
"about.learnMoreParents": "Information for Parents",
"about.learnMoreCredits": "Scratch Credits",
"about.literacy": "Learn to Code, Code to Learn",
"about.literacyDescription": "The ability to code computer programs is an important part of literacy in todays society. When people learn to code in Scratch, they learn important strategies for solving problems, designing projects, and communicating ideas.",
"about.schools": "Scratch in Schools",
"about.schoolsDescription": "Students are learning with Scratch at all levels (from elementary school to college) and across disciplines (such as math, computer science, language arts, social studies). Educators share stories, exchange resources, ask questions, and find people on the <a href=\"http://scratched.gse.harvard.edu/\">ScratchEd website</a>.",
"about.research": "Research",
"about.researchDescription": "The MIT Scratch Team and collaborators are researching how people use and learn with Scratch (for an introduction, see <a href=\"http://web.media.mit.edu/~mres/papers/Scratch-CACM-final.pdf\">Scratch: Programming for All</a>). Find out more about Scratch <a href=\"/info/research\">research</a> and <a href=\"/statistics\">statistics</a> about Scratch.",
"about.support": "Support and Funding",
"about.supportDescription": "The Scratch project, initiated in 2003, has received generous support from the National Science Foundation (grants 0325828, 1002713, 1027848, 1019396), Intel Foundation, Microsoft, MacArthur Foundation, LEGO Foundation, Code-to-Learn Foundation, Google, Dell, Fastly, Inversoft, and MIT Media Lab research consortia. If you&#39;d like to support Scratch, please see our <a href=\"https://secure.donationpay.org/scratchfoundation/\">donate page</a>, or contact us at donate@scratch.mit.edu."
}

View file

@ -8,7 +8,7 @@ var Carousel = require('../../components/carousel/carousel.jsx');
var Input = require('../../components/forms/input.jsx');
var Spinner = require('../../components/spinner/spinner.jsx');
require('../../main.scss');
require('./components.scss');
var Components = React.createClass({

View file

@ -1,6 +1,7 @@
var React = require('react');
var render = require('../../lib/render.jsx');
require('../../main.scss');
require('./credits.scss');
var Credits = React.createClass({

View file

@ -8,6 +8,7 @@ var Button = require('../../components/forms/button.jsx');
var Box = require('../../components/box/box.jsx');
var SubNavigation = require('../../components/subnavigation/subnavigation.jsx');
require('../../main.scss');
require('./hoc.scss');
var Hoc = React.createClass({

19
src/views/hoc/l10n.json Normal file
View file

@ -0,0 +1,19 @@
{
"hoc.activityCards": "Activity Cards",
"hoc.activityCardsHeader": "Activity Cards and Guides",
"hoc.activityCardsInfo1": "Want tips and ideas for these Hour of Code&trade; activities? Use the activity cards to get ideas for creating with Scratch. Facilitator guides can help you plan a group activity.",
"hoc.addToStudios": "Add Your Projects to Studios",
"hoc.addToStudiosDescription": "These studios include projects created by young people around the world. Take a look at the studios to get inspired - or submit your own projects to the studios!",
"hoc.facilitatorGuide": "Facilitator Guide",
"hoc.findOutMore": "Find out more",
"hoc.helpScratch": "Help with Scratch",
"hoc.helpScratchDescription": "You can find tutorials and helpful hints in the <a href=\"/projects/editor/?tip_bar=home\">Tips Window</a>. For more resources, see <a href=\"/help\">Scratch Help</a>",
"hoc.moreActivities": "Want More Activities?",
"hoc.moreDescription": "Check out these other tutorials. Or remix one of our <a href=\"/starter_projects\">Starter Projects</a>",
"hoc.officialNotice": "The \"Hour of Code&trade;\" is a nationwide initiative by <a href=\"http://csedweek.org\">Computer Science Education Week</a> and <a href=\"http://code.org\">Code.org</a> to introduce millions of students to one hour of computer science and computer programming.",
"hoc.studioAlice": "Alice in Wonderland Studio",
"hoc.studioWeBareBears": "We Bare Bears Studio",
"hoc.subTitle": "With Scratch, you can program your own stories, games, and animations — and share them online.",
"hoc.tipsDescription": "Need help getting started? Looking for ideas?&nbsp; You can find tutorials and helpful hints in the <a href=\"/projects/editor/?tip_bar=home\">Tips Window</a>",
"hoc.title": "Get Creative with Coding"
}

View file

@ -0,0 +1,28 @@
{
"splash.featuredProjects": "Featured Projects",
"splash.featuredStudios": "Featured Studios",
"splash.projectsCuratedBy": "Projects Curated by",
"splash.scratchDesignStudioTitle": "Scratch Design Studio",
"splash.visitTheStudio": "Visit the studio",
"splash.recentlySharedProjects": "Recently Shared Projects",
"splash.projectsByScratchersFollowing": "Projects by Scratchers I'm Following",
"splash.projectsLovedByScratchersFollowing": "Projects Loved by Scratchers I'm Following",
"splash.projectsInStudiosFollowing": "Projects in Studios I'm Following",
"splash.communityRemixing": "What the Community is Remixing",
"splash.communityLoving": "What the Community is Loving",
"intro.aboutScratch": "ABOUT SCRATCH",
"intro.forEducators": "FOR EDUCATORS",
"intro.forParents": "FOR PARENTS",
"intro.joinScratch": "JOIN SCRATCH",
"intro.seeExamples": "SEE EXAMPLES",
"intro.tagLine": "Create stories, games, and animations<br /> Share with others around the world",
"intro.tryItOut": "TRY IT OUT",
"news.scratchNews": "Scratch News",
"welcome.welcomeToScratch": "Welcome to Scratch!",
"welcome.learn": "Learn how to make a project in Scratch",
"welcome.tryOut": "Try out starter projects",
"welcome.connect": "Connect with other Scratchers"
}

View file

@ -17,6 +17,7 @@ var Modal = require('../../components/modal/modal.jsx');
var News = require('../../components/news/news.jsx');
var Welcome = require('../../components/welcome/welcome.jsx');
require('../../main.scss');
require('./splash.scss');
var Splash = injectIntl(React.createClass({
@ -318,6 +319,25 @@ var Splash = injectIntl(React.createClass({
var featured = this.renderHomepageRows();
var emailConfirmationStyle = {width: 500, height: 330, padding: 1};
var homepageCacheState = this.getHomepageRefreshStatus();
var formatMessage = this.props.intl.formatMessage;
var formatHTMLMessage = this.props.intl.formatHTMLMessage;
var messages = {
'general.viewAll': formatMessage({id: 'general.viewAll'}),
'news.scratchNews': formatMessage({id: 'news.scratchNews'}),
'welcome.welcomeToScratch': formatMessage({id: 'welcome.welcomeToScratch'}),
'welcome.learn': formatMessage({id: 'welcome.learn'}),
'welcome.tryOut': formatMessage({id: 'welcome.tryOut'}),
'welcome.connect': formatMessage({id: 'welcome.connect'}),
'intro.aboutScratch': formatMessage({id: 'intro.aboutScratch'}),
'intro.forEducators': formatMessage({id: 'intro.forEducators'}),
'intro.forParents': formatMessage({id: 'intro.forParents'}),
'intro.joinScratch': formatMessage({id: 'intro.joinScratch'}),
'intro.seeExamples': formatMessage({id: 'intro.seeExamples'}),
'intro.tagLine': formatHTMLMessage({id: 'intro.tagLine'}),
'intro.tryItOut': formatMessage({id: 'intro.tryItOut'})
};
return (
<div className="splash">
{this.shouldShowEmailConfirmation() ? [
@ -341,14 +361,16 @@ var Splash = injectIntl(React.createClass({
{this.state.session.user ? [
<div key="header" className="splash-header">
{this.shouldShowWelcome() ? [
<Welcome key="welcome" onDismiss={this.handleDismiss.bind(this, 'welcome')}/>
<Welcome key="welcome"
onDismiss={this.handleDismiss.bind(this, 'welcome')}
messages={messages} />
] : [
<Activity key="activity" items={this.state.activity} />
]}
<News items={this.state.news} />
<News items={this.state.news} messages={messages} />
</div>
] : [
<Intro projectCount={this.state.projectCount} key="intro"/>
<Intro projectCount={this.state.projectCount} messages={messages} key="intro"/>
]}
{featured}

View file

@ -3,7 +3,7 @@ var path = require('path');
var po2icu = require('po2icu');
var tap = require('tap');
var buildLocales = require('../../lib/locale-compare');
var buildLocales = require('../../bin/lib/locale-compare');
tap.test('buildLocalesFile', function (t) {
var md5map = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../fixtures/test_es_md5map.json'), 'utf8'));

View file

@ -2,7 +2,7 @@ var fs = require('fs');
var path = require('path');
var tap = require('tap');
var buildLocales = require('../../lib/locale-compare');
var buildLocales = require('../../bin/lib/locale-compare');
tap.test('buildLocalesFile', function (t) {
var actualMd5map = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../fixtures/test_es_md5map.json'), 'utf8'));

View file

@ -1,6 +1,6 @@
var tap = require('tap');
var buildLocales = require('../../lib/locale-compare');
var buildLocales = require('../../bin/lib/locale-compare');
tap.test('buildLocalesMergeTranslations', function (t) {
var existingTranslations = {

View file

@ -1,6 +1,6 @@
var tap = require('tap');
var buildLocales = require('../../lib/locale-compare');
var buildLocales = require('../../bin/lib/locale-compare');
tap.test('buildLocalesGetMD5', function (t) {
var testString1 = 'are there bears here?';

View file

@ -6,8 +6,7 @@ var routes = require('./server/routes.json');
// Prepare all entry points
var entry = {
init: './src/init.js',
main: './src/main.jsx'
init: './src/init.js'
};
routes.forEach(function (route) {
entry[route.view] = './src/views/' + route.view + '/' + route.view + '.jsx';
@ -53,7 +52,8 @@ module.exports = {
},
plugins: [
new CopyWebpackPlugin([
{from: 'static'}
{from: 'static'},
{from: 'intl', to: 'js'}
]),
new webpack.optimize.UglifyJsPlugin({
compress: {