Compare commits

..

33 Commits

Author SHA1 Message Date
Rainer Killinger
9f886a8968 0.43.0 2021-04-07 13:34:29 +02:00
openstappsbot
e80afe2c93 refactor: update all 2021-04-06 16:12:49 +00:00
Rainer Killinger
6b10cc86d8 refactor: update @types/node for node version 14 2021-04-06 14:08:42 +02:00
openstappsbot
1aedd6332f refactor: update all 2021-03-29 04:15:38 +00:00
Rainer Killinger
790c8bbc3f docs: update changelog 2021-02-16 14:04:46 +01:00
Rainer Killinger
e934017a65 0.42.0 2021-02-16 14:04:40 +01:00
Rainer Killinger
88dbedd932 refactor: update dependenices 2021-02-15 10:57:25 +01:00
Rainer Killinger
8b1b3444fd refactor: align SCCreativeWork to schema.org spec 2021-02-15 10:37:43 +01:00
Rainer Killinger
d5f3d71a41 refactor: overhaul translator as mentioned in #118 2021-02-15 10:37:38 +01:00
wulkanat@gmail.com
cbcf9c9adb docs: update changelog 2021-02-08 14:59:21 +01:00
wulkanat@gmail.com
321e4f6f24 0.41.0 2021-02-08 14:59:14 +01:00
Wieland Schöbl
8510f11d7b fix: add date, numeric range filter to SCSearchFilter 2021-02-05 13:50:18 +01:00
wulkanat@gmail.com
68aa377fe2 docs: update changelog 2021-02-05 12:59:54 +01:00
wulkanat@gmail.com
29f2c77ecc 0.40.0 2021-02-05 12:59:47 +01:00
Wieland Schöbl
29bc00616e feat: add range filter, date sorting support 2021-02-02 15:07:49 +01:00
Rainer Killinger
02920af4a4 refactor: make SCDish additives translatable 2021-02-01 13:24:12 +00:00
Rainer Killinger
0c14b0f1a0 test: change SCSemester sample data acronym 2020-12-02 17:22:17 +01:00
Rainer Killinger
db0a239761 refactor: change SCSemester acronym pattern 2020-12-02 17:19:46 +01:00
Jovan Krunić
377f55bbbe docs: update changelog 2020-12-01 17:16:02 +01:00
Jovan Krunić
8d7a0e10c6 0.39.0 2020-12-01 17:16:01 +01:00
Rainer Killinger
4865f911d1 refactor: utilize null coalescing 2020-11-23 10:06:14 +01:00
Rainer Killinger
15ae5e0873 refactor: remove narrow property value translation 2020-11-23 10:06:14 +01:00
Rainer Killinger
a246bdea84 feat: extend property value translation retrival 2020-11-23 10:06:13 +01:00
Wieland Schöbl
8641bfc877 docs: add @inheritTags documentation 2020-11-11 11:40:36 +01:00
Michel Jonathan Schmitz
d34e66fbbc docs: update changelog 2020-11-02 09:06:11 +01:00
Michel Jonathan Schmitz
c6273a85d9 0.38.1 2020-11-02 08:57:41 +01:00
Michel Jonathan Schmitz
5558d29c2b ci: remove caching 2020-10-30 15:01:00 +01:00
Michel Jonathan Schmitz
69dfd1ae39 build: replace deprecated dependencies 2020-10-30 09:50:46 +01:00
Michel Jonathan Schmitz
4638bb3684 build: update dependencies 2020-10-30 09:50:46 +01:00
Michel Jonathan Schmitz
6c687c6004 docs: update changelog 2020-10-30 09:49:52 +01:00
Michel Jonathan Schmitz
0bf1301733 0.38.0 2020-10-30 09:46:46 +01:00
Wieland Schöbl
827ba47892 refactor: swap out jsonschema package for json-schema 2020-10-29 15:14:25 +01:00
wulkanat@gmail.com
d3b620a745 docs: update changelog 2020-09-23 06:09:23 +02:00
24 changed files with 1875 additions and 2605 deletions

View File

@@ -1,10 +1,5 @@
image: registry.gitlab.com/openstapps/projectmanagement/node image: registry.gitlab.com/openstapps/projectmanagement/node
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules
before_script: before_script:
- npm install - npm install

5
.mailmap Normal file
View File

@@ -0,0 +1,5 @@
Rainer Killinger <mail-openstapps@killinger.co> Rainer Killinger <git@killinger.co>
Rainer Killinger <mail-openstapps@killinger.co> Rainer Killinger <killinge@hrz.uni-frankfurt.de>
Rainer Killinger <mail-openstapps@killinger.co> Rainer Killinger <killinger@hrz.uni-frankfurt.de>
Wieland Schöbl <wulkanat@gmail.com> wulkanat@gmail.com <wulkanat@gmail.com>
Wieland Schöbl <wulkanat@gmail.com> Wieland Schöbl <wieland.schoebl@campus.tu-berlin.de>

View File

