Skip to content

Commit

Permalink
Use functional react components (#25)
Browse files Browse the repository at this point in the history
This also fixes a case where `original` got stuck tot the old value in
case of a Translate element being updated to a new set of children, and
thus picking the wrong translation.
  • Loading branch information
ThiefMaster authored Aug 8, 2024
1 parent 89931de commit e6da7ab
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 124 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-jsx-i18n",
"version": "0.5.1",
"version": "0.6.0",
"description": "Provides gettext-enhanced React components, a babel plugin for extracting the strings and a script to compile translated strings to a format usable by the components.",
"main": "client/index.js",
"types": "client/index.d.ts",
Expand Down
226 changes: 103 additions & 123 deletions src/client/index.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,23 @@
import React from 'react';
import React, {useMemo} from 'react';
import PropTypes from 'prop-types';

export class Param extends React.Component {
static propTypes = {
// eslint-disable-next-line react/no-unused-prop-types
name: PropTypes.string.isRequired,
value: PropTypes.any,
wrapper: PropTypes.element,
children: PropTypes.string,
};
export function Param({wrapper, value, children}) {
return wrapper ? React.cloneElement(wrapper, {}, value !== undefined ? value : children) : value;
}

static defaultProps = {
value: undefined,
wrapper: undefined,
children: undefined,
};
Param.propTypes = {
// eslint-disable-next-line react/no-unused-prop-types
name: PropTypes.string.isRequired,
value: PropTypes.any,
wrapper: PropTypes.element,
children: PropTypes.string,
};

render() {
const {wrapper, value, children} = this.props;
return wrapper
? React.cloneElement(wrapper, {}, value !== undefined ? value : children)
: value;
}
}
Param.defaultProps = {
value: undefined,
wrapper: undefined,
children: undefined,
};

const collapseWhitespace = string => {
// for translated strings we never want consecutive or surrounding whitespace
Expand Down Expand Up @@ -120,28 +115,22 @@ const renderStringTranslation = (translation, values) => {
}
};

export class Singular extends React.Component {
static propTypes = {
children: PropTypes.any.isRequired,
};

render() {
const {children} = this.props;
return children;
}
export function Singular({children}) {
return children;
}

export class Plural extends React.Component {
static propTypes = {
children: PropTypes.any.isRequired,
};
Singular.propTypes = {
children: PropTypes.any.isRequired,
};

render() {
const {children} = this.props;
return children;
}
export function Plural({children}) {
return children;
}

Plural.propTypes = {
children: PropTypes.any.isRequired,
};

const getGettextFuncs = jedInstance => {
const gettext = jedInstance.gettext.bind(jedInstance);
const ngettext = jedInstance.ngettext.bind(jedInstance);
Expand Down Expand Up @@ -210,85 +199,57 @@ export const makeComponents = jedInstance => {
const {gettext, ngettext, pgettext, npgettext} = getGettextFuncs(jedInstance);
const nPlurals = getNPlurals(jedInstance);

class Translate extends React.Component {
static propTypes = {
children: PropTypes.any.isRequired,
context: PropTypes.string,
as: PropTypes.elementType,
};

static defaultProps = {
context: undefined,
as: React.Fragment,
};

// eslint-disable-next-line no-shadow, react/sort-comp
static string(string, ...args) {
const {context, params} = getContextParams(args);
const gettextFunc = pickGettextFunc(context, gettext, pgettext);
return renderStringTranslation(gettextFunc(collapseWhitespace(string)), params);
}
function Translate({children, context, as, ...rest}) {
const original = useMemo(() => getTranslatableString(children), [children]);

constructor(props) {
super(props);
this.original = getTranslatableString(props.children);
}

render() {
const {children, context, as, ...rest} = this.props;
const gettextFunc = pickGettextFunc(context, gettext, pgettext);
const translation = gettextFunc(this.original);
let content = children;
if (translation !== this.original) {
content = renderTranslation(translation, getParamValues(this));
}
// if there's no translation gettext gives us the input string
// which does not contain the information needed to render it!
// unfortunately this means that we also cannot strip surrounding
// whitespace since we may have more than just text in the children,
// which is why we fail during extraction in that case
return React.createElement(as, rest, content);
const gettextFunc = pickGettextFunc(context, gettext, pgettext);
const translation = gettextFunc(original);
let content = children;
if (translation !== original) {
content = renderTranslation(translation, getParamValues({props: {children}}));
}
// if there's no translation gettext gives us the input string
// which does not contain the information needed to render it!
// unfortunately this means that we also cannot strip surrounding
// whitespace since we may have more than just text in the children,
// which is why we fail during extraction in that case
return React.createElement(as, rest, content);
}

class PluralTranslate extends React.Component {
static propTypes = {
children: PropTypes.any.isRequired,
count: PropTypes.number.isRequired,
context: PropTypes.string,
as: PropTypes.elementType,
};

static defaultProps = {
context: undefined,
as: React.Fragment,
};

// eslint-disable-next-line no-shadow
static string(singular, plural, count = 1, ...args) {
const {context, params} = getContextParams(args);
const gettextFunc = pickGettextFunc(context, ngettext, npgettext);
return renderStringTranslation(
gettextFunc(collapseWhitespace(singular), collapseWhitespace(plural), count),
params
);
}
Translate.string = function string(original, ...args) {
const {context, params} = getContextParams(args);
const gettextFunc = pickGettextFunc(context, gettext, pgettext);
return renderStringTranslation(gettextFunc(collapseWhitespace(original)), params);
};

Translate.propTypes = {
children: PropTypes.any.isRequired,
context: PropTypes.string,
as: PropTypes.elementType,
};

Translate.defaultProps = {
context: undefined,
as: React.Fragment,
};

constructor(props) {
super(props);
React.Children.forEach(props.children, child => {
function PluralTranslate({children, count, context, as, ...rest}) {
const [singularString, pluralString] = useMemo(() => {
let singular, plural;
React.Children.forEach(children, child => {
if (!React.isValidElement(child)) {
throw new Error(`Unexpected PluralTranslate child: ${child}`);
} else if (child.type === Singular) {
this.singularString = getTranslatableString(child.props.children);
singular = getTranslatableString(child.props.children);
} else if (child.type === Plural) {
this.pluralString = getTranslatableString(child.props.children);
plural = getTranslatableString(child.props.children);
}
});
}

getChild(plural) {
const {children} = this.props;
return [singular, plural];
}, [children]);

function getChild(plural) {
const component = plural ? Plural : Singular;
for (const child of React.Children.toArray(children)) {
if (React.isValidElement(child) && child.type === component) {
Expand All @@ -297,26 +258,45 @@ export const makeComponents = jedInstance => {
}
}

render() {
const {count, context, as, ...rest} = this.props;
const gettextFunc = pickGettextFunc(context, ngettext, npgettext);
const translation = gettextFunc(this.singularString, this.pluralString, count);
let content;
if (translation === this.singularString) {
content = this.getChild(false);
} else if (translation === this.pluralString) {
content = this.getChild(true);
} else {
// For languages with only one plural, the plural translation is
// used even if count == 1. In that case the corresponding <Plural>
// component must be rendered.
const plural = nPlurals === 1 || count !== 1;
const values = getParamValues(this.getChild(plural));
content = renderTranslation(translation, values);
}
return React.createElement(as, rest, content);
const gettextFunc = pickGettextFunc(context, ngettext, npgettext);
const translation = gettextFunc(singularString, pluralString, count);
let content;
if (translation === singularString) {
content = getChild(false);
} else if (translation === pluralString) {
content = getChild(true);
} else {
// For languages with only one plural, the plural translation is
// used even if count == 1. In that case the corresponding <Plural>
// component must be rendered.
const plural = nPlurals === 1 || count !== 1;
const values = getParamValues(getChild(plural));
content = renderTranslation(translation, values);
}
return React.createElement(as, rest, content);
}

PluralTranslate.propTypes = {
children: PropTypes.any.isRequired,
count: PropTypes.number.isRequired,
context: PropTypes.string,
as: PropTypes.elementType,
};

PluralTranslate.defaultProps = {
context: undefined,
as: React.Fragment,
};

// eslint-disable-next-line no-shadow
PluralTranslate.string = function string(singular, plural, count = 1, ...args) {
const {context, params} = getContextParams(args);
const gettextFunc = pickGettextFunc(context, ngettext, npgettext);
return renderStringTranslation(
gettextFunc(collapseWhitespace(singular), collapseWhitespace(plural), count),
params
);
};

return {Translate, PluralTranslate};
};

0 comments on commit e6da7ab

Please sign in to comment.