Skip to content

Commit

Permalink
Feat($plural): support for param interpolation (#32)
Browse files Browse the repository at this point in the history
  • Loading branch information
robisim74 committed Mar 14, 2023
1 parent c625af7 commit 1833026
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 40 deletions.
10 changes: 6 additions & 4 deletions packages/qwik-speak/src/plural.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@ import { $translate } from './translate';
* Get the plural by a number.
* The value is passed as a parameter to the translate function
* @param value A number or a string
* @param prefix Optional prefix for the key
* @param key Optional key
* @param params Optional parameters contained in the values
* @param options Intl PluralRulesOptions object
* @param ctx Optional Speak context to be provided outside the component$
* @param lang Optional language if different from the current one
* @returns The translation for the plural
*/
export const $plural = (
value: number | string,
prefix?: string,
key?: string,
params?: any,
options?: Intl.PluralRulesOptions,
ctx?: SpeakState,
lang?: string
Expand All @@ -27,7 +29,7 @@ export const $plural = (
value = +value;

const rule = new Intl.PluralRules(lang, options).select(value);
const key = prefix ? `${prefix}${config.keySeparator}${rule}` : rule;
key = key ? `${key}${config.keySeparator}${rule}` : rule;

return $translate(key, { value }, ctx, lang);
return $translate(key, { value, ...params }, ctx, lang);
};
4 changes: 2 additions & 2 deletions packages/qwik-speak/src/tests/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ const translationData: Translation = {
test: 'Test',
array: ['Test1', 'Test2'],
},
one: 'One software developer',
other: '{{value}} software developers',
one: 'One {{ role }} developer',
other: '{{value}} {{ role }} developers',
arrayObjects: [
{ one: '1' },
{ two: '3' }
Expand Down
4 changes: 2 additions & 2 deletions packages/qwik-speak/src/tests/plural.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import { ctx } from './config';

describe('plural function', () => {
test('one', () => {
const value = p(1, '', {}, ctx);
const value = p(1, '', { role: 'software' }, {}, ctx);
expect(value).toBe('One software developer');
});
test('other', () => {
const value = p(2, '', {}, ctx);
const value = p(2, '', { role: 'software' }, {}, ctx);
expect(value).toBe('2 software developers');
});
});
43 changes: 30 additions & 13 deletions packages/qwik-speak/tools/inline/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export function transformPlural(
if (args?.length > 0) {
if (checkDynamicPlural(args, originalFn)) continue;

const { defaultLang, supportedLangs } = withLang(args[4], opts);
const { defaultLang, supportedLangs } = withLang(args[5], opts);

// Map of rules
const rules = new Map<string, string[]>();
Expand Down Expand Up @@ -397,7 +397,11 @@ export function quoteValue(value: string): string {
}

export function interpolateParam(property: Property): string {
return property.value.type === 'Literal' ? "${'" + property.value.value + "'}" : '${' + property.value.value + '}';
return property.value.type === 'Literal' ? property.value.value : '${' + property.value.value + '}';
}

export function strParam(property: Property): string {
return property.value.type === 'Literal' ? quoteValue(property.value.value) : property.value.value;
}

export function getKey(key: string, keyValueSeparator: string): string {
Expand Down Expand Up @@ -459,7 +463,7 @@ export function transpileFn(
for (const lang of supportedLangs.filter(x => x !== defaultLang)) {
const value = values.get(lang);
// Enclose in round brackets
if (!translation && typeof value === 'object') translation += '(';
if (!translation) translation += '(';
if (typeof value === 'object') {
translation += `$lang(${quoteValue(lang)}) && ${stringifyObject(value)} || `;
} else {
Expand All @@ -473,7 +477,7 @@ export function transpileFn(
translation += defaultValue;
}
// Enclose in round brackets
if (supportedLangs.filter(x => x !== defaultLang).length > 0 && typeof defaultValue === 'object') translation += ')';
if (supportedLangs.filter(x => x !== defaultLang).length > 0) translation += ')';

return translation;
}
Expand Down Expand Up @@ -502,20 +506,29 @@ export function transpilePluralFn(
const rulesBylang = rules.get(lang);
if (rulesBylang) {
for (const rule of rulesBylang) {
const prefix = args[1]?.value;
const key = prefix ? `${prefix}${opts.keySeparator}${rule}` : rule;
let key = args[1]?.value;
key = key ? `${key}${opts.keySeparator}${rule}` : rule;

// Params
const params: Property[] = [{
type: 'Property',
key: { type: 'Identifier', value: 'value' },
value: { type: 'Identifier', value: args[0].value! }
}];
if (args[2]?.properties) {
args[2].properties.forEach(p => params.push(p));
}
const strParams = params.map(p => `${p.key.value}: ${strParam(p)}`).join(', ');

if (rule !== rulesBylang[rulesBylang.length - 1]) {
if (args[2]?.properties) {
const options = args[2].properties.map(p => `${p.key.value}: ${quoteValue(p.value.value)}`).join(', ');
expr += `$rule(${quoteValue(lang)}, ${args[0].value}, ${quoteValue(rule)}, {${options}}) &&
${translateAlias}(${quoteValue(key)}, { value: ${args[0].value}}, ${args[3]?.value}, ${quoteValue(lang)}) || `;
if (args[3]?.properties) {
const strOptions = args[3].properties.map(p => `${p.key.value}: ${strParam(p)}`).join(', ');
expr += `$rule(${quoteValue(lang)}, ${args[0].value}, ${quoteValue(rule)}, {${strOptions}}) && ${translateAlias}(${quoteValue(key)}, {${strParams}}, ${args[4]?.value}, ${quoteValue(lang)}) || `;
} else {
expr += `$rule(${quoteValue(lang)}, ${args[0].value}, ${quoteValue(rule)}) &&
${translateAlias}(${quoteValue(key)}, { value: ${args[0].value}}, ${args[3]?.value}, ${quoteValue(lang)}) || `;
expr += `$rule(${quoteValue(lang)}, ${args[0].value}, ${quoteValue(rule)}) && ${translateAlias}(${quoteValue(key)}, {${strParams}}, ${args[4]?.value}, ${quoteValue(lang)}) || `;
}
} else {
expr += `${translateAlias}(${quoteValue(key)}, { value: ${args[0].value}}, ${args[3]?.value}, ${quoteValue(lang)})`;
expr += `${translateAlias}(${quoteValue(key)}, {${strParams}}, ${args[4]?.value}, ${quoteValue(lang)})`;
}
}
}
Expand All @@ -524,12 +537,16 @@ export function transpilePluralFn(
}

for (const lang of supportedLangs.filter(x => x !== defaultLang)) {
// Enclose in round brackets
if (!translation) translation += '(';
translation += `$lang(${quoteValue(lang)}) && `;
translation += transpileRules(lang);
translation += ' || ';
}

translation += transpileRules(defaultLang);
// Enclose in round brackets
if (supportedLangs.filter(x => x !== defaultLang).length > 0) translation += ')';
return translation;
}

Expand Down
70 changes: 64 additions & 6 deletions packages/qwik-speak/tools/tests/inline.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { test, describe, expect } from 'vitest';

import type { Translation } from '../core/types';
import { getKey, getValue, qwikSpeakInline, transpileFn, addLang } from '../inline/plugin';
import { getRules } from '../core/intl-parser';
import { getKey, getValue, qwikSpeakInline, transpileFn, addLang, transpilePluralFn } from '../inline/plugin';
import { inlinedCode, mockCode } from './mock';

describe('inline', () => {
Expand All @@ -27,7 +28,7 @@ describe('inline', () => {
}
]
}, '.');
expect(value).toBe("`Key1 ${'Param1'}`");
expect(value).toBe('`Key1 Param1`');
value = getValue('key1', { key1: 'Key1 {{param1}} and {{param2}}' },
{
type: 'ObjectExpression', properties: [
Expand All @@ -43,7 +44,7 @@ describe('inline', () => {
}
]
}, '.');
expect(value).toBe("`Key1 ${'Param1'} and ${variable}`");
expect(value).toBe('`Key1 Param1 and ${variable}`');
value = getValue('key1', { key1: 'Key1' }, {
type: 'ObjectExpression', properties: [
{
Expand All @@ -64,13 +65,24 @@ describe('inline', () => {
]
}, '.');
expect(value).toBe('`Key1 {{param1}}`');
value = getValue('key1', { key1: 'Key1 {{param1}}' },
{
type: 'ObjectExpression', properties: [
{
type: 'Property',
key: { type: 'Identifier', value: 'param1' },
value: { type: 'Literal', value: 'Param1 ${variable}' }
}
]
}, '.');
expect(value).toBe('`Key1 Param1 ${variable}`');
});
test('transpileFn', () => {
let values = new Map<string, string>();
values.set('en-US', '`Value`');
values.set('it-IT', '`Valore`');
let line = transpileFn(values, ['en-US', 'it-IT'], 'en-US');
expect(line).toBe('$lang(`it-IT`) && `Valore` || `Value`');
expect(line).toBe('($lang(`it-IT`) && `Valore` || `Value`)');
values = new Map<string, string>();
values.set('en-US', '`Value`');
line = transpileFn(values, ['en-US'], 'en-US');
Expand All @@ -95,20 +107,66 @@ describe('inline', () => {
const line = transpileFn(values, ['en-US', 'it-IT'], 'en-US');
expect(line).toBe('($lang(`it-IT`) && {"value1":"Valore1"} || {"value1":"Value1"})');
});
test('transpileFn with objects multiple languages', () => {
test('transpileFn with objects - multiple languages', () => {
const values = new Map<string, Translation>();
values.set('en-US', { value1: 'Value1' });
values.set('it-IT', { value1: 'Valore1' });
values.set('es-ES', { value1: 'Valor1' });
const line = transpileFn(values, ['en-US', 'it-IT', 'es-ES'], 'en-US');
expect(line).toBe('($lang(`it-IT`) && {"value1":"Valore1"} || $lang(`es-ES`) && {"value1":"Valor1"} || {"value1":"Value1"})');
});
test('transpileFn with objects one language', () => {
test('transpileFn with objects - one language', () => {
const values = new Map<string, Translation>();
values.set('en-US', { value1: 'Value1' });
const line = transpileFn(values, ['en-US'], 'en-US');
expect(line).toBe('{"value1":"Value1"}');
});
test('transpilePluralFn', () => {
const supportedLangs = ['en-US'];
const rules = new Map<string, string[]>();
for (const lang of supportedLangs) {
const rulesByLang = getRules(lang);
rules.set(lang, [...rulesByLang]);
}
const line = transpilePluralFn(rules, supportedLangs, 'en-US', '$translate',
[
{ type: 'Identifier', value: 'state.count' },
{ type: 'Literal', value: 'home.devs' }
],
{ keySeparator: '.' } as any
);
expect(line).toBe('($rule(`en-US`, state.count, `other`) && $translate(`home.devs.other`, {value: state.count}, undefined, `en-US`) || $translate(`home.devs.one`, {value: state.count}, undefined, `en-US`))');
});
test('transpilePluralFn with params and options', () => {
const supportedLangs = ['en-US'];
const rules = new Map<string, string[]>();
for (const lang of supportedLangs) {
const rulesByLang = getRules(lang);
rules.set(lang, [...rulesByLang]);
}
const line = transpilePluralFn(rules, supportedLangs, 'en-US', 't',
[
{ type: 'Identifier', value: 'state.count' },
{ type: 'Literal', value: 'home.devs' },
{
type: 'ObjectExpression', properties: [{
type: 'Property',
key: { type: 'Identifier', value: 'role' },
value: { type: 'Literal', value: 'software' }
}]
},
{
type: 'ObjectExpression', properties: [{
type: 'Property',
key: { type: 'Identifier', value: 'type' },
value: { type: 'Literal', value: 'cardinal' }
}]
}
],
{ keySeparator: '.' } as any
);
expect(line).toBe('($rule(`en-US`, state.count, `other`, {type: `cardinal`}) && t(`home.devs.other`, {value: state.count, role: `software`}, undefined, `en-US`) || t(`home.devs.one`, {value: state.count, role: `software`}, undefined, `en-US`))');
});
test('addLang', () => {
const code = addLang(`import { useStore } from "@builder.io/qwik";
export const s_xJBzwgVGKaQ = ()=>{
Expand Down
24 changes: 11 additions & 13 deletions packages/qwik-speak/tools/tests/mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,39 +103,37 @@ export const s_xJBzwgVGKaQ = ()=>{
return /*#__PURE__*/ _jsxs(_Fragment, {
children: [
/*#__PURE__*/ _jsx("h1", {
children: $lang(\`it-IT\`) && \`Qwik Speak\` || \`Qwik Speak\`
children: ($lang(\`it-IT\`) && \`Qwik Speak\` || \`Qwik Speak\`)
}),
/*#__PURE__*/ _jsx("h2", {
children: $lang(\`it-IT\`) && \`Traduci le tue app Qwik in qualsiasi lingua\` || \`Translate your Qwik apps into any language\`
children: ($lang(\`it-IT\`) && \`Traduci le tue app Qwik in qualsiasi lingua\` || \`Translate your Qwik apps into any language\`)
}),
/*#__PURE__*/ _jsx("h3", {
children: $lang(\`it-IT\`) && \`Parametri\` || \`Parameters\`
children: ($lang(\`it-IT\`) && \`Parametri\` || \`Parameters\`)
}),
/*#__PURE__*/ _jsx("p", {
children: $lang(\`it-IT\`) && \`Ciao! Sono \${'Qwik Speak'}\` || \`Hi! I am \${'Qwik Speak'}\`
children: ($lang(\`it-IT\`) && \`Ciao! Sono Qwik Speak\` || \`Hi! I am Qwik Speak\`)
}),
/*#__PURE__*/ _jsx("h3", {
children: $lang(\`it-IT\`) && \`Tag Html\` || \`Html tags\`
children: ($lang(\`it-IT\`) && \`Tag Html\` || \`Html tags\`)
}),
/*#__PURE__*/ _jsx("p", {
dangerouslySetInnerHTML: $lang(\`it-IT\`) && \`<em>Libreria di internazionalizzazione (i18n) per tradurre testi, date e numeri nelle app Qwik</em>\` || \`<em>Internationalization (i18n) library to translate texts, dates and numbers in Qwik apps</em>\`
dangerouslySetInnerHTML: ($lang(\`it-IT\`) && \`<em>Libreria di internazionalizzazione (i18n) per tradurre testi, date e numeri nelle app Qwik</em>\` || \`<em>Internationalization (i18n) library to translate texts, dates and numbers in Qwik apps</em>\`)
}),
/*#__PURE__*/ _jsx("h3", {
children: $lang(\`it-IT\`) && \`Plurale\` || \`Plural\`
children: ($lang(\`it-IT\`) && \`Plurale\` || \`Plural\`)
}),
/*#__PURE__*/ _jsx("button", {
onClick$: qrl(()=>import("./entry_Home.js"), "s_d7QwW4Vfl2A", [
state
]),
children: $lang(\`it-IT\`) && \`Incrementa\` || \`Increment\`
children: ($lang(\`it-IT\`) && \`Incrementa\` || \`Increment\`)
}),
/*#__PURE__*/ _jsx("p", {
children: $lang(\`it-IT\`) && ($rule(\`it-IT\`, state.count, \`other\`) &&
\`\${state.count} sviluppatori software\` || \`\${state.count} sviluppatore software\`) || ($rule(\`en-US\`, state.count, \`other\`) &&
\`\${state.count} software developers\` || \`\${state.count} software developer\`)
children: ($lang(\`it-IT\`) && ($rule(\`it-IT\`, state.count, \`other\`) && \`\${state.count} sviluppatori software\` || \`\${state.count} sviluppatore software\`) || ($rule(\`en-US\`, state.count, \`other\`) && \`\${state.count} software developers\` || \`\${state.count} software developer\`))
}),
/*#__PURE__*/ _jsx("h3", {
children: $lang(\`it-IT\`) && \`Date e tempo relativo\` || \`Dates & relative time\`
children: ($lang(\`it-IT\`) && \`Date e tempo relativo\` || \`Dates & relative time\`)
}),
/*#__PURE__*/ _jsx("p", {
children: fd(Date.now(), {
Expand All @@ -147,7 +145,7 @@ export const s_xJBzwgVGKaQ = ()=>{
children: rt(-1, 'second')
}),
/*#__PURE__*/ _jsx("h3", {
children: $lang(\`it-IT\`) && \`Numeri e valute\` || \`Numbers & currencies\`
children: ($lang(\`it-IT\`) && \`Numeri e valute\` || \`Numbers & currencies\`)
}),
/*#__PURE__*/ _jsx("p", {
children: fn(1000000)
Expand Down

0 comments on commit 1833026

Please sign in to comment.