@@ -1,3 +1,52 @@
# [0.42.0](https://gitlab.com/openstapps/core/compare/v0.41.0...v0.42.0) (2021-02-16)
# [0.41.0](https://gitlab.com/openstapps/core/compare/v0.40.0...v0.41.0) (2021-02-08)
### Bug Fixes
* add date, numeric range filter to SCSearchFilter ([8510f11](https://gitlab.com/openstapps/core/commit/8510f11d7b4c62a6b239a70f47fe07e8cc86ab63))
# [0.40.0](https://gitlab.com/openstapps/core/compare/v0.39.0...v0.40.0) (2021-02-05)
### Features
* add range filter, date sorting support ([29bc006](https://gitlab.com/openstapps/core/commit/29bc00616e87a8d346d8c304fab2e3818921c75e))
# [0.39.0](https://gitlab.com/openstapps/core/compare/v0.38.1...v0.39.0) (2020-12-01)
### Features
* extend property value translation retrival ([a246bde](https://gitlab.com/openstapps/core/commit/a246bdea84e0ca390be6ab38723d637626db87d2))
## [0.38.1](https://gitlab.com/openstapps/core/compare/v0.38.0...v0.38.1) (2020-11-02)
# [0.38.0](https://gitlab.com/openstapps/core/compare/v0.37.0...v0.38.0) (2020-10-30)
# [0.37.0](https://gitlab.com/openstapps/core/compare/v0.36.0...v0.37.0) (2020-09-23)
### Bug Fixes
* remove keyword tag from steps ([c369c85](https://gitlab.com/openstapps/core/commit/c369c8520a2eed169555a35a50ce745c08e1f9da))
* remove redundant property declaration ([66075ef](https://gitlab.com/openstapps/core/commit/66075ef99b95198a9cd5c0a396603e089221bcd9))
# [0.36.0](https://gitlab.com/openstapps/core/compare/v0.35.0...v0.36.0) (2020-07-16) # [0.36.0](https://gitlab.com/openstapps/core/compare/v0.35.0...v0.36.0) (2020-07-16)

View File

@@ -35,16 +35,18 @@ node --require ts-node/register src/cli.ts routes PATH/TO/ROUTES.md
Annotations are used to add additional informations to fields, which are used to autogenerate mappings from the core objects. Annotations are used to add additional informations to fields, which are used to autogenerate mappings from the core objects.
External dependencies can not be covered by the annotations. Documentation about some of the annotations can be found in: [typedoc](https://typedoc.org/guides/doccomments/) External dependencies can not be covered by the annotations. Documentation about some of the annotations can be found in: [typedoc](https://typedoc.org/guides/doccomments/)
| annotation | description | parameters | | annotation | description | parameters |
|-------------------|-------------------------------------------|---------------| |-------------------|-------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------|
| `@aggregatable` | used for generating of aggregations of the field if the core schema is used to put data into a database/key-value store | whether the property is being used on the top type or across all types: `global` | | `@aggregatable` | used for generating of aggregations of the field if the core schema is used to put data into a database/key-value store | whether the property is being used on the top type or across all types: `global` |
| `@float` | number field is interpreted as float | | | `@float` | number field is interpreted as float | |
| `@indexable` | marks the type as indexable if the core schema is used to put data into a database/key-value store| | | `@indexable` | marks the type as indexable if the core schema is used to put data into a database/key-value store | |
| `@integer` | number field is interpreted as integer | | | `@integer` | number field is interpreted as integer | |
| `@keyword` | string field is interpreted as keyword | | | `@keyword` | string field is interpreted as keyword | |
| `@sortable` | field is sortable if the core schema is used to put data into a database/key-value store | sort method to be used: `ducet`, `price`, `distance` | | `@sortable` | field is sortable if the core schema is used to put data into a database/key-value store. Fields are always sortable through generic sort, even without annotation. | sort method to be used: `ducet`, `price`, `distance` |
| `@text` | string field is interpreted as text | | | `@text` | string field is interpreted as text | |
| `@validatable` | marks the type as validatable if the core schema is used to put data into a database/key-value store | | | `@date` | string field is interpreted as a date field | |
| `@filterable` | non-object/nested field is filterable if the core schema is used to put data into a database/key-value store | | | `@validatable` | marks the type as validatable if the core schema is used to put data into a database/key-value store | |
| `@filterable` | non-object/nested field is filterable if the core schema is used to put data into a database/key-value store | |
| `@inheritTags` | inherit all tags from another field | `[SCThingType]::[field]` |
*Note: tags ignore casing, but for consistency they should use the variants proposed here.*

3803
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "@openstapps/core", "name": "@openstapps/core",
"version": "0.37.0", "version": "0.43.0",
"description": "StAppsCore - Generalized model of data", "description": "StAppsCore - Generalized model of data",
"keywords": [ "keywords": [
"Model", "Model",
@@ -25,50 +25,53 @@
"preversion": "npm run prepublishOnly", "preversion": "npm run prepublishOnly",
"push": "git push && git push origin \"v$npm_package_version\"", "push": "git push && git push origin \"v$npm_package_version\"",
"schema": "node --max-old-space-size=8192 --stack-size=10240 ./node_modules/.bin/openstapps-core-tools schema src lib/schema", "schema": "node --max-old-space-size=8192 --stack-size=10240 ./node_modules/.bin/openstapps-core-tools schema src lib/schema",
"test": "nyc mocha --require ts-node/register --require source-map-support/register --ui mocha-typescript test/*.spec.ts", "test": "nyc mocha --require ts-node/register --recursive 'test/*.spec.ts'",
"tslint": "tslint -p tsconfig.json -c tslint.json 'src/**/*.ts'" "tslint": "tslint -p tsconfig.json -c tslint.json 'src/**/*.ts'"
}, },
"author": "Karl-Philipp Wulfert <krlwlfrt@gmail.com>", "author": "Karl-Philipp Wulfert <krlwlfrt@gmail.com>",
"contributors": [ "contributors": [
"Andreas Lehmann", "Andreas Lehmann",
"Anselm Stordeur <anselmstordeur@gmail.com>", "Anselm Stordeur <anselmstordeur@gmail.com>",
"Axel Nieder-Vahrenholz",
"Benjamin Jöckel", "Benjamin Jöckel",
"Imran Hossain", "Frank Nagel",
"Jovan Krunić <jovan.krunic@gmail.com>", "Jovan Krunić <jovan.krunic@gmail.com>",
"Michel Jonathan Schmitz", "Michel Jonathan Schmitz",
"Rainer Killinger <mail-openstapps@killinger.co>", "Rainer Killinger <mail-openstapps@killinger.co>",
"Roman Klopsch",
"Sebastian Lange", "Sebastian Lange",
"Wieland Schöbl", "Wieland Schöbl"
"Roman Klopsch"
], ],
"dependencies": { "dependencies": {
"@openstapps/core-tools": "0.19.0",
"@types/geojson": "1.0.6", "@types/geojson": "1.0.6",
"@types/json-patch": "0.0.30", "@types/json-patch": "0.0.30",
"@types/node": "10.17.14", "@types/json-schema": "7.0.7",
"@types/node": "14.14.37",
"fast-clone": "1.5.13", "fast-clone": "1.5.13",
"http-status-codes": "2.1.2", "fast-deep-equal": "3.1.3",
"http-status-codes": "2.1.4",
"json-patch": "0.7.0", "json-patch": "0.7.0",
"jsonschema": "1.2.5", "json-schema": "0.3.0",
"ts-optchain": "0.1.8" "ts-optchain": "0.1.8"
}, },
"devDependencies": { "devDependencies": {
"@openstapps/configuration": "0.24.0", "@openstapps/configuration": "0.27.0",
"@openstapps/core-tools": "0.14.0", "@openstapps/logger": "0.6.0",
"@openstapps/logger": "0.5.0", "@testdeck/mocha": "0.1.2",
"@types/chai": "4.2.12", "@types/chai": "4.2.16",
"@types/rimraf": "3.0.0", "@types/rimraf": "3.0.0",
"chai": "4.2.0", "chai": "4.3.4",
"conditional-type-checks": "1.0.5", "conditional-type-checks": "1.0.5",
"conventional-changelog-cli": "2.1.0", "conventional-changelog-cli": "2.1.1",
"mocha": "8.1.1", "mocha": "8.3.2",
"mocha-typescript": "1.1.17",
"nyc": "15.1.0", "nyc": "15.1.0",
"rimraf": "3.0.2", "rimraf": "3.0.2",
"source-map-support": "0.5.19", "source-map-support": "0.5.19",
"ts-node": "9.0.0", "ts-node": "9.1.1",
"tslint": "6.1.3", "tslint": "6.1.3",
"typedoc": "0.14.2", "typedoc": "0.18.0",
"typescript": "3.5.3" "typescript": "3.8.3"
}, },
"nyc": { "nyc": {
"all": true, "all": true,

View File

@@ -18,6 +18,8 @@
* *
* @pattern ^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$ * @pattern ^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$
* @see https://gist.github.com/philipashlock/8830168 * @see https://gist.github.com/philipashlock/8830168
*
* @date
*/ */
export type SCISO8601Date = string; export type SCISO8601Date = string;
/* tslint:enable */ /* tslint:enable */

View File

@@ -12,8 +12,8 @@
* You should have received a copy of the GNU General Public License along with * You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {ValidationError} from '@openstapps/core-tools/lib/common';
import {BAD_REQUEST} from 'http-status-codes'; import {BAD_REQUEST} from 'http-status-codes';
import {ValidationError} from 'jsonschema';
import {SCError} from '../error'; import {SCError} from '../error';
/** /**

View File

@@ -13,7 +13,7 @@
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {OK} from 'http-status-codes'; import {OK} from 'http-status-codes';
import {Schema} from 'jsonschema'; import {JSONSchema7} from 'json-schema';
import {SCInternalServerErrorResponse} from '../errors/internal-server-error'; import {SCInternalServerErrorResponse} from '../errors/internal-server-error';
import {SCMethodNotAllowedErrorResponse} from '../errors/method-not-allowed'; import {SCMethodNotAllowedErrorResponse} from '../errors/method-not-allowed';
import {SCNotFoundErrorResponse} from '../errors/not-found'; import {SCNotFoundErrorResponse} from '../errors/not-found';
@@ -78,12 +78,12 @@ export interface SCPluginMetaData {
/** /**
* How the requests of the plugin looks like, a JSON schema for validation * How the requests of the plugin looks like, a JSON schema for validation
*/ */
requestSchema: Schema; requestSchema: JSONSchema7;
/** /**
* How the responses of the plugin looks like, a JSON schema for validation * How the responses of the plugin looks like, a JSON schema for validation
*/ */
responseSchema: Schema; responseSchema: JSONSchema7;
/** /**
* The desired route, for example /feedback. * The desired route, for example /feedback.

View File

@@ -19,6 +19,7 @@ import {SCMap} from '../../general/map';
import {SCSearchAvailabilityFilter} from './filters/availability'; import {SCSearchAvailabilityFilter} from './filters/availability';
import {SCSearchBooleanFilter} from './filters/boolean'; import {SCSearchBooleanFilter} from './filters/boolean';
import {SCSearchDistanceFilter} from './filters/distance'; import {SCSearchDistanceFilter} from './filters/distance';
import {SCSearchDateRangeFilter, SCSearchNumericRangeFilter} from './filters/range';
import {SCSearchValueFilter} from './filters/value'; import {SCSearchValueFilter} from './filters/value';
/** /**
@@ -28,7 +29,9 @@ export type SCSearchFilterType =
'availability' 'availability'
| 'boolean' | 'boolean'
| 'distance' | 'distance'
| 'value'; | 'value'
| 'date range'
| 'numeric range';
/** /**
* Structure of a filter instruction * Structure of a filter instruction
@@ -57,4 +60,6 @@ export type SCSearchFilter =
SCSearchAvailabilityFilter SCSearchAvailabilityFilter
| SCSearchBooleanFilter | SCSearchBooleanFilter
| SCSearchDistanceFilter | SCSearchDistanceFilter
| SCSearchValueFilter; | SCSearchValueFilter
| SCSearchNumericRangeFilter
| SCSearchDateRangeFilter;

View File

@@ -0,0 +1,112 @@
/*
* Copyright (C) 2020 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {SCThingsField} from '../../../meta';
import {SCSearchAbstractFilter, SCSearchAbstractFilterArguments} from '../filter';
/**
* A date range filter
*
* Filter for documents with a date field that satisfies the given constraints
*
* @see https://www.elastic.co/guide/en/elasticsearch/reference/5.6/query-dsl-range-query.html#_date_format_in_range_queries
*/
export interface SCSearchDateRangeFilter extends SCSearchAbstractFilter<SCDateRangeFilterArguments> {
/**
* @see SCSearchAbstractFilter.type
*/
type: 'date range';
}
/**
* A distance filter
*
* Filter for documents with a numeric field that satisfies the given constraints
*
* @see https://www.elastic.co/guide/en/elasticsearch/reference/5.6/query-dsl-range-query.html#query-dsl-range-query
*/
export interface SCSearchNumericRangeFilter extends SCSearchAbstractFilter<SCNumericRangeFilterArguments> {
/**
* @see SCSearchAbstractFilter.type
*/
type: 'numeric range';
}
/**
* Additional arguments for date range filters
*
* Filter uses a plain string to allow for date math expressions
* @see https://www.elastic.co/guide/en/elasticsearch/client/net-api/current/date-math-expressions.html
*/
export interface SCDateRangeFilterArguments extends SCRangeFilterArguments<string> {
/**
* Optional date format specifier
*
* @see https://www.elastic.co/guide/en/elasticsearch/reference/5.6/query-dsl-range-query.html#_date_format_in_range_queries
*/
format?: string;
/**
* Optional timezone specifier
*
* @see https://www.elastic.co/guide/en/elasticsearch/reference/5.6/query-dsl-range-query.html#_time_zone_in_range_queries
*/
timeZone?: string;
}
/**
* Additional arguments for numeric range filters
*/
export type SCNumericRangeFilterArguments = SCRangeFilterArguments<number>;
/**
* Additional arguments for range filters
*
* @see https://www.elastic.co/guide/en/elasticsearch/reference/5.6/query-dsl-range-query.html#query-dsl-range-query
*/
export interface SCRangeFilterArguments<T> extends SCSearchAbstractFilterArguments {
/**
* Bounds of the range
*/
bounds: Bounds<T>;
/**
* Field where the filter will be applied
*/
field: SCThingsField;
}
export interface Bounds<T> {
/**
* The lower bound
*/
lowerBound?: Bound<T>;
/**
* The upper bound
*/
upperBound?: Bound<T>;
}
export interface Bound<T> {
/**
* Limit of the bound
*/
limit: T;
/**
* Bound mode
*/
mode: 'inclusive' | 'exclusive';
}

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2019 StApps * Copyright (C) 2019, 2020 StApps
* This program is free software: you can redistribute it and/or modify it * This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free * under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3. * Software Foundation, version 3.
@@ -16,6 +16,7 @@ import {SCMap} from '../../general/map';
import {SCThingsField} from '../../meta'; import {SCThingsField} from '../../meta';
import {SCDistanceSort} from './sorts/distance'; import {SCDistanceSort} from './sorts/distance';
import {SCDucetSort} from './sorts/ducet'; import {SCDucetSort} from './sorts/ducet';
import {SCGenericSort} from './sorts/generic';
import {SCPriceSort} from './sorts/price'; import {SCPriceSort} from './sorts/price';
/** /**
@@ -51,9 +52,9 @@ export interface SCSearchAbstractSortArguments extends SCMap<unknown> {
/** /**
* Type of a sort instruction * Type of a sort instruction
*/ */
export type SCSearchSortType = 'distance' | 'price' | 'ducet'; export type SCSearchSortType = 'distance' | 'price' | 'ducet' | 'generic';
/** /**
* A sort instruction * A sort instruction
*/ */
export type SCSearchSort = SCDistanceSort | SCPriceSort | SCDucetSort; export type SCSearchSort = SCDistanceSort | SCPriceSort | SCDucetSort | SCGenericSort;

View File

@@ -0,0 +1,25 @@
/*
* Copyright (C) 2020 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {SCSearchAbstractSort, SCSearchAbstractSortArguments} from '../sort';
/**
* Sort instruction for generic sort such as date
*/
export interface SCGenericSort extends SCSearchAbstractSort<SCSearchAbstractSortArguments> {
/**
* @see SCSearchAbstractSort.type
*/
type: 'generic';
}

View File

@@ -12,7 +12,7 @@
* You should have received a copy of the GNU General Public License along with * You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {SCLanguage, SCMetaTranslations, SCTranslations} from '../../general/i18n'; import {SCLanguageCode, SCMetaTranslations, SCTranslations} from '../../general/i18n';
import {SCISO8601Date} from '../../general/time'; import {SCISO8601Date} from '../../general/time';
import {SCOrganizationWithoutReferences} from '../organization'; import {SCOrganizationWithoutReferences} from '../organization';
import {SCPersonWithoutReferences} from '../person'; import {SCPersonWithoutReferences} from '../person';
@@ -30,6 +30,12 @@ import {
*/ */
export interface SCCreativeWorkWithoutReferences export interface SCCreativeWorkWithoutReferences
extends SCThingWithoutReferences, SCThingThatCanBeOfferedWithoutReferences { extends SCThingWithoutReferences, SCThingThatCanBeOfferedWithoutReferences {
/**
* Languages this creative work is available in
*/
availableLanguages?: SCLanguageCode[];
/** /**
* Date the creative work was published * Date the creative work was published
* *
@@ -38,9 +44,11 @@ export interface SCCreativeWorkWithoutReferences
datePublished?: SCISO8601Date; datePublished?: SCISO8601Date;
/** /**
* List of languages this creative work is written/recorded/... in * Languages this creative work is written/recorded/... in
*
* @filterable
*/ */
inLanguages?: SCLanguage[]; inLanguage?: SCLanguageCode;
/** /**
* Keywords of the creative work * Keywords of the creative work
@@ -103,9 +111,10 @@ export class SCCreativeWorkMeta
de: { de: {
...SCThingMeta.getInstance<SCThingMeta>().fieldTranslations.de, ...SCThingMeta.getInstance<SCThingMeta>().fieldTranslations.de,
...SCThingThatCanBeOfferedMeta.getInstance().fieldTranslations.de, ...SCThingThatCanBeOfferedMeta.getInstance().fieldTranslations.de,
authors: 'Authoren', authors: 'Autoren',
availableLanguages: 'verfügbare Übersetzungen',
datePublished: 'Veröffentlichungsdatum', datePublished: 'Veröffentlichungsdatum',
inLanguages: 'verfügbare Übersetzungen', inLanguage: 'Inhaltssprache',
keywords: 'Schlagwörter', keywords: 'Schlagwörter',
publishers: 'Verleger', publishers: 'Verleger',
}, },
@@ -113,8 +122,9 @@ export class SCCreativeWorkMeta
...SCThingMeta.getInstance<SCThingMeta>().fieldTranslations.en, ...SCThingMeta.getInstance<SCThingMeta>().fieldTranslations.en,
...SCThingThatCanBeOfferedMeta.getInstance().fieldTranslations.en, ...SCThingThatCanBeOfferedMeta.getInstance().fieldTranslations.en,
authors: 'authors', authors: 'authors',
availableLanguages: 'available languages',
datePublished: 'release date', datePublished: 'release date',
inLanguages: 'available Languages', inLanguage: 'content language',
keywords: 'keywords', keywords: 'keywords',
publishers: 'publishers', publishers: 'publishers',
}, },

View File

@@ -91,6 +91,13 @@ export interface SCDish
export interface SCDishTranslatableProperties export interface SCDishTranslatableProperties
extends SCThingWithCategoriesTranslatableProperties, SCThingThatCanBeOfferedTranslatableProperties { extends SCThingWithCategoriesTranslatableProperties, SCThingThatCanBeOfferedTranslatableProperties {
/**
* Additives of the dish
*
* @filterable
* @keyword
*/
additives?: string[];
/** /**
* Characteristics of the dish * Characteristics of the dish
*/ */

View File

@@ -29,7 +29,7 @@ export interface SCSemesterWithoutReferences
* The short name of the semester, using the given pattern. * The short name of the semester, using the given pattern.
* *
* @filterable * @filterable
* @pattern ^(WS|SS) [0-9]{4}(/[0-9]{2})?$ * @pattern ^(WS|SS|WiSe|SoSe) [0-9]{4}(/[0-9]{2})?$
* @keyword * @keyword
*/ */
acronym: string; acronym: string;

View File

@@ -13,8 +13,9 @@
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import clone = require('fast-clone'); import clone = require('fast-clone');
import equal = require('fast-deep-equal/es6');
import {Defined, TSOCType} from 'ts-optchain'; import {Defined, TSOCType} from 'ts-optchain';
import {SCTranslations} from './general/i18n'; import {SCLanguageCode} from './general/i18n';
import {isThing} from './guards'; import {isThing} from './guards';
import {SCClasses} from './meta'; import {SCClasses} from './meta';
import {SCThing, SCThingType} from './things/abstract/thing'; import {SCThing, SCThingType} from './things/abstract/thing';
@@ -27,13 +28,32 @@ const standardCacheSize = 200;
*/ */
export class SCThingTranslator { export class SCThingTranslator {
/**
* Getter for language property
*/
get language(): SCLanguageCode {
return this._language;
}
/**
* Setter for language property. Also flushes translation cache
*
* @param language The language the translator instance will use from now on
*/
set language(language: SCLanguageCode) {
if (language !== this._language) {
this.cache.flush();
}
this._language = language;
}
/** /**
* Property representing the translators target language * Property representing the translators target language
*/ */
private _language: keyof SCTranslations<SCThing>; private _language: SCLanguageCode;
/** /**
* Property representing the translators base language * LRU cache containing already translated SCThings
* This means every translation is given for this language
*/ */
private readonly cache: LRUCache<SCThing>; private readonly cache: LRUCache<SCThing>;
@@ -42,36 +62,23 @@ export class SCThingTranslator {
*/ */
private readonly metaClasses: typeof SCClasses; private readonly metaClasses: typeof SCClasses;
/**
* LRU cache containing SCThings translations have been provided for
*/
private readonly sourceCache: LRUCache<SCThing>;
/** /**
* @example * @example
* // returns translator instance for german * // returns translator instance for german
* new SCThingTranslator('de'); * new SCThingTranslator('de');
*/ */
constructor(language: keyof SCTranslations<SCThing>, cacheCapacity: number = standardCacheSize) { constructor(language: SCLanguageCode, cacheCapacity: number = standardCacheSize) {
this.cache = new LRUCache(cacheCapacity); this.cache = new LRUCache(cacheCapacity);
this.sourceCache = new LRUCache(cacheCapacity);
this._language = language; this._language = language;
this.metaClasses = SCClasses; this.metaClasses = SCClasses;
} }
/**
* Getter for language property
*/
get language(): keyof SCTranslations<SCThing> {
return this._language;
}
/**
* Setter for language property. Also flushes translation cache
*
* @param language The language the translator instance will use from now on
*/
set language(language: keyof SCTranslations<SCThing>) {
if (language !== this._language) {
this.cache.flush();
}
this._language = language;
}
/** /**
* Get field value translation recursively * Get field value translation recursively
* *
@@ -101,8 +108,8 @@ export class SCThingTranslator {
* @param language The language the thing property values are translated to * @param language The language the thing property values are translated to
* @returns The thing with all known meta values translated * @returns The thing with all known meta values translated
*/ */
private getAllMetaFieldTranslations<T extends SCThing>(thingType: SCThingType, private getAllMetaFieldTranslations(thingType: SCThingType,
language: keyof SCTranslations<T>): object | undefined { language: SCLanguageCode): object | undefined {
const fieldTranslations = {}; const fieldTranslations = {};
const metaClass = this.getMetaClassInstance(thingType); const metaClass = this.getMetaClassInstance(thingType);
if (typeof metaClass === 'undefined') { if (typeof metaClass === 'undefined') {
@@ -151,7 +158,7 @@ export class SCThingTranslator {
* @returns The thing with translated meta field values * @returns The thing with translated meta field values
*/ */
private replaceAvailableMetaFieldValueTranslations(instance: any, private replaceAvailableMetaFieldValueTranslations(instance: any,
language: keyof SCTranslations<any>): any { language: SCLanguageCode): any {
const metaClass = this.getMetaClassInstance(instance.type); const metaClass = this.getMetaClassInstance(instance.type);
if (typeof metaClass === 'undefined') { if (typeof metaClass === 'undefined') {
return instance; return instance;
@@ -181,6 +188,60 @@ export class SCThingTranslator {
return instance; return instance;
} }
/**
* Recursively translates the given object in-place
* Translated values overwrite current values (destructive)
*
* @param instance The thing / object that will be translated
* @returns The thing translated
*/
private translateThingInPlaceDestructively<T>(instance: T): T {
const targetLanguage = this.language;
let nextInstance = instance as any;
// Recursively call this function on all nested SCThings, arrays and objects
Object.keys(nextInstance)
.forEach((key) => {
if (
isThing(nextInstance[key]) ||
nextInstance[key] instanceof Array ||
nextInstance[key] instanceof Object) {
nextInstance[key] = this.translateThingInPlaceDestructively(nextInstance[key]);
}
});
// Spread variable translations given by the connector into thing
if (typeof nextInstance.translations?.[targetLanguage] !== 'undefined') {
nextInstance = {...nextInstance, ...nextInstance.translations![targetLanguage]} as T;
}
// Spread known translations from meta classes into (partly) translated thing
this.replaceAvailableMetaFieldValueTranslations(nextInstance, targetLanguage);
return nextInstance;
}
/**
* Recursively translates the given object in-place
* Translated values overwrite current values (destructive)
*
* @param instance The thing / object that will be translated
* @returns The thing translated
*/
public translate<T extends SCThing>(thing: T): T {
if (equal(this.sourceCache.get(thing), thing)) {
const cachedInstance = this.cache.get(thing);
if (typeof cachedInstance !== 'undefined') {
return cachedInstance as T;
}
}
const translatedInstance = this.translateThingInPlaceDestructively(clone(thing));
delete translatedInstance.translations;
this.cache.putObject(translatedInstance);
this.sourceCache.putObject(thing);
return translatedInstance as T;
}
/** /**
* Get field value translation recursively * Get field value translation recursively
* @example * @example
@@ -189,23 +250,26 @@ export class SCThingTranslator {
* // or * // or
* const dishTranslatedAccess = translator.translate(dish); * const dishTranslatedAccess = translator.translate(dish);
* dishTranslatedAccess.offers[0].inPlace.categories[1](); * dishTranslatedAccess.offers[0].inPlace.categories[1]();
* // undoing the TSTSOCType<T> * // undoing the TSOCType<T>
* const dishAsBefore: SCDish = dishTranslatedAccess()!; * const dishAsBefore: SCDish = dishTranslatedAccess()!;
* @param data Top level object that gets passed through the recursion * @param thing Top level object that gets passed through the recursion
* @returns an TSOCType<T> object allowing for access to translations or a translated value(s) * @returns an TSOCType<T> object allowing for access to translations or a translated value(s)
*/ */
public translate<T extends SCThing>(data: T): TSOCType<T> { public translatedAccess<T extends SCThing>(thing: T): TSOCType<T> {
return new Proxy( return new Proxy(
((defaultValue?: Defined<T>) => (data == null ? defaultValue : data)) as TSOCType<T>, ((defaultValue?: Defined<T>) => (thing == null ? defaultValue : thing)) as TSOCType<T>,
{ {
get: (target, key) => { get: (target, key) => {
const obj: any = target(); const obj: any = target();
const objTranslatedFromCache = this.cache.get(data); if (equal(this.sourceCache.get(thing), thing)) {
if (typeof objTranslatedFromCache !== 'undefined') { const objTranslatedFromCache = this.cache.get(thing);
return this.deeptranslate((objTranslatedFromCache as any)[key]); if (typeof objTranslatedFromCache !== 'undefined') {
return this.deeptranslate((objTranslatedFromCache as any)[key]);
}
} }
const objTranslated = this.translateWholeThingDestructively(clone(obj)); const objTranslated = this.translateThingInPlaceDestructively(clone(obj));
this.cache.putObject(objTranslated); this.cache.putObject(objTranslated);
this.sourceCache.putObject(thing);
return this.deeptranslate(objTranslated[key]); return this.deeptranslate(objTranslated[key]);
}, },
@@ -219,71 +283,31 @@ export class SCThingTranslator {
* @example * @example
* const translatedMetaDish = translator.translatedPropertyNames<SCCourseOfStudies>(SCThingType.CourseOfStudies); * const translatedMetaDish = translator.translatedPropertyNames<SCCourseOfStudies>(SCThingType.CourseOfStudies);
* @param type The type whose property names will be translated * @param type The type whose property names will be translated
* @param language The language all property names will be translated to
* @returns An object with the properties of the SCThingType where the values are the known property tranlations * @returns An object with the properties of the SCThingType where the values are the known property tranlations
*/ */
public translatedPropertyNames<T extends SCThing>(type: SCThingType, public translatedPropertyNames<T extends SCThing>(type: SCThingType): T | undefined {
language?: keyof SCTranslations<T>): T | undefined { return this.getAllMetaFieldTranslations(type, this.language) as T;
const targetLanguage = (typeof language !== 'undefined') ? language : this.language;
return this.getAllMetaFieldTranslations(type, targetLanguage) as T;
} }
/** /**
* Given a SCThingType this function will translate it * Given a SCThingType and a corresponding property name it returns the known property value translation
* * Access pattern to the meta object containing the translation can be thought of as type.field[key] with key being optional
* @param type The type that will be translated * @example
* @param language The language the type will be translated to * const singleValueTranslation = translator.translatedPropertyValue(SCThingType.Dish, 'categories', 'main dish');
* @returns Known translation of type parameter * @param type The type for whose property values a translation is required
* @param field The property for which a translation is required
* @param key If specified tries to access the field with this key
* @returns Known translation for the property
*/ */
public translatedThingType<T extends unknown>(type: SCThingType, public translatedPropertyValue(type: SCThingType,
language?: keyof SCTranslations<T>): string { field: string,
const targetLanguage = (typeof language !== 'undefined') ? language : this.language; key?: string): string | undefined {
const metaClass = this.getMetaClassInstance(type); const fieldValueTranslations = this.getMetaClassInstance(type).fieldValueTranslations[this.language] ??
this.getMetaClassInstance(type).fieldValueTranslations.en;
const fieldTranslation = fieldValueTranslations?.[field];
if (typeof metaClass.fieldValueTranslations[targetLanguage] !== 'undefined' && return fieldTranslation?.[key ?? ''] ?? key ?? fieldTranslation;
typeof metaClass.fieldValueTranslations[targetLanguage].type !== 'undefined') {
return metaClass.fieldValueTranslations[targetLanguage].type as string ;
}
return type;
} }
/**
* Recursively translates the given object in-place
* Translated values overwrite current values (destructive)
*
* @param instance The thing / object that will be translated
* @param language The language the thing / object is translated to
* @returns The thing translated
*/
public translateWholeThingDestructively(instance: any,
language?: keyof SCTranslations<any>): any {
const targetLanguage = (typeof language !== 'undefined') ? language : this.language;
let nextInstance = instance;
// Recursively call this function on all nested SCThings, arrays and objects
Object.keys(nextInstance)
.forEach((key) => {
if (
isThing((nextInstance as any)[key]) ||
nextInstance[key] instanceof Array ||
nextInstance[key] instanceof Object) {
nextInstance[key] = this.translateWholeThingDestructively(nextInstance[key], targetLanguage);
}
});
// Spread variable translations given by the connector into thing
if (typeof nextInstance.translations !== 'undefined') {
if (typeof nextInstance.translations![targetLanguage] !== 'undefined') {
nextInstance = {...nextInstance, ...nextInstance.translations![targetLanguage]} as typeof instance;
}
}
// Spread known translations from meta classes into (partly) translated thing
this.replaceAvailableMetaFieldValueTranslations(nextInstance, targetLanguage);
return nextInstance;
}
} }
/** /**

View File

@@ -13,7 +13,7 @@
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {expect} from 'chai'; import {expect} from 'chai';
import {slow, suite, test, timeout} from 'mocha-typescript'; import {slow, suite, test, timeout} from '@testdeck/mocha';
import {SCBulkResponse} from '../src/protocol/routes/bulk-request'; import {SCBulkResponse} from '../src/protocol/routes/bulk-request';
import {SCMultiSearchResponse} from '../src/protocol/routes/search-multi'; import {SCMultiSearchResponse} from '../src/protocol/routes/search-multi';
import {SCSearchResponse} from '../src/protocol/routes/search'; import {SCSearchResponse} from '../src/protocol/routes/search';

View File

@@ -21,12 +21,7 @@
"name": "Symposion Publishing" "name": "Symposion Publishing"
} }
], ],
"inLanguages": [ "inLanguage": "de",
{
"name": "german",
"code": "de"
}
],
"bookEdition": "2., überarb. u. erw. Aufl.", "bookEdition": "2., überarb. u. erw. Aufl.",
"isbn": "3936608776", "isbn": "3936608776",
"numberOfPages": 537, "numberOfPages": 537,

View File

@@ -6,7 +6,7 @@
"address": "http://foo.com:1234", "address": "http://foo.com:1234",
"name": "Foo Plugin", "name": "Foo Plugin",
"requestSchema": { "requestSchema": {
"$schema": "http://json-schema.org/draft-06/schema#", "$schema": "http://json-schema.org/draft-07/schema#",
"definitions": { "definitions": {
"SCFooPluginRequest": { "SCFooPluginRequest": {
"type": "object", "type": "object",
@@ -33,10 +33,10 @@
], ],
"additionalProperties": false, "additionalProperties": false,
"description": "User query", "description": "User query",
"id": "https://core.stapps.tu-berlin.de/v0.18.0/lib/schema/SCFooPluginRequest.json" "$id": "https://core.stapps.tu-berlin.de/v0.18.0/lib/schema/SCFooPluginRequest.json"
}, },
"responseSchema": { "responseSchema": {
"$schema": "http://json-schema.org/draft-06/schema#", "$schema": "http://json-schema.org/draft-07/schema#",
"definitions": { "definitions": {
"SCFooPluginResponse": { "SCFooPluginResponse": {
"type": "object", "type": "object",
@@ -65,7 +65,7 @@
], ],
"additionalProperties": false, "additionalProperties": false,
"description": "A response to a query", "description": "A response to a query",
"id": "https://core.stapps.tu-berlin.de/v0.18.0/lib/schema/SCFooPluginResponse.json" "$id": "https://core.stapps.tu-berlin.de/v0.18.0/lib/schema/SCFooPluginResponse.json"
}, },
"route": "/foo" "route": "/foo"
} }

View File

@@ -4,9 +4,9 @@
"uid": "7cebbc3e-0a21-5371-ab0d-f7ba12f53dbd", "uid": "7cebbc3e-0a21-5371-ab0d-f7ba12f53dbd",
"type": "semester", "type": "semester",
"name": "Sommersemester 2018", "name": "Sommersemester 2018",
"acronym": "SS 2018", "acronym": "SoSe 2018",
"alternateNames": [ "alternateNames": [
"SoSe 2018" "Sommer 2018"
], ],
"startDate": "2018-04-01", "startDate": "2018-04-01",
"endDate": "2018-09-30", "endDate": "2018-09-30",

View File

@@ -13,7 +13,7 @@
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {expect} from 'chai'; import {expect} from 'chai';
import {slow, suite, test, timeout} from 'mocha-typescript'; import {slow, suite, test, timeout} from '@testdeck/mocha';
import {SCBulkRoute} from '../src/protocol/routes/bulk-request'; import {SCBulkRoute} from '../src/protocol/routes/bulk-request';
import {SCBulkAddRoute} from '../src/protocol/routes/bulk-add'; import {SCBulkAddRoute} from '../src/protocol/routes/bulk-add';
import {SCThingUpdateRoute} from '../src/protocol/routes/thing-update'; import {SCThingUpdateRoute} from '../src/protocol/routes/thing-update';

View File

@@ -18,7 +18,7 @@ import {Logger} from '@openstapps/logger';
import {fail} from 'assert'; import {fail} from 'assert';
import {expect} from 'chai'; import {expect} from 'chai';
import {mkdirSync} from 'fs'; import {mkdirSync} from 'fs';
import {slow, suite, test, timeout} from 'mocha-typescript'; import {slow, suite, test, timeout} from '@testdeck/mocha';
import {join, resolve} from 'path'; import {join, resolve} from 'path';
import {DeclarationReflection, ProjectReflection} from 'typedoc'; import {DeclarationReflection, ProjectReflection} from 'typedoc';
import {ArrayType, IntrinsicType, ReferenceType, StringLiteralType, Type, UnionType} from 'typedoc/dist/lib/models'; import {ArrayType, IntrinsicType, ReferenceType, StringLiteralType, Type, UnionType} from 'typedoc/dist/lib/models';

View File

@@ -14,8 +14,8 @@
*/ */
import {expect} from 'chai'; import {expect} from 'chai';
import clone = require('fast-clone'); import clone = require('fast-clone');
import {slow, suite, test, timeout} from 'mocha-typescript'; import {slow, suite, test, timeout} from '@testdeck/mocha';
import {SCThingOriginType, SCThingType} from '../src/things/abstract/thing'; import {SCThingOriginType, SCThingType, SCThingRemoteOrigin} from '../src/things/abstract/thing';
import {SCBuildingWithoutReferences} from '../src/things/building'; import {SCBuildingWithoutReferences} from '../src/things/building';
import {SCDish, SCDishMeta} from '../src/things/dish'; import {SCDish, SCDishMeta} from '../src/things/dish';
import {SCSetting, SCSettingInputType} from '../src/things/setting'; import {SCSetting, SCSettingInputType} from '../src/things/setting';
@@ -105,142 +105,182 @@ const setting: SCSetting = {
}; };
const translator = new SCThingTranslator('de'); const translator = new SCThingTranslator('de');
// tslint:disable-next-line:no-eval const translatorEN = new SCThingTranslator('en');
const languageNonExistant = eval("'jp'");
// this will simulate a translator always utilizing the base language translations // this will simulate a translator always utilizing the base language translations
const translatorWithFallback = new SCThingTranslator(languageNonExistant); const translatorWithFallback = new SCThingTranslator('tt');
const translatedThingDE = translator.translate(dish);
const translatedThingFallback = translatorWithFallback.translate(dish);
// tslint:disable:max-line-length member-ordering newline-per-chained-call prefer-function-over-method completed-docs TranslationSpecInplace // tslint:disable:max-line-length member-ordering newline-per-chained-call prefer-function-over-method completed-docs TranslationSpecInplace
@suite(timeout(10000), slow(5000)) @suite(timeout(10000), slow(5000))
export class TranslationSpecInplace { export class TranslationSpecInplace {
@test @test
public directEnumSingleValue () { public directEnumSingleValue() {
expect(translator.translate(setting).inputType()).to.equal('einfache Auswahl'); expect(translator.translatedAccess(setting).inputType()).to.equal('einfache Auswahl');
} }
@test @test
public directStringLiteralType() { public directStringLiteralType() {
expect(translator.translate(dish).type()).to.equal('Essen'); expect(translator.translatedAccess(dish).type()).to.equal('Essen');
expect(translatedThingDE.type).to.equal('Essen');
} }
@test @test
public directStringProperty() { public directStringProperty() {
expect(translator.translate(dish).name()).to.equal('de-dish-name'); expect(translator.translatedAccess(dish).name()).to.equal('de-dish-name');
expect(translatedThingDE.name).to.equal('de-dish-name');
} }
@test @test
public directArrayOfString() { public directArrayOfString() {
expect(translator.translate(dish).characteristics()).to.deep expect(translator.translatedAccess(dish).characteristics()).to.deep
.equal([{name: 'de-characteristic0'}, {name: 'de-characteristic1'}]); .equal([{name: 'de-characteristic0'}, {name: 'de-characteristic1'}]);
expect(translatedThingDE.characteristics).to.deep
.equal([{name: 'de-characteristic0'}, {name: 'de-characteristic1'}]);
} }
@test @test
public directArrayOfStringSubscript() { public directArrayOfStringSubscript() {
expect(translator.translate(dish).characteristics[1]()).to.deep.equal({name: 'de-characteristic1'}); expect(translator.translatedAccess(dish).characteristics[1]()).to.deep.equal({name: 'de-characteristic1'});
expect(translatedThingDE.characteristics![1]).to.deep.equal({name: 'de-characteristic1'});
} }
@test @test
public directMetaArrayOfString() { public directMetaArrayOfString() {
expect(translator.translate(dish).categories()).to.deep.equal(['Hauptgericht', 'Nachtisch']); expect(translator.translatedAccess(dish).categories()).to.deep.equal(['Hauptgericht', 'Nachtisch']);
expect(translatedThingDE.categories).to.deep.equal(['Hauptgericht', 'Nachtisch']);
} }
@test @test
public directMetaArrayOfStringSubscript() { public directMetaArrayOfStringSubscript() {
expect(translator.translate(dish).categories[1]()).to.equal('Nachtisch'); expect(translator.translatedAccess(dish).categories[1]()).to.equal('Nachtisch');
expect(translatedThingDE.categories[1]).to.equal('Nachtisch');
} }
@test @test
public nestedStringLiteralType() { public nestedStringLiteralType() {
expect(translator.translate(dish).offers[0].inPlace.type()).to.equal('Gebäude'); expect(translator.translatedAccess(dish).offers[0].inPlace.type()).to.equal('Gebäude');
expect(translatedThingDE.offers![0].inPlace!.type).to.equal('Gebäude');
} }
@test @test
public nestedStringProperty() { public nestedStringProperty() {
expect(translator.translate(dish).offers[0].inPlace.name()).to.equal('de-space-name'); expect(translator.translatedAccess(dish).offers[0].inPlace.name()).to.equal('de-space-name');
expect(translatedThingDE.offers![0].inPlace!.name).to.equal('de-space-name');
} }
@test @test
public nestedMetaArrayOfString() { public nestedMetaArrayOfString() {
expect(translator.translate(dish).offers[0].inPlace.categories()).to.deep.equal(['Büro', 'Bildung']); expect(translator.translatedAccess(dish).offers[0].inPlace.categories()).to.deep.equal(['Büro', 'Bildung']);
expect(translatedThingDE.offers![0].inPlace!.categories).to.deep.equal(['Büro', 'Bildung']);
} }
@test @test
public nestedMetaArrayOfStringSubscript() { public nestedMetaArrayOfStringSubscript() {
expect(translator.translate(dish).offers[0].inPlace.categories[1]()).to.equal('Bildung'); expect(translator.translatedAccess(dish).offers[0].inPlace.categories[1]()).to.equal('Bildung');
expect(translatedThingDE.offers![0].inPlace!.categories[1]).to.equal('Bildung');
} }
@test @test
public directStringLiteralTypeFallback() { public directStringLiteralTypeFallback() {
expect(translatorWithFallback.translate(dish).type()).to.equal('dish'); expect(translatorWithFallback.translatedAccess(dish).type()).to.equal('dish');
expect(translatedThingFallback.type).to.equal('dish');
} }
@test @test
public directStringPropertyFallback() { public directStringPropertyFallback() {
expect(translatorWithFallback.translate(dish).name()).to.equal('base-dish-name'); expect(translatorWithFallback.translatedAccess(dish).name()).to.equal('base-dish-name');
expect(translatedThingFallback.name).to.equal('base-dish-name');
} }
@test @test
public directArrayOfStringSubscriptFallback() { public directArrayOfStringSubscriptFallback() {
expect(translatorWithFallback.translate(dish).characteristics[1]()) expect(translatorWithFallback.translatedAccess(dish).characteristics[1]())
.to.deep.equal({name: 'base-characteristic1'});
expect(translatedThingFallback.characteristics![1])
.to.deep.equal({name: 'base-characteristic1'}); .to.deep.equal({name: 'base-characteristic1'});
} }
@test @test
public directMetaArrayOfStringFallback() { public directMetaArrayOfStringFallback() {
expect(translatorWithFallback.translate(dish).categories()).to.deep.equal(['main dish', 'dessert']); expect(translatorWithFallback.translatedAccess(dish).categories()).to.deep.equal(['main dish', 'dessert']);
expect(translatedThingFallback.categories).to.deep.equal(['main dish', 'dessert']);
} }
@test @test
public directMetaArrayOfStringSubscriptFallback() { public directMetaArrayOfStringSubscriptFallback() {
expect(translatorWithFallback.translate(dish).categories[1]()).to.equal('dessert'); expect(translatorWithFallback.translatedAccess(dish).categories[1]()).to.equal('dessert');
expect(translatedThingFallback.categories[1]).to.equal('dessert');
} }
@test @test
public nestedStringLiteralTypeFallback() { public nestedStringLiteralTypeFallback() {
expect(translatorWithFallback.translate(dish).offers[0].inPlace.type()).to.equal('building'); expect(translatorWithFallback.translatedAccess(dish).offers[0].inPlace.type()).to.equal('building');
expect(translatedThingFallback.offers![0].inPlace!.type).to.equal('building');
} }
@test @test
public nestedStringPropertyFallback() { public nestedStringPropertyFallback() {
expect(translatorWithFallback.translate(dish).offers[0].inPlace.name()).to.equal('base-space-name'); expect(translatorWithFallback.translatedAccess(dish).offers[0].inPlace.name()).to.equal('base-space-name');
expect(translatedThingFallback.offers![0].inPlace!.name).to.equal('base-space-name');
} }
@test @test
public nestedMetaArrayOfStringFallback() { public nestedMetaArrayOfStringFallback() {
expect(translatorWithFallback.translate(dish).offers[0].inPlace.categories()) expect(translatorWithFallback.translatedAccess(dish).offers[0].inPlace.categories())
.to.deep.equal(['office', 'education']);
expect(translatedThingFallback.offers![0].inPlace!.categories)
.to.deep.equal(['office', 'education']); .to.deep.equal(['office', 'education']);
} }
@test @test
public nestedMetaArrayOfStringSubscriptFallback() { public nestedMetaArrayOfStringSubscriptFallback() {
expect(translatorWithFallback.translate(dish).offers[0].inPlace.categories[1]()).to.equal('education'); expect(translatorWithFallback.translatedAccess(dish).offers[0].inPlace.categories[1]()).to.equal('education');
expect(translatedThingFallback.offers![0].inPlace!.categories[1]).to.equal('education');
} }
@test @test
public directStringLiteralTypeUndefined() { public directStringLiteralTypeUndefined() {
// tslint:disable-next-line:no-eval // tslint:disable-next-line:no-eval
const undefinedThing = eval('(x) => undefined;'); const undefinedThing = eval('(x) => undefined;');
expect(translator.translate(undefinedThing())('defaultValue')).to.equal('defaultValue'); expect(translator.translatedAccess(undefinedThing())('defaultValue')).to.equal('defaultValue');
expect(translator.translate(dish).name('defaultValue')).to.not.equal('defaultValue'); expect(translator.translatedAccess(dish).name('defaultValue')).to.not.equal('defaultValue');
} }
@test @test
public nestedMetaArrayOfStringSubscriptUndefined() { public nestedMetaArrayOfStringSubscriptUndefined() {
// tslint:disable-next-line: no-eval // tslint:disable-next-line: no-eval
const workingTranslation = eval('translator.translate(dish).offers[0].inPlace.categories[1](\'printer\');'); const workingTranslation = eval('translator.translatedAccess(dish).offers[0].inPlace.categories[1](\'printer\');');
// tslint:disable-next-line: no-eval // tslint:disable-next-line: no-eval
const defaultValueTranslation = eval('translator.translate(dish).offers[0].inPlace.categories[1234](\'printer\');'); const defaultValueTranslation = eval('translator.translatedAccess(dish).offers[0].inPlace.categories[1234](\'printer\');');
expect(defaultValueTranslation).to.equal('printer'); expect(defaultValueTranslation).to.equal('printer');
expect(workingTranslation).to.not.equal('printer'); expect(workingTranslation).to.not.equal('printer');
} }
@test
public reaccessWithChangedSourceOmitsLRUCache() {
const translatorDE = new SCThingTranslator('de');
const dishCopy = clone(dish);
const translatedDish = translatorDE.translatedAccess(dish);
const distructivelyTranslatedDish = translatorDE.translate(dish);
(dishCopy.origin as SCThingRemoteOrigin).name = 'tranlator.spec';
expect(translatorDE.translatedAccess(dishCopy)).not.to.deep.equal(translatedDish);
expect(translatorDE.translate(dishCopy)).not.to.equal(distructivelyTranslatedDish);
}
@test @test
public changingTranslatorLanguageFlushesItsLRUCache() { public changingTranslatorLanguageFlushesItsLRUCache() {
const translatorDE = new SCThingTranslator('de'); const translatorDE = new SCThingTranslator('de');
expect(translatorDE.translate(dish).name()).to.equal('de-dish-name'); expect(translatorDE.translatedAccess(dish).name()).to.equal('de-dish-name');
expect(translatorDE.translate(dish).name).to.equal('de-dish-name');
translatorDE.language = 'en'; translatorDE.language = 'en';
expect(translatorDE.translate(dish).name()).to.equal('base-dish-name'); expect(translatorDE.translatedAccess(dish).name()).to.equal('base-dish-name');
expect(translatorDE.translate(dish).name).to.equal('base-dish-name');
} }
@test @test
@@ -250,7 +290,7 @@ export class TranslationSpecInplace {
for (let i = 0; i < 201; i++) { for (let i = 0; i < 201; i++) {
const anotherDish = Object.assign({}, dish); const anotherDish = Object.assign({}, dish);
anotherDish.uid = String(i); anotherDish.uid = String(i);
expect(translatorDE.translate(anotherDish).name()).to.equal('de-dish-name'); expect(translatorDE.translatedAccess(anotherDish).name()).to.equal('de-dish-name');
} }
} }
} }
@@ -262,22 +302,32 @@ export class MetaTranslationSpec {
@test @test
public consistencyWithMetaClass() { public consistencyWithMetaClass() {
const dishMetaTranslationsDE = translator.translatedPropertyNames(dish.type); const dishMetaTranslationsDE = translator.translatedPropertyNames(dish.type);
const dishMetaTranslationsEN = translator.translatedPropertyNames(dish.type, 'en'); const dishMetaTranslationsEN = translatorEN.translatedPropertyNames(dish.type);
expect(dishMetaTranslationsEN).to.not.deep.equal(dishMetaTranslationsDE); expect(dishMetaTranslationsEN).to.not.deep.equal(dishMetaTranslationsDE);
expect(dishMetaTranslationsDE).to.deep.equal(SCDishMeta.getInstance().fieldTranslations.de); expect(dishMetaTranslationsDE).to.deep.equal(SCDishMeta.getInstance().fieldTranslations.de);
expect(dishMetaTranslationsEN).to.deep.equal(SCDishMeta.getInstance().fieldTranslations.en); expect(dishMetaTranslationsEN).to.deep.equal(SCDishMeta.getInstance().fieldTranslations.en);
} }
@test @test
public retrieveTranslatedThingType() { public retrieveTranslatedPropertyValueType() {
const dishTypeDE = translator.translatedThingType(dish.type); const dishTypeDE = translator.translatedPropertyValue(dish.type, 'type');
const dishTypeEN = translator.translatedThingType(dish.type, 'en'); const dishTypeEN = translatorEN.translatedPropertyValue(dish.type, 'type', undefined);
const dishTypeBASE = translatorWithFallback.translatedThingType(dish.type); const dishTypeBASE = translatorWithFallback.translatedPropertyValue(dish.type, 'type');
expect(dishTypeDE).to.deep.equal(SCDishMeta.getInstance().fieldValueTranslations.de.type); expect(dishTypeDE).to.deep.equal(SCDishMeta.getInstance().fieldValueTranslations.de.type);
expect(dishTypeEN).to.deep.equal(SCDishMeta.getInstance().fieldValueTranslations.en.type); expect(dishTypeEN).to.deep.equal(SCDishMeta.getInstance().fieldValueTranslations.en.type);
expect(dishTypeBASE).to.deep.equal(SCDishMeta.getInstance().fieldValueTranslations.en.type); expect(dishTypeBASE).to.deep.equal(SCDishMeta.getInstance().fieldValueTranslations.en.type);
} }
@test
public retrieveTranslatedPropertyValueNested() {
const dishTypeDE = translator.translatedPropertyValue(dish.type, 'categories', 'main dish');
const dishTypeEN = translatorEN.translatedPropertyValue(dish.type, 'categories', 'main dish');
const dishTypeBASE = translatorWithFallback.translatedPropertyValue(dish.type, 'categories', 'main dish');
expect(dishTypeDE).to.deep.equal(SCDishMeta.getInstance<SCDishMeta>().fieldValueTranslations.de.categories['main dish']);
expect(dishTypeEN).to.deep.equal(dish.categories[0]);
expect(dishTypeBASE).to.deep.equal(dish.categories[0]);
}
@test @test
public thingWithoutMetaClass() { public thingWithoutMetaClass() {
const dishCopy = clone(dish); const dishCopy = clone(dish);