Skip to content

Commit

Permalink
Merge pull request #18 from synacor/markup-text
Browse files Browse the repository at this point in the history
Add <MarkupText /> component
  • Loading branch information
billneff79 authored Feb 8, 2018
2 parents 86aad59 + 5e78322 commit 908c3f3
Show file tree
Hide file tree
Showing 9 changed files with 319 additions and 49 deletions.
93 changes: 72 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

**Simple localization for Preact.**

- Tiny: about 1.2kb gzipped
- Tiny: about 1.3kb gzipped
- Supports dictionary and key scopes/namespaces while maintaining a global dictionary
- Supports nested dictionaries:
- Wrap your component in a default dictionary and scope key
Expand All @@ -19,11 +19,6 @@
- [Fallback Text](#fallback-text)
- [Pluralization and Templating](#pluralization-and-templating)
- [API](#api)
- [IntlProvider](#intlprovider)
- [Localizer](#localizer)
- [Text](#text)
- [withText](#withtext)
- [intl](#intl)

<!-- /MDTOC -->

Expand Down Expand Up @@ -126,6 +121,15 @@ In addition to [`<Text>`](#Text), [`withText()`](#withText) and [`<Localizer>`](

<!-- Generated by documentation.js. Update this documentation by updating the source code. -->

#### Table of Contents

- [IntlProvider](#intlprovider)
- [Localizer](#localizer)
- [MarkupText](#markuptext)
- [Text](#text)
- [withText](#withtext)
- [intl](#intl)

### IntlProvider

`<IntlProvider>` is a nestable internationalization definition provider.
Expand All @@ -139,9 +143,9 @@ making them available to descendant components.
**Parameters**

- `props`
- `props.scope` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** Nest `definition` under a root key, and set the active scope for the tree (essentially prefixing all `<Text />` keys).
- `props.mark` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** If `true`, all `<Text>` elements will be shown with a red/green background indicating whether they have valid Intl keys. (optional, default `false`)
- `props.definition` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Merge the given definition into the current intl definition, giving the _current_ definition precedence (i.e., only adding keys, acting as defaults) (optional, default `{}`)
- `props.scope` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** Nest `definition` under a root key, and set the active scope for the tree (essentially prefixing all `<Text />` keys).
- `props.mark` **[Boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** If `true`, all `<Text>` elements will be shown with a red/green background indicating whether they have valid Intl keys. (optional, default `false`)
- `props.definition` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Merge the given definition into the current intl definition, giving the _current_ definition precedence (i.e., only adding keys, acting as defaults) (optional, default `{}`)

**Examples**

Expand All @@ -166,10 +170,10 @@ It "renders" out any `<Text />` values in its child's props.

**Parameters**

- `props` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)**
- `props.children` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Child components with props to localize.
- `context` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)**
- `context.intl` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** [internal] dictionary and scope info
- `props` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)**
- `props.children` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Child components with props to localize.
- `context` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)**
- `context.intl` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** [internal] dictionary and scope info

**Examples**

Expand All @@ -191,6 +195,53 @@ It "renders" out any `<Text />` values in its child's props.
<abbr title="Open Source Software">OSS</abbr>
```

### MarkupText

`<MarkupText>` is just like [Text](#text) but it can also contain html markup in rendered strings. It wraps its contents in a `<span>` tag.

**Parameters**

- `props` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** props
- `props.id` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** Key to look up in intl dictionary, within any parent scopes (`$scope1.$scope2.$id`)
- `props.fields` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Values to inject into template `{{fields}}` (optional, default `{}`)
- `props.plural` **[Number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)?** Integer "count", used to select plural forms
- `context` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)**
- `context.intl` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** [internal] dictionary and scope info

**Examples**

```javascript
// If there is no dictionary in context..
<MarkupText id="foo"><b>The Foo</b></MarkupText>
// ..produces the vnode:
<span><b>The Foo</b></span>
```

```javascript
// Given a dictionary and some fields..
<IntlProvider definition={{ foo:'Le Feux <b>{{bar}}</b>' }}>
<MarkupText id="foo" fields={{ bar: 'BEAR' }}>The Foo</MarkupText>
</IntlProvider>
// ..produces the vnode:
<span>Le Feux <b>BEAR</b></span>
```

```javascript
// Within a scope, both `id` and the definition are namespaced..
<IntlProvider scope="weather" definition={{ foo:'Le <a href="http://foo.com">Feux</a>' }}>
<MarkupText id="foo">The Foo</MarkupText>
</IntlProvider>
// ..produces the vnode:
<span>Le <a href="http://foo.com">Feux</a></span>
```

```javascript
// renders nothing if there is no key match and no fallback
<div><MarkupText /></div>
// ..produces the vnode:
<div/>
```

### Text

`<Text>` renders internationalized text.
Expand All @@ -203,13 +254,13 @@ When string lookup fails, renders its children as fallback text.

**Parameters**

- `props` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** props
- `props.id` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** Key to look up in intl dictionary, within any parent scopes (`$scope1.$scope2.$id`)
- `props.plural` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** Integer "count", used to select plural forms
- `props.fields` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Values to inject into template `{{fields}}` (optional, default `{}`)
- `props` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** props
- `props.id` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** Key to look up in intl dictionary, within any parent scopes (`$scope1.$scope2.$id`)
- `props.plural` **[Number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)?** Integer "count", used to select plural forms
- `props.fields` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Values to inject into template `{{fields}}` (optional, default `{}`)
- `props.children`
- `context` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)**
- `context.intl` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** [internal] dictionary and scope info
- `context` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)**
- `context.intl` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** [internal] dictionary and scope info

**Examples**

Expand Down Expand Up @@ -247,7 +298,7 @@ based on a mapping to the dictionary & scope in context.

**Parameters**

- `mapping` **([Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) \| [Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function) \| [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String))** Maps prop names to intl keys (or `<Text>` nodes).
- `mapping` **([Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object) \| [Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function) \| [String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String))** Maps prop names to intl keys (or `<Text>` nodes).

**Examples**

Expand Down Expand Up @@ -303,6 +354,6 @@ or it can take an annotation form like:
**Parameters**

- `Child`
- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** If there are two arguments, the second argument is Passed as `props` to `<IntlProvider />`
- `options` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** If there are two arguments, the second argument is Passed as `props` to `<IntlProvider />`
- `options.scope` Nest `definition` under a root key, and set the active scope for the tree (essentially prefixing all `<Text />` keys).
- `options.definition` Merge the given definition into the current intl definition, giving the _current_ definition precedence (i.e., only adding keys, acting as defaults) (optional, default `{}`)
3 changes: 2 additions & 1 deletion karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ module.exports = function(config) {
}]
],
plugins: [
['transform-react-jsx', { pragma: 'h' }]
['transform-react-jsx', { pragma: 'h' }],
'transform-object-rest-spread'
]
}
}]
Expand Down
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"babel-core": "^6.26.0",
"babel-eslint": "^7.2.3",
"babel-loader": "^7.1.2",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-plugin-transform-react-jsx": "^6.8.0",
"babel-preset-env": "^1.6.0",
"buble": "^0.15.2",
Expand All @@ -58,11 +59,11 @@
"documentation": "^5.2.2",
"documentation-theme-default": "^3.0.0-beta1",
"eslint": "^4.5.0",
"eslint-config-synacor": "^2.0.0-beta.2",
"eslint-config-synacor": "^2.1.0",
"eslint-plugin-mocha": "^4.7.0",
"eslint-plugin-react": "^7.3.0",
"gzip-size-cli": "^2.0.0",
"karma": "^1.7.1",
"karma": "^2.0.0",
"karma-chai": "^0.1.0",
"karma-chai-as-promised": "^0.1.2",
"karma-chai-sinon": "^0.1.5",
Expand All @@ -74,15 +75,15 @@
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^2.0.3",
"mkdirp": "^0.5.1",
"mocha": "^3.4.2",
"mocha": "^5.0.0",
"npm-run-all": "^4.1.1",
"preact": "^8.2.5",
"preact-jsx-chai": "^2.2.1",
"rimraf": "^2.5.4",
"rollup": "^0.45.2",
"rollup-plugin-buble": "^0.15.0",
"rollup-plugin-memory": "^2.0.0",
"sinon": "^3.2.1",
"sinon": "^4.2.2",
"sinon-chai": "^2.13.0",
"strip-json-comments-cli": "^1.0.1",
"uglify-js": "^3.0.28",
Expand Down
28 changes: 28 additions & 0 deletions src/components/highlight-i18n.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { h } from 'preact';
import delve from 'dlv';


/** Highlight/colorize the i18n'd node if `mark` is set on `intl` in context. If not, just returns `value`
*
* @private
* @param {String|VNode} value The l10n'd text/vnode to highlight or pass through
* @param {string} id The key used to lookup the value in the intl dictionary
*/
export function HighlightI18N({ value, id }, { intl }) {

if (intl && intl.mark) {
const dictionaryKey = `dictionary${intl && intl.scope ? `.${intl.scope}` : ''}.${id}`;
return h('mark', {
style: 'background: ' + (
value
? delve(intl, dictionaryKey)
? 'rgba(119,231,117,.5)' // Green = this string is fully internationalized
: 'rgba(229,226,41,.5)' // Yellow = this string does not have a value in the dictionary, but it has a fallback value
: 'rgba(228,147,51,.5)' // Red = this string has no value and no fallback
),
title: id
}, value);
}

return value ;
}
4 changes: 2 additions & 2 deletions src/components/localizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import translateMapping from '../lib/translate-mapping';
* // produces:
* <abbr title="Open Source Software">OSS</abbr>
*/
export const Localizer = ({ children }, { intl }) => {
export function Localizer({ children }, { intl }) {
let child = children && children[0];
return child && cloneElement(child, translateMapping(child.attributes, intl, true));
};
}
56 changes: 56 additions & 0 deletions src/components/markup-text.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { h } from 'preact';
import { Text } from './text';
import { Localizer } from './localizer';
import { HighlightI18N } from './highlight-i18n';

/* eslint-disable react/no-danger */

/** `<MarkupText>` is just like {@link Text} but it can also contain html markup in rendered strings. It wraps its contents in a `<span>` tag.
*
* @param {Object} props props
* @param {String} props.id Key to look up in intl dictionary, within any parent scopes (`$scope1.$scope2.$id`)
* @param {Object} [props.fields={}] Values to inject into template `{{fields}}`
* @param {Number} [props.plural] Integer "count", used to select plural forms
* @param {Object} context
* @param {Object} context.intl [internal] dictionary and scope info
*
* @example
* // If there is no dictionary in context..
* <MarkupText id="foo"><b>The Foo</b></MarkupText>
* // ..produces the vnode:
* <span><b>The Foo</b></span>
*
* @example
* // Given a dictionary and some fields..
* <IntlProvider definition={{ foo:'Le Feux <b>{{bar}}</b>' }}>
* <MarkupText id="foo" fields={{ bar: 'BEAR' }}>The Foo</MarkupText>
* </IntlProvider>
* // ..produces the vnode:
* <span>Le Feux <b>BEAR</b></span>
*
* @example
* // Within a scope, both `id` and the definition are namespaced..
* <IntlProvider scope="weather" definition={{ foo:'Le <a href="http://foo.com">Feux</a>' }}>
* <MarkupText id="foo">The Foo</MarkupText>
* </IntlProvider>
* // ..produces the vnode:
* <span>Le <a href="http://foo.com">Feux</a></span>
*
* @example
* // renders nothing if there is no key match and no fallback
* <div><MarkupText /></div>
* // ..produces the vnode:
* <div/>
*/
export function MarkupText(props) {
return (
<Localizer>
<Html html={<Text {...props} />} id={props.id} />
</Localizer>
);
}

function Html({ html, id }) {
let value = !html ? html : typeof html === 'string' ? <span dangerouslySetInnerHTML={{ __html: html }} /> : <span>{html}</span> ;
return <HighlightI18N id={id} value={value} />;
}
23 changes: 5 additions & 18 deletions src/components/text.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { h } from 'preact';
import translate from '../lib/translate';
import delve from 'dlv';
import { HighlightI18N } from './highlight-i18n';

/** `<Text>` renders internationalized text.
* It attempts to look up translated values from a dictionary in context.
Expand Down Expand Up @@ -39,7 +39,8 @@ import delve from 'dlv';
* // ..produces the text:
* "Le Feux"
*/
export const Text = ({ id, children, plural, fields }, { intl }) => {
export function Text({ id, children, plural, fields }, { intl }) {

let fallback = children && children[0];

let value = translate(
Expand All @@ -51,19 +52,5 @@ export const Text = ({ id, children, plural, fields }, { intl }) => {
fallback
);

if (intl && intl.mark) {
const dictionaryKey = `dictionary${intl && intl.scope ? `.${intl.scope}` : ''}.${id}`;
return h('mark', {
style: 'background: ' + (
value
? delve(intl, dictionaryKey)
? 'rgba(119,231,117,.5)' // Green = this string is fully internationalized
: 'rgba(229,226,41,.5)' // Yellow = this string does not have a value in the dictionary, but it has a fallback value
: 'rgba(228,147,51,.5)' // Red = this string has no value and no fallback
),
title: id
}, value);
}

return value;
};
return <HighlightI18N id={id} value={value} />;
}
4 changes: 3 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { intl } from './intl';
import { IntlProvider } from './components/intl-provider';
import { Text } from './components/text';
import { MarkupText } from './components/markup-text';
import { Localizer } from './components/localizer';
import { withText } from './components/with-text';

export { intl, IntlProvider, Text, Localizer, withText };
export { intl, IntlProvider, Text, MarkupText, Localizer, withText };

intl.intl = intl;
intl.IntlProvider = IntlProvider;
intl.Text = Text;
intl.MarkupText = MarkupText;
intl.Localizer = Localizer;
intl.withText = withText;
export default intl;
Loading

0 comments on commit 908c3f3

Please sign in to comment.