Skip to content

Commit

Permalink
feat: implement i18n for error messages #923 #924 (#928)
Browse files Browse the repository at this point in the history
* feat: implement i18n for error messages #923 #924

* feat: add translations in French

* fix: change typings as suggested in review

* fix: change French translations
  • Loading branch information
ocruze authored Jun 21, 2024
1 parent 56f231b commit e01c8c8
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 21 deletions.
36 changes: 36 additions & 0 deletions data/slds/1.0/unknown_wellknownname.sld
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<StyledLayerDescriptor xmlns="http://www.opengis.net/sld" xmlns:ogc="http://www.opengis.net/ogc"
xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:se="http://www.opengis.net/se" version="1.1.0"
xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengis.net/sld/1.1.0/StyledLayerDescriptor.xsd">
<NamedLayer>
<se:Name>foret</se:Name>
<UserStyle>
<se:Name>foret</se:Name>
<se:FeatureTypeStyle>
<se:Rule>
<se:Name>Single symbol</se:Name>
<se:PolygonSymbolizer>
<se:Fill>
<se:GraphicFill>
<se:Graphic>
<se:Mark>
<se:WellKnownName>brush://dense5</se:WellKnownName>
<se:Fill>
<se:SvgParameter name="fill">#8d5a99</se:SvgParameter>
</se:Fill>
</se:Mark>
</se:Graphic>
</se:GraphicFill>
</se:Fill>
<se:Stroke>
<se:SvgParameter name="stroke">#232323</se:SvgParameter>
<se:SvgParameter name="stroke-width">1</se:SvgParameter>
<se:SvgParameter name="stroke-linejoin">bevel</se:SvgParameter>
</se:Stroke>
</se:PolygonSymbolizer>
</se:Rule>
</se:FeatureTypeStyle>
</UserStyle>
</NamedLayer>
</StyledLayerDescriptor>
51 changes: 39 additions & 12 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"dependencies": {
"fast-xml-parser": "^4.2.2",
"geostyler-style": "^8.1.0",
"i18next": "^23.11.5",
"lodash": "^4.17.21"
},
"devDependencies": {
Expand Down
100 changes: 91 additions & 9 deletions src/SldStyleParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ import {
XmlBuilderOptionsOptional
} from 'fast-xml-parser';

import { isNil } from 'lodash';
import { isNil, merge } from 'lodash';
import i18next from 'i18next';

import {
geoStylerFunctionToSldFunction,
Expand All @@ -68,6 +69,8 @@ export type ConstructorParams = {
symbolizerUnits?: string;
parserOptions?: X2jOptionsOptional;
builderOptions?: XmlBuilderOptionsOptional;
translations?: SldStyleParserTranslations;
locale?: string;
};

const WELLKNOWNNAME_TTF_REGEXP = /^ttf:\/\/(.+)#(.+)$/;
Expand Down Expand Up @@ -98,6 +101,52 @@ const COMBINATION_MAP = {

type CombinationType = keyof typeof COMBINATION_MAP;

export type SldStyleParserTranslationKeys = {
marksymbolizerParseFailedUnknownWellknownName?: string;
noFilterDetected?: string;
symbolizerKindParseFailed?: string;
colorMapEntriesParseFailedColorUndefined?: string;
contrastEnhancParseFailedHistoAndNormalizeMutuallyExclusive?: string;
channelSelectionParseFailedRGBAndGrayscaleMutuallyExclusive?: string;
channelSelectionParseFailedRGBChannelsUndefined?: string;
};

export type SldStyleParserTranslations = Record<string, SldStyleParserTranslationKeys>;

export const defaultTranslations: SldStyleParserTranslations = {
en: {
marksymbolizerParseFailedUnknownWellknownName:
'MarkSymbolizer cannot be parsed. WellKnownName {{wellKnownName}} is not supported.',
noFilterDetected: 'No Filter detected.',
symbolizerKindParseFailed: 'Failed to parse SymbolizerKind {{sldSymbolizerName}} from SldRule.',
colorMapEntriesParseFailedColorUndefined: 'Cannot parse ColorMapEntries. color is undefined.',
contrastEnhancParseFailedHistoAndNormalizeMutuallyExclusive:
'Cannot parse ContrastEnhancement. Histogram and Normalize are mutually exclusive.',
channelSelectionParseFailedRGBAndGrayscaleMutuallyExclusive:
'Cannot parse ChannelSelection. RGB and Grayscale are mutually exclusive.',
channelSelectionParseFailedRGBChannelsUndefined:
'Cannot parse ChannelSelection. Red, Green and Blue channels must be defined.'
},
de: {},
fr: {
marksymbolizerParseFailedUnknownWellknownName:
'Échec de lecture du symbole de type MarkSymbolizer. Le WellKnownName {{wellKnownName}} n\'est pas supporté.',
noFilterDetected: 'Aucun filtre détecté.',
symbolizerKindParseFailed: 'Échec de lecture du type de symbole {{sldSymbolizerName}} à partir de SldRule.',
colorMapEntriesParseFailedColorUndefined: 'Lecture de ColorMapEntries échoué. color n\'est pas défini.',
contrastEnhancParseFailedHistoAndNormalizeMutuallyExclusive:
'Échec de lecture des propriétés de contraste ContrastEnhancement échoué. '
+'Histogram et Normalize sont mutuellement exclusifs.',
channelSelectionParseFailedRGBAndGrayscaleMutuallyExclusive:
'Échec de lecture de la sélection de canaux ChannelSelection. '
+'RGB et Grayscale sont mutuellement exclusifs.',
channelSelectionParseFailedRGBChannelsUndefined:
'Échec de lecture de la sélection de canaux ChannelSelection. '
+'Les canaux Rouge, Vert et Bleu doivent être définis.',

},
} as const;

/**
* This parser can be used with the GeoStyler.
* It implements the geostyler-style StyleParser interface.
Expand Down Expand Up @@ -191,6 +240,10 @@ export class SldStyleParser implements StyleParser<string> {
}
};

translations: SldStyleParserTranslations = defaultTranslations;

locale: string = 'en';

constructor(opts?: ConstructorParams) {
this.parser = new XMLParser({
...opts?.parserOptions,
Expand All @@ -213,9 +266,37 @@ export class SldStyleParser implements StyleParser<string> {
if (opts?.sldVersion) {
this.sldVersion = opts?.sldVersion;
}

if (opts?.locale) {
this.locale = opts.locale;
}

if (opts?.translations){
this.translations = merge(this.translations, opts.translations);
}

i18next.init({
lng: this.locale,
resources: {
en: {
translation: this.translations.en,
},
de: {
translation: this.translations.de,
},
fr: {
translation: this.translations.fr,
}
}
});

Object.assign(this, opts);
}

translate(key: keyof SldStyleParserTranslationKeys, params?: any): string {
return i18next.t(key, params) as string;
}

private _parser: XMLParser;
get parser(): XMLParser {
return this._parser;
Expand Down Expand Up @@ -505,7 +586,7 @@ export class SldStyleParser implements StyleParser<string> {
case 'RasterSymbolizer':
return this.getRasterSymbolizerFromSldSymbolizer(sldSymbolizer.RasterSymbolizer);
default:
throw new Error('Failed to parse SymbolizerKind from SldRule');
throw new Error(this.translate('symbolizerKindParseFailed', { sldSymbolizerName: sldSymbolizerName }));
}
});

Expand Down Expand Up @@ -590,7 +671,7 @@ export class SldStyleParser implements StyleParser<string> {
negatedFilter
];
} else {
throw new Error('No Filter detected');
throw new Error(this.translate('noFilterDetected'));
}
return filter;
}
Expand Down Expand Up @@ -1005,7 +1086,9 @@ export class SldStyleParser implements StyleParser<string> {
markSymbolizer.wellKnownName = wellKnownName;
break;
}
throw new Error('MarkSymbolizer cannot be parsed. Unsupported WellKnownName.');
throw new Error(
this.translate('marksymbolizerParseFailedUnknownWellknownName', { wellKnownName: wellKnownName })
);
}

const strokeColor = getParameterValue(strokeEl, 'stroke', this.readingSldVersion);
Expand Down Expand Up @@ -1083,7 +1166,7 @@ export class SldStyleParser implements StyleParser<string> {
const cmEntries = colorMapEntries.map((cm) => {
const color = getAttribute(cm, 'color');
if (!color) {
throw new Error('Cannot parse ColorMapEntries. color is undefined.');
throw new Error(this.translate('colorMapEntriesParseFailedColorUndefined'));
}
let quantity = getAttribute(cm, 'quantity');
if (quantity) {
Expand Down Expand Up @@ -1119,8 +1202,7 @@ export class SldStyleParser implements StyleParser<string> {
const hasHistogram = !!get(sldContrastEnhancement, 'Histogram');
const hasNormalize = !!get(sldContrastEnhancement, 'Normalize');
if (hasHistogram && hasNormalize) {
throw new Error(`Cannot parse ContrastEnhancement. Histogram and Normalize
are mutually exclusive.`);
throw new Error(this.translate('contrastEnhancParseFailedHistoAndNormalizeMutuallyExclusive'));
} else if (hasHistogram) {
contrastEnhancement.enhancementType = 'histogram';
} else if (hasNormalize) {
Expand Down Expand Up @@ -1166,7 +1248,7 @@ export class SldStyleParser implements StyleParser<string> {
const gray = get(sldChannelSelection, 'GrayChannel');

if (gray && red && blue && green) {
throw new Error('Cannot parse ChannelSelection. RGB and Grayscale are mutually exclusive');
throw new Error(this.translate('channelSelectionParseFailedRGBAndGrayscaleMutuallyExclusive'));
}
if (gray) {
const grayChannel = this.getChannelFromSldChannel(gray);
Expand All @@ -1183,7 +1265,7 @@ export class SldStyleParser implements StyleParser<string> {
greenChannel
};
} else {
throw new Error('Cannot parse ChannelSelection. Red, Green and Blue channels must be defined.');
throw new Error(this.translate('channelSelectionParseFailedRGBChannelsUndefined'));
}
return channelSelection;
}
Expand Down
50 changes: 50 additions & 0 deletions src/SldStyleParser.v1.0.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,56 @@ describe('SldStyleParser implements StyleParser (reading)', () => {
expect(readResult.output).toEqual(function_nested);
});

describe(('displays error messages'), () => {
describe('in English (default locale)', () => {
it('unknown WellknownName', async () => {
const sld = fs.readFileSync('./data/slds/1.0/unknown_wellknownname.sld', 'utf8');
const readResult = await styleParser.readStyle(sld);

expect(readResult.errors).toBeDefined();
expect(readResult.errors?.[0].message.toString())
.contains('MarkSymbolizer cannot be parsed.');
});
});

describe('in French (default messages of the parser)', () => {
it('unknown WellknownName', async () => {
styleParser = new SldStyleParser({
locale: 'fr'
});

const sld = fs.readFileSync('./data/slds/1.0/unknown_wellknownname.sld', 'utf8');
const readResult = await styleParser.readStyle(sld);

expect(readResult.errors).toBeDefined();
expect(readResult.errors?.[0].message.toString())
.contains('Échec de lecture du symbole de type MarkSymbolizer.');
});
});

describe('in French (user defined messages)', () => {
it('unknown WellknownName', async () => {
styleParser = new SldStyleParser({
locale: 'fr',
translations: {
fr: {
marksymbolizerParseFailedUnknownWellknownName:
'Echec de lecture de MarkSymbolizer. WellKnownName {{wellKnownName}} inconnu.',
}
}
});

const sld = fs.readFileSync('./data/slds/1.0/unknown_wellknownname.sld', 'utf8');
const readResult = await styleParser.readStyle(sld);

expect(readResult.errors).toBeDefined();
expect(readResult.errors?.[0].message.toString())
.contains('Echec de lecture de MarkSymbolizer.');
});
});

});

describe('#getFilterFromOperatorAndComparison', () => {
it('is defined', () => {
expect(styleParser.getFilterFromOperatorAndComparison).toBeDefined();
Expand Down

0 comments on commit e01c8c8

Please sign in to comment.