Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add transchoice #1

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .babelrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"presets": ["es2015"]
"presets": ["@babel/preset-env"]
}
4 changes: 0 additions & 4 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ module.exports = {
browser: true,
},
extends: 'airbnb-base',
// required to lint *.vue files
plugins: [
'html'
],
// check if imports actually resolve
'settings': {
'import/resolver': {
Expand Down
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
This is a simple vue filter to provide a similar way of using translations in vue as one would in
twig templates with the Symfony `trans` filter. The filter is not bound to the Symfony framework so can be used as a stand alone package as well.

Also implemented is the multiple choice syntax using a `count` parameter from Symfony, detailed more downbelow.

## Installation and configuration

Install the filter using `npm` or `yarn`:
Expand Down Expand Up @@ -89,6 +91,40 @@ const context = { versionNumber: 1 }
{{ 'app.version' | trans(context) }} // Result: "version 1"
```

## Pluralize easily by adding `count`
By adding the context parameter `count` you can add inflection or pluralize effortlessly.

```javascript
window.translations = {
'app.changes': '{0}No changes|{1} 1 change|]1,Inf[%count% changes'
};


// Somewhere in the app...
const context = { count: 1 }
{{ 'app.changes' | trans(context) }} // Result: "1 change"

// or if we increase the count
const context = { count: 5 }
{{ 'app.changes' | trans(context) }} // Result: "5 changes"
```

Combine `count` with other parameters:

```javascript
window.translations = {
'app.generic': '{0}No %item%|{1} 1 %item%|]1,Inf[%count% %items%'
};


// Somewhere in the app...
const context = { count: 1, item: "line" }
{{ 'app.generic' | trans(context) }} // Result: "1 line"

const context = { count: 5, item: "line" }
{{ 'app.generic' | trans(context) }} // Result: "5 lines"
```

## Upcoming

- Provide config to edit context pre and suffix.
Expand Down
2 changes: 1 addition & 1 deletion dist/vue-trans.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 8 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "vue-trans",
"version": "0.0.3",
"version": "0.0.4",
"description": "A filter to provide similar use of translations as with Symfony | trans",
"main": "dist/vue-trans.js",
"scripts": {
Expand All @@ -24,20 +24,17 @@
"url": "https://github.com/Trekels/vue-trans/issues"
},
"devDependencies": {
"babel-core": "^6.26.0",
"babel-eslint": "^8.0.1",
"babel-loader": "^7.1.2",
"babel-preset-es2015": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
"babel-register": "^6.26.0",
"@babel/core": "^7.6.0",
"@babel/preset-env": "^7.6.0",
"babel-eslint": "^10.0.3",
"babel-jest": "^24.9.0",
"babel-loader": "^8.0.6",
"eslint": "^4.9.0",
"eslint-config-airbnb-base": "^12.1.0",
"eslint-friendly-formatter": "^3.0.0",
"eslint-loader": "^1.9.0",
"eslint-plugin-html": "^3.2.2",
"eslint-plugin-import": "^2.8.0",
"jest": "^21.2.1",
"jest": "^25.0.0",
"webpack": "^3.8.1"
},
"dependencies": {}
}
}
81 changes: 71 additions & 10 deletions src/filter.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
export default (key, context) => {
let translation = key;

if (window.translations !== undefined && window.translations[key] !== undefined) {
translation = window.translations[key];
}

const trans = (translation, context) => {
let result = translation;
if (typeof context === 'object') {
const matches = translation.match(/(%([^%]|%%)*%)/g);
const matches = result.match(/(%([^%]|%%)*%)/g);

if (matches) {
matches.forEach((match) => {
Expand All @@ -16,10 +11,76 @@ export default (key, context) => {
}

const regex = new RegExp(match, 'g');
translation = translation.replace(regex, context[prop]);
result = result.replace(regex, context[prop]);
});
}
}

return translation;
return result;
};

// match groups for `rangePattern`
// match 0: the entire translation string
// match 1: open or closed range ] or [ i.e. exlusive or inclusive start
// match 2: value of range start
// match 3: value of range end
// match 4: open or closed range ] or [ i.e. inclusive or exlusive end
// match 5: the contents of the template (i.e. exclude type preamble)
const rangePattern = /(\[|\])(\d+|Inf), ?(\d+|Inf)(\[|\])(.+)/;
const exactPattern = /\{(\d+)\}(.*)/;

// a typical multiple choice pattern looks like:
// '{0} no apples|{1} one apple|]1, Inf] A lot of apples'
const transchoice = (translation, context) => {
let result = translation;
// split choice template on delimiter `|`
const templates = result.split('|');
templates.forEach((t) => {
const choice = parseInt(context.count, 10);
// for each choice pattern, determine the type: exact or range
const exactMatch = t.match(exactPattern);
if (exactMatch) {
// we found the correct exact match
if (parseInt(exactMatch[1], 10) === choice) {
// eslint-disable-next-line prefer-destructuring
result = exactMatch[2];
}
} else {
const rangeMatch = t.match(rangePattern);
// there was no pattern
if (!rangeMatch) { return; }
let start = rangeMatch[2];
let end = rangeMatch[3];
if (start !== 'Inf') start = parseInt(start, 10);
if (end !== 'Inf') end = parseInt(end, 10);

// starting range excludes the start value
if (rangeMatch[1] === ']' && typeof start === 'number') {
start += 1;
}
// ending range excludes the end value
if (rangeMatch[4] === '[' && typeof end === 'number') {
end -= 1;
}
// the passed in value was within the range
if (choice >= start && (choice <= end || end === 'Inf')) {
// eslint-disable-next-line prefer-destructuring
result = rangeMatch[5];
}
}
});
return trans(result, context);
};

export default (key, context) => {
let translation = key;

if (window.translations !== undefined && window.translations[key] !== undefined) {
translation = window.translations[key];
}

if (context && typeof context === 'object' && Object.prototype.hasOwnProperty.call(context, 'count')) {
return transchoice(translation, context);
}
return trans(translation, context);
};
14 changes: 14 additions & 0 deletions test/trans.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ describe('Global translations object provided', () => {
'context_key': 'context: %value%',
'multiple_context_keys': 'context: %value% %value_two%',
'multiple_similar_keys': 'context: %value% %value%',
'multiple_choice': '{0}no apples|{1}one apple|]1, Inf]%count% apples',
'multiple_choice_with_param': '{0}no %param%|{1}one %param%|]1, Inf]%count% %param%s'
};
});

Expand Down Expand Up @@ -56,5 +58,17 @@ describe('Global translations object provided', () => {
expect(trans('multiple_similar_keys', { value: 'test' }))
.toBe('context: test test');
});
it('Should be able to parse multiple choices', () => {
expect(trans('multiple_choice', { count: '1' }))
.toBe('one apple');
expect(trans('multiple_choice', { count: '0' }))
.toBe('no apples');
expect(trans('multiple_choice', { count: '3' }))
.toBe('3 apples');
});
it('Should be able to parse multiple choices with context parameters', () => {
expect(trans('multiple_choice_with_param', { count: '3', param: 'pear' }))
.toBe('3 pears');
});
});
});
31 changes: 16 additions & 15 deletions webpack.config.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
let path = require('path');
let webpack = require('webpack');
const path = require('path');
const webpack = require('webpack');
const friendlyFormatter = require('eslint-friendly-formatter');

module.exports = {
context: __dirname,
entry: {
'vue-trans': './src/index.js'
'vue-trans': './src/index.js',
},
output: {
path: path.join(__dirname, './dist'),
filename: '[name].js',
library: 'vue-trans',
libraryTarget: 'umd'
libraryTarget: 'umd',
},
module: {
rules: [
Expand All @@ -20,33 +21,33 @@ module.exports = {
use: {
loader: 'babel-loader',
options: {
presets: ['es2015', 'stage-0']
}
}
presets: ['@babel/preset-env'],
},
},
},
{
test: /\.(js)$/,
loader: 'eslint-loader',
enforce: 'pre',
options: {
formatter: require('eslint-friendly-formatter')
}
formatter: friendlyFormatter,
},
},
]
],
},
plugins: [
new webpack.optimize.UglifyJsPlugin({
beautify: false,
mangle: {
screw_ie8: true,
keep_fnames: true
keep_fnames: true,
},
compress: {
screw_ie8: true,
warnings: false
warnings: false,
},
comments: false,
sourceMap: true
})
]
sourceMap: true,
}),
],
};
Loading