diff --git a/.jshintignore b/.jshintignore
new file mode 100644
index 0000000..07e6e47
--- /dev/null
+++ b/.jshintignore
@@ -0,0 +1 @@
+/node_modules
diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 0000000..a5aaaed
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,3 @@
+{
+ "esnext": true
+}
diff --git a/ComponentInterpolator.js b/ComponentInterpolator.js
new file mode 100644
index 0000000..978ab05
--- /dev/null
+++ b/ComponentInterpolator.js
@@ -0,0 +1,99 @@
+/*
+Given:
+
+Ohai {this.props.user}, click here right now please!
+
+Pre-process it into:
+
+
+
+
+
+*/
+
+var React = require('react');
+var cloneWithProps = require('react/lib/cloneWithProps');
+var invariant = require('react/lib/invariant');
+var { string } = React.PropTypes;
+
+var OWN_PROPS = ['defaultValue', 'translateKey', 'children'];
+
+var ComponentInterpolator = React.createClass({
+ propTypes: {
+ string: string.isRequired
+ },
+
+ componentWillMount() {
+ var textCount = this.textCount();
+ var componentCount = this.componentCount();
+ invariant(
+ textCount === 0,
+ ' cannot have any text children'
+ );
+ },
+
+ textCount(node) {
+ node = node || this;
+ count = 0;
+ React.Children.forEach(node.props.children, function(child) {
+ count += typeof child === 'string' ? 1 : this.textCount(child);
+ });
+ return count;
+ },
+
+ componentCount(node) {
+ node = node || this;
+ count = 0;
+ React.Children.forEach(node.props.children, function(child) {
+ count += typeof child === 'string' ? 0 : 1 + this.componentCount(child);
+ });
+ return count;
+ },
+
+ inferChildren(string, children) {
+ var tokens = (string || '').split(/(\*+)/);
+ return this.interpolateChildren(tokens, children);
+ },
+
+ interpolateChildren(tokens, children, eof) {
+ var token, child, newChildren = [];
+ while (tokens.length) {
+ token = tokens.shift();
+ if (token === eof) break;
+ if (token.match(/\*/)) {
+ child = children.shift();
+ child = cloneWithProps(child, {
+ key: child.key,
+ children: this.interpolateChildren(tokens, child.children, token)
+ });
+ }
+ else {
+ child = token;
+ }
+ newChildren.push(child);
+ }
+ return newChildren;
+ },
+
+ extraProps() {
+ var props = {};
+ for (var key in this.props) {
+ if (OWN_PROPS.indexOf(key) === -1)
+ props[key] = this.props[key];
+ }
+ return props;
+ },
+
+ render() {
+ var options = this.extraProps();
+ var translateKey = this.props.translateKey;
+ var defaultValue = this.props.defaultValue || this.props.children;
+ options.defaultValue = defaultValue;
+
+ var children = this.inferChildren(this.props.string, this.props.children);
+ return React.createElement('span', {}, children);
+ }
+});
+
+module.exports = ComponentInterpolator;
+
diff --git a/Translate.js b/Translate.js
index 4663633..a224a6f 100644
--- a/Translate.js
+++ b/Translate.js
@@ -5,18 +5,18 @@ Given:
Pre-process it into:
-
+
-
+
*/
var React = require('react');
-var cloneWithProps = require('react/lib/cloneWithProps');
+var I18n = require('i18n');
var invariant = require('react/lib/invariant');
var { string } = React.PropTypes;
-OWN_PROPS = ['defaultValue', 'translateKey', 'children'];
+var OWN_PROPS = ['defaultValue', 'translateKey', 'children'];
var Translate = React.createClass({
propTypes: {
@@ -29,14 +29,14 @@ var Translate = React.createClass({
var componentCount = this.componentCount();
invariant(
textCount <= 1,
- ' can only have a one text child when not using pre-processing'
+ ' can only have one text child when not using pre-processing'
);
invariant(
- componentCount === 0 || textCount === 0,
- ' cannot have both text and component children when not using pre-processing'
+ componentCount === 0,
+ ' cannot have any component children when not using pre-processing'
);
invariant(
- textCount > 0 || this.props.defaultValue || this.props.translateKey,
+ textCount === 1 || this.props.defaultValue || this.props.translateKey,
' needs at least a translateKey, defaultValue, or text child'
);
},
@@ -50,49 +50,6 @@ var Translate = React.createClass({
return count;
},
- componentCount() {
- node = node || this;
- count = 0;
- React.Children.forEach(node.props.children, function(child) {
- count += typeof child === 'string' ? 0 : 1 + this.componentCount(child);
- });
- return count;
- },
-
- inferChildren(string, children) {
- var tokens = string.split(/(\*+)/);
- return this.interpolateChildren(tokens, children, result);
- },
-
- interpolateChildren(tokens, children, eof) {
- var token, child, newChildren = [];
- while (tokens.length) {
- token = tokens.shift();
- break if token === eof;
- if (token.match(/\*/)) {
- child = children.shift();
- child = cloneWithProps(child, {
- key: child.key,
- children: this.interpolateChildren(tokens, child.children, token)
- });
- }
- else {
- child = token;
- }
- newChildren.push(child);
- }
- return newChildren;
- },
-
- extraProps() {
- var props = {};
- for (var key in this.props) {
- if (OWN_PROPS.indexOf(key) === -1)
- props[key] = this.props[key];
- }
- return props;
- },
-
render() {
var options = this.extraProps();
var translateKey = this.props.translateKey;
diff --git a/__tests__/ComponentInterpolator.test.js b/__tests__/ComponentInterpolator.test.js
new file mode 100644
index 0000000..3650b47
--- /dev/null
+++ b/__tests__/ComponentInterpolator.test.js
@@ -0,0 +1,15 @@
+var subjector = require('../test_utils/subjector');
+var Subject = subjector(__dirname + '/../ComponentInterpolator');
+
+describe('ComponentInterpolator', function() {
+ it('renders', function() {
+ var subject = Subject({string: 'Hello World'});
+ expect(subject.isMounted()).toEqual(true);
+ expect(subject.getDOMNode().textContent).toEqual('Hello World');
+ });
+
+ it('escapes html in the string', function() {
+ var subject = Subject({string: 'My favorite tag is '});
+ expect(subject.getDOMNode().textContent).toEqual('My favorite tag is ');
+ });
+});
diff --git a/package.json b/package.json
index 1b4b692..b9a8474 100644
--- a/package.json
+++ b/package.json
@@ -4,11 +4,25 @@
"description": "i18n made simple",
"main": "main.js",
"scripts": {
- "test": "echo \"Error: no test specified\" && exit 1"
+ "test": "node_modules/.bin/jsxhint . && node_modules/.bin/jest"
},
"author": "Jon Jensen",
"license": "ISC",
"devDependencies": {
- "react": "^0.12.2"
+ "jest-cli": "^0.2.2",
+ "jsxhint": "^0.10.0",
+ "react": "0.12.x",
+ "react-tools": "^0.12.2"
+ },
+ "peerDependencies": {
+ "react": "0.12.x"
+ },
+ "jest": {
+ "scriptPreprocessor": "test_utils/preprocessor.js",
+ "setupTestFrameworkScriptFile": "test_utils/jasmine.js",
+ "unmockedModulePathPatterns": [
+ "node_modules",
+ "test_utils"
+ ]
}
}
diff --git a/test_utils/jasmine.js b/test_utils/jasmine.js
new file mode 100644
index 0000000..07ed006
--- /dev/null
+++ b/test_utils/jasmine.js
@@ -0,0 +1 @@
+jasmine.VERBOSE = true;
diff --git a/test_utils/preprocessor.js b/test_utils/preprocessor.js
new file mode 100644
index 0000000..d380a11
--- /dev/null
+++ b/test_utils/preprocessor.js
@@ -0,0 +1,6 @@
+var ReactTools = require('react-tools');
+module.exports = {
+ process: function(src) {
+ return ReactTools.transform(src, {harmony: true});
+ }
+};
diff --git a/test_utils/subjector.js b/test_utils/subjector.js
new file mode 100644
index 0000000..74a9590
--- /dev/null
+++ b/test_utils/subjector.js
@@ -0,0 +1,14 @@
+module.exports = function(path) {
+ jest.dontMock(path);
+
+ var React = require('react/addons');
+ var { addons: { TestUtils } } = React;
+ var Component = require(path);
+
+ return function(props) {
+ return TestUtils.renderIntoDocument(
+ React.createElement(Component, props)
+ );
+ };
+};
+