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