Commit 5f7f3a6f authored by Ray Schamp's avatar Ray Schamp

Merge pull request #291 from LLK/release/2.2.4

Release 2.2.4
parents e688d62c 25948c9a
ESLINT=./node_modules/.bin/eslint
NODE=node
SASSLINT=./node_modules/.bin/sass-lint -v
TAP=./node_modules/.bin/tap
WATCH=./node_modules/.bin/watch
WEBPACK=./node_modules/.bin/webpack
GIT_VERSION=$(shell git rev-parse --verify --short=5 HEAD 2> /dev/null)
GIT_VERSION?=$(WWW_VERSION)
GIT_MESSAGE=$(shell git log -1 --pretty=%s 2> /dev/null)
# ------------------------------------
build:
@make clean
@make static
@make translations
@make webpack
@make tag
clean:
rm -rf ./build
......@@ -19,7 +23,7 @@ clean:
deploy:
ifeq ($(shell grep "artifact: deploy.zip" .elasticbeanstalk/config.yml), )
ifeq ($(shell grep "artifact: deploy.zip" .elasticbeanstalk/config.yml 2> /dev/null), )
@echo "You must configure elasticbeanstalk to deploy an artifact."
@echo "Add the following to your .elasticbeanstalk/config.yml"
@echo "deploy:\n artifact: deploy.zip"
......@@ -27,30 +31,20 @@ else
@make build
git archive -o deploy.zip HEAD
zip -rv deploy.zip build
eb deploy -l $$(git rev-parse --verify --short=5 HEAD) -m "$$(git log -1 --pretty=%s)"
eb deploy -l $(GIT_VERSION) -m "$(GIT_MESSAGE)"
endif
static:
cp -a ./static/. ./build/
tag:
echo $(GIT_VERSION) > ./build/version.txt
translations:
./src/scripts/build-locales locales/translations.json
./lib/bin/build-locales locales/translations.json
webpack:
$(WEBPACK) --bail
# ------------------------------------
watch:
$(WATCH) "make clean && make static" ./static &
$(WEBPACK) -d --watch &
wait
stop:
-pkill -f "$(WEBPACK) -d --watch"
-pkill -f "$(WATCH) make clean && make static ./static"
-pkill -f "$(NODE) ./server/index.js"
start:
$(NODE) ./server/index.js
......@@ -59,6 +53,11 @@ start:
test:
@make lint
@make build
@echo ""
@make unit
@echo ""
@make functional
@echo ""
lint:
$(ESLINT) ./*.js
......@@ -72,6 +71,15 @@ lint:
$(SASSLINT) ./src/views/**/*.scss
$(SASSLINT) ./src/components/**/*.scss
unit:
$(TAP) ./test/unit/*.js
functional:
$(TAP) ./test/functional/*.js
integration:
$(TAP) ./test/integration/*.js
# ------------------------------------
.PHONY: build clean deploy static translations webpack watch stop start test lint
.PHONY: build clean deploy static tag translations webpack watch stop start test lint
......@@ -9,38 +9,41 @@ npm install
npm run build
```
During development, you can use `npm run watch` to cause any update you make to files in either `./static` or `./src` to trigger a rebuild of the project.
### To Run
```bash
npm start
```
or to start and watch at once
```bash
npm run dev
```
During development, `npm start` watches any update you make to files in either `./static` or `./src` and triggers a rebuild of the project. In development the build is stored in memory, and not served from the `./build` directory.
Once running, open `http://localhost:8333` in your browser. If you wish to have the server reload automatically, you can install either [nodemon](https://github.com/remy/nodemon) or [forever](https://github.com/foreverjs/forever).
### To stop
```bash
# Stops all `start` and `watch` processes
npm stop
```
Use `^C` to stop the node process `npm start` starts.
#### Configuration
`npm start` and `npm run watch` can be configured with the following environment variables
`npm start` can be configured with the following environment variables
| Variable | Default | Description |
| ------------- | ------------------------------------- | ---------------------------------------------- |
| `API_HOST` | `https://api.scratch.mit.edu` | Hostname for API requests |
| `NODE_ENV` | `null` | If not `production`, app acts like development |
| `PORT` | `8333` | Port for devserver (http://localhost:XXXX) |
| `PROXY_HOST` | `https://scratch.mit.edu` | Pass-through location for scratchr2 |
| `FALLBACK` | `''` | Pass-through location for scratchr2 |
### To Test
```bash
npm test
```
### Current issues with the development
We're currently in the process of transitioning into this web client from Scratch's existing structure. As we transition, there are going to be some issues along the way that relate to how this client needs to interact with the existing infrastructure to work properly in production.
On top of migrating to using this as our web client, Scratch is also transitioning into using a new API backend, Scratch REST API. As that is also currently in development and incomplete, we are set up to fall back to using existing Scratch endpoints if an API endpoint does not exist – which is where the `FALLBACK` comes in.
Most of the issues we have currently revolve around the use of `FALLBACK`. This variable is used to specify what url to fall back onto should a request fail within the context of this webclient, or when using the `API_HOST`. If not specified in the process, it will not be used, and any request that is not made through the web client or the API will be unreachable.
Setting `FALLBACK=https://scratch.mit.edu` allows the web client to retrieve data from the Scratch website in your development environment. However, because of security concerns, trying to send data to Scratch through your development environment won't work. This means the following things will be broken for the time being:
* Login on the splash page (*In the process of being fixed*)
* Some update attempts to production data made through a development version of the web client
{
"an": {
"locale": "an",
"parentLocale": "ca"
},
"la": {
"locale": "la",
"parentLocale": "it"
},
"yum": {
"locale": "yum",
"parentLocale": "en"
},
"cat": {
"locale": "cat",
"parentLocale": "en"
}
}
{
"general.accountSettings": "Account settings",
"general.about": "About",
"general.aboutScratch": "About Scratch",
"general.donate": "Donate",
"general.collaborators": "Collaborators",
"general.community": "Community",
......@@ -21,6 +22,7 @@
"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",
......@@ -45,10 +47,37 @@
"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 today’s 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.",
......@@ -66,20 +95,25 @@
"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",
"infro.forParents": "FOR PARENTS",
"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",
......@@ -91,6 +125,7 @@
"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",
......
......@@ -16,7 +16,6 @@
"es": "Español",
"eu": "Euskara",
"fr": "Français",
"fr-ca": "Français (Canada)",
"ga": "Gaeilge",
"gd": "Gàidhlig",
"gl": "Galego",
......@@ -33,6 +32,7 @@
"cat": "Meow",
"nl": "Nederlands",
"nb": "Norsk Bokmål",
"uz": "Oʻzbekcha",
"pl": "Polski",
"pt": "Português",
"pt-br": "Português Brasileiro",
......
......@@ -5,29 +5,17 @@
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');
/*
Existing translations should be in the key value format specified by react-intl (i.e.
formatted message id, with icu string as the value). New Translations should be in the
format returned by po2icu (i.e. a source language icu string for key, and a localized
language icu string for value).
var localeCompare = require('../locale-compare');
// -----------------------------------------------------------------------------
// Main script
// -----------------------------------------------------------------------------
ICU Map is an object in the reverse react-intl formatting (icu string as key), which will
help determine if the translation belongs in www currently.
*/
var mergeNewTranslations = function (existingTranslations, newTranslations, icuMap) {
for (var id in newTranslations) {
if (icuMap.hasOwnProperty(id) && newTranslations[id].length > 0) {
existingTranslations[icuMap[id]] = newTranslations[id];
}
}
return existingTranslations;
};
var args = process.argv.slice(2);
......@@ -49,13 +37,15 @@ try {
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 locales = {
en: idsWithICU
};
var md5WithIds = localeCompare.getMD5Map(icuWithIds);
// Get ui localization strings first
glob(poUiDir + '/*', function (err, files) {
......@@ -67,16 +57,17 @@ glob(poUiDir + '/*', function (err, files) {
var pyFile = path.resolve(file, 'LC_MESSAGES/django.po');
var translations = {};
try {
var jsTranslations = po2icu.poFileToICUSync(lang, jsFile);
translations = mergeNewTranslations(translations, jsTranslations, icuWithIds);
translations = localeCompare.mergeNewTranslations(translations, jsTranslations, idsWithICU, md5WithIds);
} catch (err) {
process.stdout.write(lang + ': ' + err + '\n');
}
try {
var pyTranslations = po2icu.poFileToICUSync(lang, pyFile);
translations = mergeNewTranslations(translations, pyTranslations, icuWithIds);
translations = localeCompare.mergeNewTranslations(translations, pyTranslations, idsWithICU, md5WithIds);
} catch (err) {
process.stdout.write(lang + ': ' + err + '\n');
}
......
// -----------------------------------------------------------------------------
// Helper Methods for build-locales node script.
// -----------------------------------------------------------------------------
var crypto = require('crypto');
var Helpers = {};
/**
* Get the md5 has of a string with whitespace removed.
*
* @param {string} string a string
* @return {string} an md5 hash of the string in hex.
*/
Helpers.getMD5 = function (string) {
var cleanedString = string.replace(/\s+/g, '');
return crypto.createHash('md5').update(cleanedString, 'utf8').digest('hex');
};
/*
Existing translations should be in the key value format specified by react-intl (i.e.
formatted message id, with icu string as the value). New Translations should be in the
format returned by po2icu (i.e. a source language icu string for key, and a localized
language icu string for value).
ICU Map is an object in the reverse react-intl formatting (icu string as key), which will
help determine if the translation belongs in www currently.
*/
Helpers.mergeNewTranslations = function (existingTranslations, newTranslations, icuTemplate, md5Map) {
for (var id in newTranslations) {
var md5 = Helpers.getMD5(id);
if (md5Map.hasOwnProperty(md5) && newTranslations[id].length > 0) {
existingTranslations[md5Map[md5]] = newTranslations[id];
}
}
//Fill in defaults
for (var id in icuTemplate) {
if (!existingTranslations.hasOwnProperty(id)) existingTranslations[id] = icuTemplate[id];
}
return existingTranslations;
};
/**
* Converts a map of icu strings with react-intl id values into a map
* with md5 hashes of the icu strings as keys and react-intl id values.
* This is done so as to eliminate potential po conversion misses that
* could be caused by different white space formatting between po and icu.
*
* The md5 is generated after all white space is removed from the string.
*
* @param {object} idICUMap map where key=icuString, value=react-intl id
*
* @return {object}
*/
Helpers.getMD5Map = function (ICUIdMap) {
var md5Map = {};
for (var icu in ICUIdMap) {
var md5 = Helpers.getMD5(icu);
md5Map[md5] = ICUIdMap[icu];
}
return md5Map;
};
module.exports = Helpers;
......@@ -7,18 +7,21 @@ var express = require('express');
var path = require('path');
var proxy = require('express-http-proxy');
var url = require('url');
var webpackDevMiddleware = require('webpack-dev-middleware');
var webpack = require('webpack');
var handler = require('./handler');
var log = require('./log');
var proxies = require('./proxies.json');
var routes = require('./routes.json');
var isProduction = process.env.NODE_ENV === 'production';
// Create server
var app = express();
app.disable('x-powered-by');
// Block POST & PUT requests in production
if (process.env.NODE_ENV === 'production') {
if (isProduction) {
app.use(function (req, res, next) {
if (req.method === 'GET') return next();
if (req.method === 'OPTIONS') return next();
......@@ -32,10 +35,12 @@ if (process.env.NODE_ENV === 'production') {
// Server setup
app.use(log());
app.use(compression());
app.use(express.static(path.resolve(__dirname, '../build'), {
lastModified: true,
maxAge: '1y'
}));
if (isProduction) {
app.use(express.static(path.resolve(__dirname, '../build'), {
lastModified: true,
maxAge: '1y'
}));
}
app.use(function (req, res, next) {
req._path = url.parse(req.url).path;
next();
......@@ -65,21 +70,21 @@ if (typeof process.env.NODE_SENTRY_DSN === 'string') {
});
}
// Bind proxies in development
if (process.env.NODE_ENV !== 'production') {
var proxyHost = process.env.PROXY_HOST || 'https://scratch.mit.edu';
app.use('/', proxy(proxyHost, {
filter: function (req) {
for (var i in proxies) {
if (req._path.indexOf(proxies[i]) === 0) return true;
}
return false;
},
forwardPath: function (req) {
return req._path;
if (!isProduction) {
// Use webpack-dev-server in development
var compiler = webpack(require('../webpack.config.js'));
app.use(webpackDevMiddleware(compiler, {
headers: {
'X-From-Webpack': true
}
}));
var proxyHost = process.env.FALLBACK || '';
if (proxyHost !== '') {
// Fall back to scratchr2 in development
// This proxy middleware must come last
app.use('/', proxy(proxyHost));
}
}
// Start listening
......
[
"/accounts/",
"/csrf_token/",
"/fragment/",
"/get_image/",
"/i18n/setlang/",
"/login_retry/",
"/media/",
"/scratch_admin/homepage/clear-cache/",
"/session/",
"/site-api",
"/static/"
]
......@@ -18,5 +18,10 @@
"pattern": "/hoc",
"view": "hoc",
"title": "Hour of Code"
},
{
"pattern": "/info/credits",
"view": "credits",
"title": "Credits"
}
]
......@@ -7,7 +7,7 @@
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta http-equiv="x-frame-options" content="deny">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="viewport" content="width=942, initial-scale=1">
<title>Scratch - {{title}}</title>
......@@ -48,6 +48,7 @@
<!-- Scripts -->
<script src="/js/lib/react{{min}}.js"></script>
<script src="/js/lib/react-dom{{min}}.js"></script>
<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>
......
......@@ -132,7 +132,7 @@ var Footer = React.createClass({
</a>
</dd>
<dd>
<a href="https://secure.donationpay.org/codetolearn/">
<a href="https://secure.donationpay.org/scratchfoundation/">
<FormattedMessage
id='general.donate'
defaultMessage={'Donate'} />
......
......@@ -281,6 +281,15 @@ var Navigation = React.createClass({
<FormattedMessage {...defaultMessages.myStuff} />
</a>
</li>
{this.state.session.permissions.educator ? [
<li>
<a href="/educators/classes/">
<FormattedMessage
id='general.myClasses'
defaultMessage={'My Classes'} />
</a>
</li>
] : []}
<li>
<a href="/accounts/settings/">
<FormattedMessage
......
......@@ -35,7 +35,7 @@ var News = React.createClass({
className="news"
title={formatMessage(defaultMessages.scratchNews)}
moreTitle={formatMessage(defaultMessages.viewAll)}
moreHref="/news">
moreHref="/discuss/5/">
<ul>
{this.props.items.map(function (item) {
......
var ReactIntl = require('react-intl');
var customLanguages = require('../../custom-locales.json');
for (var locale in customLanguages) {
ReactIntl.addLocaleData(customLanguages[locale]);
}
module.exports = ReactIntl;
var ReactDOM = require('react-dom');
var ReactIntl = require('react-intl');
var ReactIntl = require('./intl.jsx');
var IntlProvider = ReactIntl.IntlProvider;
var render = function (jsx, element) {
......
......@@ -34,6 +34,9 @@ var Api = {
}
xhr(opts, function (err, res, body) {
if (err) log.error(err);
// Legacy API responses come as lists, and indicate to redirect the client like
// [{success: true, redirect: "/location/to/redirect"}]
if (body && body[0] && 'redirect' in body[0]) window.location = body[0].redirect;
callback(err, body);
});
}.bind(this);
......
var React = require('react');
var FormattedHTMLMessage = require('react-intl').FormattedHTMLMessage;
var FormattedMessage = require('react-intl').FormattedMessage;
var render = require('../../lib/render.jsx');
require('./about.scss');
......@@ -7,8 +9,92 @@ var About = React.createClass({
type: 'About',
render: function () {
return (
<div>
<h1>I am the about page!</h1>
<div className="inner">
<h1><FormattedMessage id='general.aboutScratch' /></h1>
<div className="masthead">
<div>
<p><FormattedMessage id='about.introOne' /></p>
<p><FormattedMessage id='about.introTwo' /></p>
<p><FormattedMessage id='about.introThree' /></p>
<ul>
<li><a href = "/parents/"><FormattedMessage id="about.introParents" /></a></li>
<li><a href = "/educators/"><FormattedMessage id="about.introEducators" /></a></li>
</ul>
</div>
<div>
<iframe
src="https://player.vimeo.com/video/65583694?title=0&byline=0&portrait=0"
frameBorder="0"
webkitAllowFullScreen
mozallowfullscreen
allowFullScreen />
</div>
</div>
<div className="body">
<ul>
<li>
<h2><FormattedMessage id='about.whoUsesScratch' /></h2>
<img src="/images/about/who-uses-scratch.jpg" />
<p><FormattedHTMLMessage id='about.whoUsesScratchDescription' /></p>
</li>
<li>
<h2><FormattedMessage id='about.literacy' /></h2>
<iframe
src="https://embed-ssl.ted.com/talks/mitch_resnick_let_s_teach_kids_to_code.html"
scrolling="no"
webkitAllowFullScreen
mozallowfullscreen
allowFullScreen />
<p><FormattedHTMLMessage id='about.literacyDescription' /></p>
</li>
<li>
<h2><FormattedMessage id='about.aroundTheWorld' /></h2>
<img src="/images/about/around-the-world.png" />
<p><FormattedHTMLMessage id='about.aroundTheWorldDescription' /></p>
</li>
<li>
<h2><FormattedMessage id='about.schools' /></h2>
<img src="/images/about/scratch-in-schools.jpg" />
<p><FormattedHTMLMessage id='about.schoolsDescription' /></p>
</li>
<li>
<h2><FormattedMessage id='about.quotes' /></h2>
<img src="/images/about/quotes.gif" />
<p><FormattedHTMLMessage id='about.quotesDescription' /></p>
</li>
<li>
<h2><FormattedMessage id='about.research' /></h2>
<img src="/images/about/research-remix.png" />
<p><FormattedHTMLMessage id='about.researchDescription' /></p>
</li>
<li>
<h2><FormattedMessage id='about.learnMore' /></h2>
<p>
<ul className="list">
<li><a href="/help"><FormattedMessage id='about.learnMoreHelp' /></a></li>
<li><a href="/info/faq"><FormattedMessage id='about.learnMoreFaq' /></a></li>
<li><a href="/parents"><FormattedMessage id='about.learnMoreParents' /></a></li>
<li><a href="/credits"><FormattedMessage id='about.learnMoreCredits' /></a></li>
</ul>
</p>
</li>
<li>
<h2><FormattedMessage id='about.support' /></h2>
<p><FormattedHTMLMessage id='about.supportDescription' /></p>
</li>
</ul>
</div>
</div>
);
}
......
@import "../../colors";
#view {
.masthead {
display: flex;
flex-wrap: no-wrap;
justify-content: space-between;
align-items: stretch;
div {
display: inline-block;
width: calc(50% - 10px);
ul {
margin: 0;
padding: 0;
list-style: none;
}
li {
display: inline-block;
text-transform: uppercase;
&:nth-child(odd) {
margin-right: 10px;
border-right: 1px solid $ui-gray;
padding-right: 10px;
}
}
iframe {
border: 1px solid $ui-gray;
width: 460px;
height: 290px;
}
}
}
.body {
position: relative;
margin: 20px 0 0;
border: 1px solid $ui-gray;
border-radius: 10px;
background: $ui-white;
padding: 26px 40px 6px;
width: calc(100% - 80px);
ul {
display: flex;
margin: 0;
padding: 0;
list-style: none;
justify-content: space-between;
flex-wrap: wrap;
}
li {
display: inline-block;
margin-bottom: 20px;
width: calc(384px + 5px + 5px);
}
img, iframe {
display: block;
border: 1px solid $ui-gray;
padding: 5px;
width: 396px;
height: 222px;
}
}
}
This diff is collapsed.
@import "../../colors";
#view {
p {
line-height: 1.5rem;
}
ul {
display: flex;
margin: 0;
padding: 0;
list-style: none;
flex-wrap: wrap;
}
li {
display: inline-block;
margin: 10px 0;
width: 188px;
text-align: center;
img {
margin: 10px 23px;
border: 2px;
border-style: solid;
border-radius: 50%;
border-color: $ui-dark-gray;
background-color: $ui-white;
padding: 20px;
width: 85px;
height: 85px;
}
}
}
......@@ -399,9 +399,8 @@ var Hoc = React.createClass({
</div>
</section>
</div>
<img src = "https://code.org/api/hour/begin_scratch.png" />
</div>
);
}
});
......
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"<p> <img src=\"{STATIC_URL}/images/help/ask-and-wait.png\" /> asks a question and stores the keyboard input in <img src=\"{STATIC_URL}/images/help/answer.png\" />. The answer is shared by all sprites. </p><p>If you want to save the current answer, you can store it in a variable or list. For example, <img src=\"{STATIC_URL}/images/help/answer-ex2.png\"/> </p><p>To view the value of answer, click the checkbox next to the answer block.<br><img src=\"{STATIC_URL}/images/help/answer-checkbox.png\" /></p>": "test.id1",
"<p> <img src=\"{STATIC_URL}/images/help/ask-and-wait.png\" /> asks a question and stores the keyboard input in <img src=\"{STATIC_URL}/images/help/answer.png\" />. The question appears in a voice balloon on the screen. The program waits as the user types in a response, until the Enter key is pressed or the check mark is clicked. </p>": "test.id2"
}
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-03-20 14:16+0000\n"
"PO-Revision-Date: 2015-09-21 17:37+0000\n"
"Last-Translator: Anonymous Pootle User\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Pootle 2.5.1.1\n"
"X-POOTLE-MTIME: 1442857052.000000\n"
#: test.html:15
#, python-format
msgid ""
"\n"
"<p> <img src=\"%(STATIC_URL)s/images/help/ask-and-wait.png\" /> asks a "
"question and stores the keyboard input in <img src=\"%(STATIC_URL)s/images/"
"help/answer.png\" />. The answer is shared by all sprites. </p>\n"
"<p>If you want to save the current answer, you can store it in a variable or "
"list. For example, <img src=\"%(STATIC_URL)s/images/help/answer-ex2.png\"/"
"> \n"
"</p>\n"
"\n"
"<p>\n"
"To view the value of answer, click the checkbox next to the answer block."
"<br>\n"
"<img src=\"%(STATIC_URL)s/images/help/answer-checkbox.png\" />\n"
"</p>"
msgstr ""
"\n"
"<p><img src=\"%(STATIC_URL)s/images/help/es/ask-and-wait.png\" /> hace una "
"pregunta y almacena la entrada de teclado en <img src=\"%(STATIC_URL)s/"
"images/help/es/answer.png\" />. La respuesta se comparte para todos los "
"objetos. </p>\n"
"<p>Si deseas guardar la respuesta actual, debes almacenarla en una variable "
"o una lista. Por ejemplo, <img src=\"%(STATIC_URL)s/images/help/es/answer-"
"ex2.png\"/> \n"
"</p>\n"
"\n"
"<p>\n"
"Si deseas ver el valor de una respuesta, haz clic en la casilla que aparece "
"junto al bloque de respuesta.<br>\n"
"<img src=\"%(STATIC_URL)s/images/help/es/answer-checkbox.png\" />\n"
"</p>"
#: test.html:18
#, python-format
msgid ""
"\n"
"<p> <img src=\"%(STATIC_URL)s/images/help/ask-and-wait.png\" /> asks a "
"question and stores the keyboard input in <img src=\"%(STATIC_URL)s/images/"
"help/answer.png\" />. The question appears in a voice balloon on the screen. "
"The program waits as the user types in a response, until the Enter key is "
"pressed or the check mark is clicked. \n"
"</p>"
msgstr ""
"\n"
"<p> <img src=\"%(STATIC_URL)s/images/help/es/ask-and-wait.png\" /> hace una "
"pregunta y almacena la entrada de teclado en <img src=\"%(STATIC_URL)s/"
"images/help/es/answer.png\" />. La pregunta aparece en un globo de voz en la "
"pantalla. El programa espera hasta que el usuario escriba una respuesta y "
"presione Enter o haga clic en la casilla de aprobación.\n"
"</p>"
{
"2ec20d41b181e1a41c071e13f414a74d": "test.id1",
"37ba6d5ef524504215f478912155f9ba": "test.id2"
}
var fs = require('fs');
var path = require('path');
var po2icu = require('po2icu');
var tap = require('tap');
var buildLocales = require('../../lib/locale-compare');
tap.test('buildLocalesFile', function (t) {
var md5map = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../fixtures/test_es_md5map.json'), 'utf8'));
var newTranslations = po2icu.poFileToICUSync('es', path.resolve(__dirname, '../fixtures/test_es.po'));
var translations = buildLocales.mergeNewTranslations({}, newTranslations, {}, md5map);
t.ok(translations['test.id1'] !== undefined);
t.ok(translations['test.id2'] !== undefined);
t.end();
});
var fs = require('fs');
var path = require('path');
var tap = require('tap');
var buildLocales = require('../../lib/locale-compare');
tap.test('buildLocalesFile', function (t) {
var actualMd5map = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../fixtures/test_es_md5map.json'), 'utf8'));
var templates = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../fixtures/build_locales.json'), 'utf8'));
var testMd5map = buildLocales.getMD5Map(templates);
for (var key in actualMd5map) {
t.ok(testMd5map[key] !== undefined);
}
t.end();
});
var tap = require('tap');
var buildLocales = require('../../lib/locale-compare');
tap.test('buildLocalesMergeTranslations', function (t) {
var existingTranslations = {
'test.test1': 'It\'s like raaayaaain, on your wedding day',
'test.test2': 'Free to flyyy, when you already paid'
};
var newTranslations = {
'Isn\'t it ironic? No.': 'Es irónico? No.'
};
var md5map = {
'c21ce5ceefe167028182032d4255a384': 'test.test1',
'9c40648034e467e16f8d6ae24bd610ab': 'test.test2',
'6885a345adafb3a9dd43d9f549430c88': 'test.test3'
};
var mergedTranslations = buildLocales.mergeNewTranslations(existingTranslations, newTranslations, {}, md5map);
t.ok(mergedTranslations['test.test3'] !== undefined);
t.ok(mergedTranslations['test.test2'] !== undefined);
t.end();
});
var tap = require('tap');
var buildLocales = require('../../lib/locale-compare');
tap.test('buildLocalesGetMD5', function (t) {
var testString1 = 'are there bears here?';
var testString2 = 'are\nthere\tbears here?';
t.equal(buildLocales.getMD5(testString1), buildLocales.getMD5(testString2));
t.end();
});
var CopyWebpackPlugin = require('copy-webpack-plugin');
var path = require('path');
var webpack = require('webpack');
......@@ -9,9 +10,7 @@ var entry = {
main: './src/main.jsx'
};
routes.forEach(function (route) {
if ( ! route.static ) {
entry[route.view] = './src/views/' + route.view + '/' + route.view + '.jsx';
}
entry[route.view] = './src/views/' + route.view + '/' + route.view + '.jsx';
});
// Config
......@@ -21,11 +20,12 @@ module.exports = {
externals: {
'react': 'React',
'react/addons': 'React',
'react-dom': 'ReactDOM'
'react-dom': 'ReactDOM',
'react-intl': 'ReactIntl'
},
output: {
path: path.resolve(__dirname, 'build/js'),
filename: '[name].bundle.js'
path: path.resolve(__dirname, 'build'),
filename: 'js/[name].bundle.js'
},
module: {
loaders: [
......@@ -52,12 +52,14 @@ module.exports = {
fs: 'empty'
},
plugins: [
new CopyWebpackPlugin([
{from: 'static'}
]),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
}),
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.optimize.DedupePlugin()
new webpack.optimize.OccurenceOrderPlugin()
]
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment