Skip to content

Commit

Permalink
Merge pull request #223 from ever-co/develop
Browse files Browse the repository at this point in the history
Release v0.19.0
  • Loading branch information
evereq committed May 23, 2021
2 parents aa4e725 + 7871c00 commit 70e8b13
Show file tree
Hide file tree
Showing 18 changed files with 9,935 additions and 36 deletions.
11 changes: 11 additions & 0 deletions CREDITS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# CREDITS

## Components, Libraries, Frameworks, Packages

This application uses Open Source components and 3rd party libraries, which are licensed under their own respective Open-Source licenses.
You can find the links to source code of their open source projects along with license information below.
We acknowledge and are grateful to these developers for their contributions to open source.

- [Nest](https://github.com/nestjs/nest), progressive Node.js framework, released under [MIT](https://github.com/nestjs/nest/blob/master/LICENSE), `Copyright (c) 2017 Kamil Myśliwiec` <http://kamilmysliwiec.com>

- [docker-compose-wait](https://github.com/ufoscout/docker-compose-wait), A small command-line utility to wait for other docker images to be started while using docker-compose. Released under [Apache-2.0 License](https://github.com/ufoscout/docker-compose-wait/blob/master/LICENSE).
9,580 changes: 9,580 additions & 0 deletions api/package-lock.json

Large diffs are not rendered by default.

32 changes: 16 additions & 16 deletions api/package.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
{
"name": "ever-traduora-api",
"version": "0.18.0",
"version": "0.19.0",
"license": "AGPL-3.0-only",
"homepage": "https://traduora.com",
"repository": {
"type": "git",
"url": "git+https://github.com/ever-co/ever-traduora.git"
},
"bugs": {
"url": "https://github.com/ever-co/ever-traduora/issues"
},
"type": "git",
"url": "git+https://github.com/ever-co/ever-traduora.git"
},
"bugs": {
"url": "https://github.com/ever-co/ever-traduora/issues"
},
"private": true,
"author": {
"name": "Ever Co. LTD",
"email": "[email protected]",
"url": "https://ever.co"
},
"name": "Ever Co. LTD",
"email": "[email protected]",
"url": "https://ever.co"
},
"scripts": {
"fmt": "prettier --write \"src/**/*.ts\"",
"start": "cross-env TR_MAIL_DEBUG=true TR_CORS_ENABLED=true nodemon",
Expand All @@ -29,7 +29,7 @@
"typeorm": "ts-node node_modules/.bin/typeorm"
},
"dependencies": {
"morgan": "^1.10.0",
"morgan": "^1.10.0",
"@nestjs/common": "^7.6.15",
"@nestjs/core": "^7.6.15",
"@nestjs/jwt": "^7.2.0",
Expand Down Expand Up @@ -112,8 +112,8 @@
"testEnvironment": "node"
},
"engines": {
"node": ">=14.0.0",
"yarn": ">=1.13.0"
},
"snyk": true
"node": ">=14.0.0",
"yarn": ">=1.13.0"
},
"snyk": true
}
3 changes: 3 additions & 0 deletions api/src/controllers/exports.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { stringsExporter } from '../formatters/strings';
import { phpExporter } from '../formatters/php';
import { ApiOAuth2, ApiTags, ApiOperation, ApiProduces, ApiResponse } from '@nestjs/swagger';
import { androidXmlExporter } from '../formatters/android-xml';
import { resXExporter } from '../formatters/resx';

@Controller('api/v1/projects/:projectId/exports')
export class ExportsController {
Expand Down Expand Up @@ -111,6 +112,8 @@ export class ExportsController {
return await stringsExporter(data);
case 'php':
return await phpExporter(data);
case 'resx':
return await resXExporter(data);
default:
throw new Error('Export format not implemented');
}
Expand Down
3 changes: 3 additions & 0 deletions api/src/controllers/import.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { ApiOAuth2, ApiConsumes, ApiResponse, ApiOperation, ApiTags } from '@nes
import { androidXmlParser } from '../formatters/android-xml';
import { phpParser } from '../formatters/php';
import { ApiFile } from './../decorators/api-file.decorator';
import { resXParser } from '../formatters/resx';

@Controller('api/v1/projects/:projectId/imports')
@ApiTags('Imports')
Expand Down Expand Up @@ -201,6 +202,8 @@ export class ImportController {
return await stringsParser(contents);
case 'php':
return await phpParser(contents);
case 'resx':
return await resXParser(contents);
default:
throw new Error('Export format not implemented');
}
Expand Down
1 change: 1 addition & 0 deletions api/src/domain/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,7 @@ export enum ImportExportFormat {
Gettext = 'po',
Strings = 'strings',
Php = 'php',
ResX = 'resx',
}

export class ExportQuery {
Expand Down
27 changes: 27 additions & 0 deletions api/src/formatters/fixtures/resx-in.resx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="term.one" xml:space="preserve">
<value>Current Plan: {{ project.plan.name }}</value>
</data>
<data name="term two" xml:space="preserve">
<value>{VAR_PLURAL, plural, =0 {locales} =1 {locale} other {locales} }</value>
</data>
<data name="TERM_THREE" xml:space="preserve">
<value>Export format...</value>
</data>
<data name="term:four" xml:space="preserve">
<value>hello there you\nthis should be in a newline</value>
</data>
</root>
73 changes: 73 additions & 0 deletions api/src/formatters/fixtures/simple-resx.resx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="term.one" xml:space="preserve">
<value>Current Plan: {{ project.plan.name }}</value>
</data>
<data name="term two" xml:space="preserve">
<value>{VAR_PLURAL, plural, =0 {locales} =1 {locale} other {locales} }</value>
</data>
<data name="TERM_THREE" xml:space="preserve">
<value>Export format...</value>
</data>
<data name="term:four" xml:space="preserve">
<value>hello there you\nthis should be in a newline</value>
</data>
</root>
14 changes: 14 additions & 0 deletions api/src/formatters/resx-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { loadFixture, simpleFormatFixture } from './fixtures';
import { resXExporter, resXParser } from './resx';

test('should parse resx resources files', async () => {
const input = loadFixture('resx-in.resx');
const result = await resXParser(input);
expect(result).toEqual(simpleFormatFixture);
});

test('should export resx resources files', async () => {
const result = await resXExporter({ ...simpleFormatFixture, iso: 'de_DE' });
const expected = loadFixture('simple-resx.resx');
expect(result).toEqual(expected);
});
137 changes: 137 additions & 0 deletions api/src/formatters/resx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import * as xmlJs from 'xml-js';
import { Exporter, IntermediateTranslation, IntermediateTranslationFormat, Parser } from '../domain/formatters';

export const resXParser: Parser = async (data: string) => {
const xml = xmlJs.xml2js(data);

const result: IntermediateTranslation[] = [];

for (const element of xml.elements) {
if (element.type === 'element' && element.name === 'root') {
for (const resource of element.elements) {
if (resource.type === 'element' && resource.name == 'data' && resource.attributes && resource.attributes.name) {
const term = resource.attributes.name;
if (typeof term === 'string') {
if (resource.elements && resource.elements.length > 0) {
let translationElem = resource.elements[0];
translationElem = translationElem.elements[0];
// Right now only string resources are supported
const translation = translationElem.type === 'text' ? translationElem.text : '';
result.push({
term: term,
translation: unescape(translation),
});
}
}
}
}
}
}

return {
translations: result,
};
};

export const resXExporter: Exporter = async (data: IntermediateTranslationFormat) => {
const strings = data.translations.map(translation => ({
_attributes: { name: translation.term, 'xml:space': 'preserve' },
value: escape(translation.translation),
}));
var xml = xmlJs.js2xml(
{
_declaration: { _attributes: { version: '1.0', encoding: 'utf-8' } },
root: {
'xsd:schema': getXsdSchema(),
resheader: [
{
_attributes: { name: 'resmimetype' },
value: 'text/microsoft-resx',
},
{
_attributes: { name: 'version' },
value: '2.0',
},
{
_attributes: { name: 'reader' },
value: 'System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089',
},
{
_attributes: { name: 'writer' },
value: 'System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089',
},
],
data: strings,
},
},
{ compact: true, spaces: 2 },
);
return xml;
};

function unescape(str: string): string {
return str.replace(/(?<!\\)"/g, '').replace(/\\([\'@"\?])/g, '$1');
}

function escape(str: string): string {
return str.replace(/([\'@"\?])/g, '\\$1');
}

function getXsdSchema(): any {
return {
_attributes: { id: 'root', xmlns: '', 'xmlns:xsd': 'http://www.w3.org/2001/XMLSchema', 'xmlns:msdata': 'urn:schemas-microsoft-com:xml-msdata' },
'xsd:import': { _attributes: { namespace: 'http://www.w3.org/XML/1998/namespace' } },
'xsd:element': {
_attributes: { name: 'root', 'msdata:IsDataSet': 'true' },
'xsd:complexType': {
'xsd:choice': {
_attributes: { maxOccurs: 'unbounded' },
'xsd:element': [
{
_attributes: { name: 'metadata' },
'xsd:complexType': {
'xsd:sequence': { 'xsd:element': { _attributes: { name: 'value', type: 'xsd:string', minOccurs: '0' } } },
'xsd:attribute': [
{ _attributes: { name: 'name', use: 'required', type: 'xsd:string' } },
{ _attributes: { name: 'type', type: 'xsd:string' } },
{ _attributes: { name: 'mimetype', type: 'xsd:string' } },
{ _attributes: { ref: 'xml:space' } },
],
},
},
{
_attributes: { name: 'assembly' },
'xsd:complexType': {
'xsd:attribute': [{ _attributes: { name: 'alias', type: 'xsd:string' } }, { _attributes: { name: 'name', type: 'xsd:string' } }],
},
},
{
_attributes: { name: 'data' },
'xsd:complexType': {
'xsd:sequence': {
'xsd:element': [
{ _attributes: { name: 'value', type: 'xsd:string', minOccurs: '0', 'msdata:Ordinal': '1' } },
{ _attributes: { name: 'comment', type: 'xsd:string', minOccurs: '0', 'msdata:Ordinal': '2' } },
],
},
'xsd:attribute': [
{ _attributes: { name: 'name', type: 'xsd:string', use: 'required', 'msdata:Ordinal': '1' } },
{ _attributes: { name: 'type', type: 'xsd:string', 'msdata:Ordinal': '3' } },
{ _attributes: { name: 'mimetype', type: 'xsd:string', 'msdata:Ordinal': '4' } },
{ _attributes: { ref: 'xml:space' } },
],
},
},
{
_attributes: { name: 'resheader' },
'xsd:complexType': {
'xsd:sequence': { 'xsd:element': { _attributes: { name: 'value', type: 'xsd:string', minOccurs: '0', 'msdata:Ordinal': '1' } } },
'xsd:attribute': { _attributes: { name: 'name', type: 'xsd:string', use: 'required' } },
},
},
],
},
},
},
};
}
Loading

0 comments on commit 70e8b13

Please sign in to comment.