(pronouns) enable translations for slash modifiers

This commit is contained in:
Valentyne Stigloher 2024-01-13 17:30:33 +01:00
parent d792270c80
commit 11d291a24f
14 changed files with 142 additions and 61 deletions

View File

@ -82,6 +82,10 @@ pronouns:
comprehensive:
simple: 'simple'
comprehensive: 'comprehensive'
slashes:
plural: 'plural'
pluralHonorific: 'plural-honorific'
description: 'description'
others: 'Other forms'
othersRaw: 'other'
or: 'or'

View File

@ -86,6 +86,9 @@ pronouns:
comprehensive:
simple: 'häufige'
comprehensive: 'erweitert'
slashes:
plural: 'plural'
description: 'beschreibung'
others: 'Andere Formen'
othersRaw: 'andere'
or: 'oder'

View File

@ -79,6 +79,9 @@ pronouns:
Even though for many people it's incredibly important that people use specific pronouns to talk about them,
others don't mind being addressed in any way as long as the context is clear as to who one talks about.
options: 'check out the options [share]{/pronouns=here}.'
slashes:
plural: 'plural'
description: 'description'
others: 'Other forms'
othersRaw: 'other'
or: 'or'

View File

@ -60,7 +60,7 @@ export default {
continue;
}
const pronounEntity = buildPronoun(pronouns, link);
const pronounEntity = buildPronoun(pronouns, link, this.$translator);
if (pronounEntity) {
pronounOpinions.push({
@ -76,7 +76,7 @@ export default {
if (!this.config.profile?.flags?.defaultPronoun) {
return null;
}
let mainPronoun = buildPronoun(pronouns, this.config.profile.flags.defaultPronoun);
let mainPronoun = buildPronoun(pronouns, this.config.profile.flags.defaultPronoun, this.$translator);
let mainOpinion = -1;
for (const { pronoun, opinion } of this.pronounOpinions) {
if (typeof pronoun === 'string') {

View File

@ -532,7 +532,7 @@ export default {
if (!this.config.profile?.flags?.defaultPronoun) {
return null;
}
let mainPronoun = buildPronoun(pronouns, this.config.profile.flags?.defaultPronoun);
let mainPronoun = buildPronoun(pronouns, this.config.profile.flags?.defaultPronoun, this.$translator);
let mainOpinion = -1;
for (const { value: pronoun, opinion } of this.pronouns) {
const opinionValue = opinions[opinion]?.value || 0;
@ -627,7 +627,7 @@ export default {
}
},
normaliseAndBuildPronoun(pronoun) {
return buildPronoun(pronouns, this.normalisePronoun(pronoun));
return buildPronoun(pronouns, this.normalisePronoun(pronoun), this.$translator);
},
validatePronoun(pronoun) {
pronoun = this.normalisePronoun(pronoun);
@ -637,7 +637,7 @@ export default {
return this.validateAnyPronoun(pronoun) ||
this.config.pronouns.null && this.config.pronouns.null.routes && this.config.pronouns.null.routes.includes(pronoun) ||
this.config.pronouns.mirror && this.config.pronouns.mirror.route === pronoun ||
buildPronoun(pronouns, pronoun)
buildPronoun(pronouns, pronoun, this.$translator)
? null
: 'profile.pronounsNotFound';
},

View File

@ -130,7 +130,7 @@ export default {
const key = this.getPronounKeyFromUrl();
const selectedPronoun = this.config.pronouns.enabled && key
? buildPronoun(pronouns, key)
? buildPronoun(pronouns, key, this.$translator)
: null;
return {

View File

@ -322,7 +322,7 @@ export default {
return null;
}
const slashes = this.selectedPronoun.toStringSlashes();
const slashes = this.selectedPronoun.toStringSlashes(this.$translator);
let link;
if (this.usedBaseEquals) {

View File

@ -1,15 +1,19 @@
import { Router } from 'express';
import SQL from 'sql-template-strings';
import { createCanvas, loadImage } from 'canvas';
import { loadSuml } from '../loader.js';
import { loadSuml, loadSumlFromBase } from '../loader.js';
import avatar from '../avatar.js';
import { buildPronoun, parsePronouns } from '../../src/buildPronoun.js';
import { loadTsv } from '../../src/tsv.js';
import { handleErrorAsync } from '../../src/helpers.js';
import { Translator } from '../../src/translator.js';
import { CacheObject } from '../../src/cache.js';
import { registerLocaleFont } from '../localeFont.js';
const translations = loadSuml('translations');
const baseTranslations = loadSumlFromBase('locale/_base/translations');
const translator = new Translator(translations, baseTranslations, global.config);
const drawCircle = (context, image, x, y, size) => {
context.save();
@ -84,6 +88,7 @@ router.get('/banner/:pronounName*.png', handleErrorAsync(async (req, res) => {
const pronoun = buildPronoun(
parsePronouns(loadTsv(`${__dirname}/../../data/pronouns/pronouns.tsv`)),
pronounName,
translator,
);
if (pronounName === 'zaimki' || !pronoun && pronounName !== global.config.pronouns.any && (!global.config.pronouns || pronounName !== global.config.pronouns.mirror)) {

View File

@ -1,13 +1,19 @@
import { Router } from 'express';
import { loadTsv } from '../loader.js';
import { loadSuml, loadSumlFromBase, loadTsv } from '../loader.js';
import { buildPronoun, parsePronouns } from '../../src/buildPronoun.js';
import { buildList, handleErrorAsync } from '../../src/helpers.js';
import { Example } from '../../src/classes.js';
import { caches } from '../../src/cache.js';
import { Translator } from '../../src/translator.js';
import md5 from 'js-md5';
import assert from 'assert';
import fetch from 'node-fetch';
const translations = loadSuml('translations');
const baseTranslations = loadSumlFromBase('locale/_base/translations');
const translator = new Translator(translations, baseTranslations, global.config);
const buildExample = (e) => new Example(
Example.parse(e.singular),
Example.parse(e.plural || e.singular),
@ -52,6 +58,7 @@ router.get('/pronouns/:pronoun*', handleErrorAsync(async (req, res) => {
const pronoun = buildPronoun(
parsePronouns(loadTsv('pronouns/pronouns')),
req.params.pronoun + req.params[0],
translator,
);
if (pronoun) {
pronoun.examples = addExamples(pronoun, requestExamples(req.query.examples));
@ -64,6 +71,7 @@ router.get('/pronouns-name/:pronoun*', handleErrorAsync(async (req, res) => {
const pronoun = buildPronoun(
parsePronouns(loadTsv('pronouns/pronouns')),
(req.params.pronoun + req.params[0]).toLowerCase(),
translator,
);
if (!pronoun) {
return res.status(404).json({ error: 'Not found' });

View File

@ -58,7 +58,23 @@ const buildPronounFromTemplate = (key, template) => {
);
};
const buildPronounFromSlashes = (config, path) => {
const isModifier = (chunk, key, translator) => {
// use both locale and base translations to ensure backwards compatibility if key gets translated
return chunk === `:${translator.translate(key)}` || chunk === `:${translator.get(key, false, true)}`;
};
const extractModifierValue = (chunk, key, translator) => {
// use both locale and base translations to ensure backwards compatibility if key gets translated
const prefixes = [`:${translator.translate(key)}=`, `:${translator.get(key, false, true)}=`];
for (const prefix of prefixes) {
if (chunk.startsWith(prefix)) {
return chunk.substring(prefix.length);
}
}
return null;
};
const buildPronounFromSlashes = (config, path, translator) => {
const chunks = path.split(/(?<!`)\//);
let plural = false;
let pluralHonorific = false;
@ -66,14 +82,16 @@ const buildPronounFromSlashes = (config, path) => {
const morphemeChunks = [];
for (const chunk of chunks) {
if (chunk.startsWith(':')) {
if (config.pronouns.plurals && chunk === ':plural') {
if (config.pronouns.plurals && isModifier(chunk, 'pronouns.slashes.plural', translator)) {
plural = true;
} else if (config.pronouns.plurals && config.pronouns.honorifics && chunk === ':plural-honorific') {
} else if (config.pronouns.plurals && config.pronouns.honorifics &&
isModifier(chunk, 'pronouns.slashes.pluralHonorific', translator)) {
pluralHonorific = true;
} else {
const descriptionModifierPrefix = ':description=';
if (chunk.startsWith(descriptionModifierPrefix)) {
description = unescapeControlSymbols(chunk.substring(descriptionModifierPrefix.length));
const descriptionModifierValue =
extractModifierValue(chunk, 'pronouns.slashes.description', translator);
if (descriptionModifierValue) {
description = unescapeControlSymbols(descriptionModifierValue);
}
}
} else {
@ -112,7 +130,7 @@ const buildPronounFromSlashes = (config, path) => {
}
};
export const buildPronoun = (pronouns, path) => {
export const buildPronoun = (pronouns, path, translator) => {
if (!path) {
return null;
}
@ -180,7 +198,7 @@ export const buildPronoun = (pronouns, path) => {
}
if (!pronoun && config.pronouns.slashes !== false) {
return buildPronounFromSlashes(config, path);
return buildPronounFromSlashes(config, path, translator);
}
return pronoun;

View File

@ -467,7 +467,7 @@ export class Pronoun {
return this.toArray().join(',');
}
toStringSlashes() {
toStringSlashes(translator) {
if (!config.pronouns.slashes) {
return null;
}
@ -490,13 +490,14 @@ export class Pronoun {
});
if (this.plural[0]) {
chunks.push(':plural');
chunks.push(`:${translator.translate('pronouns.slashes.plural')}`);
}
if (this.pluralHonorific[0]) {
chunks.push(':plural-honorific');
chunks.push(`:${translator.translate('pronouns.slashes.pluralHonorific')}`);
}
if (this.description) {
chunks.push(`:description=${escapeControlSymbols(this.description)}`);
const escapedDescription = escapeControlSymbols(this.description);
chunks.push(`:${translator.translate('pronouns.slashes.description')}=${escapedDescription}`);
}
// encode a trailing space so that it does not get removed during a request

View File

@ -63,6 +63,22 @@ export function listMissingTranslations(translations, baseTranslations, config)
return false;
}
if (!config.pronouns.slashes && keyMatches('pronouns.slashes.')) {
return false;
}
if (!config.pronouns.plurals && keyMatches(
'pronouns.plural',
'pronouns.slashes.plural',
'pronouns.slashes.pluralHonorific',
)) {
return false;
}
if (!config.pronouns.honorifics && keyMatches('pronouns.slashes.pluralHonorific')) {
return false;
}
if (!config.pronouns.comprehensive && keyMatches('pronouns.comprehensive.')) {
return false;
}

View File

@ -1,4 +1,16 @@
import { beforeEach, describe, expect, jest, test } from '@jest/globals';
import { Translator } from '../src/translator.js';
const translations = {
pronouns: {
slashes: {
plural: 'plural',
pluralHonorific: 'plural-honorific',
description: 'description',
},
},
};
const translator = new Translator(translations, translations, []);
// workaround to be independent of the current selected locale
jest.unstable_mockModule('../data/pronouns/morphemes.js', () => {
@ -21,13 +33,13 @@ beforeEach(() => {
});
test('finds pronouns by canonical name', () => {
const actual = expect(buildPronoun(pronouns, 'they'));
const actual = expect(buildPronoun(pronouns, 'they', translator));
actual.toBeDefined();
actual.toBe(pronouns.they);
});
test('finds pronouns by alias', () => {
const actual = expect(buildPronoun(pronouns, 'they/them'));
const actual = expect(buildPronoun(pronouns, 'they/them', translator));
actual.toBeDefined();
actual.toBe(pronouns.they);
});
@ -46,7 +58,7 @@ describe('when configured that emojiself pronouns are available', () => {
});
test('builds pronouns from emoji', () => {
const actual = expect(buildPronoun(pronouns, '💙'));
const actual = expect(buildPronoun(pronouns, '💙', translator));
actual.toBeDefined();
actual.toEqual(new Pronoun(
'💙',
@ -82,7 +94,7 @@ describe('when configured that null pronouns are available', () => {
});
test('builds pronouns from name', () => {
const actual = expect(buildPronoun(pronouns, ':S'));
const actual = expect(buildPronoun(pronouns, ':S', translator));
actual.toBeDefined();
actual.toEqual(new Pronoun(
'S',
@ -103,7 +115,7 @@ describe('when configured that null pronouns are available', () => {
));
});
test('builds nothing if name too long', () => {
expect(buildPronoun(pronouns, ':Abcdefghijklmnopqrstuvwxyz')).toBeUndefined();
expect(buildPronoun(pronouns, ':Abcdefghijklmnopqrstuvwxyz', translator)).toBeUndefined();
});
describe('with conditional placeholders', () => {
@ -112,10 +124,10 @@ describe('when configured that null pronouns are available', () => {
});
test.each(['S', 'Ringelnatz', 'Max', 'X'])('builds morpheme conditionally if name matches', (name) => {
expect(buildPronoun(pronouns, `:${name}`).morphemes.possessive_pronoun).toBe(`${name}`);
expect(buildPronoun(pronouns, `:${name}`, translator).morphemes.possessive_pronoun).toBe(`${name}`);
});
test.each(['Sofi', 'Xavier'])('builds morpheme by default if name does not match', (name) => {
expect(buildPronoun(pronouns, `:${name}`).morphemes.possessive_pronoun).toBe(`${name}s`);
expect(buildPronoun(pronouns, `:${name}`, translator).morphemes.possessive_pronoun).toBe(`${name}s`);
});
});
});
@ -127,27 +139,31 @@ describe('when configured that slashes contain all morphemes', () => {
});
test('builds generated pronoun from all morphemes', () => {
const actual = expect(buildPronoun(pronouns, 'ae/aer/aer/aers/aerself'));
const actual = expect(buildPronoun(pronouns, 'ae/aer/aer/aers/aerself', translator));
actual.toBeDefined();
actual.toEqual(generatedPronouns.aer);
});
test('unescapes morphemes', () => {
const actual = expect(buildPronoun(pronouns, 's`/he/hir/hir/hirs/hirself'));
const actual = expect(buildPronoun(pronouns, 's`/he/hir/hir/hirs/hirself', translator));
actual.toBeDefined();
actual.toEqual(generatedPronouns.sSlashHe);
});
test('builds generated pronoun from all required morphemes and plural modifier', () => {
const actual = expect(buildPronoun(pronouns, 'ae/aer/aer/aers/aerselves/:plural'));
const actual = expect(buildPronoun(pronouns, 'ae/aer/aer/aers/aerselves/:plural', translator));
actual.toBeDefined();
actual.toEqual(generatedPronouns.aerPlural);
});
test('builds generated pronoun from all required morphemes and plural honorific modifier', () => {
const actual = expect(buildPronoun(pronouns, 'ae/aer/aer/aers/aerselves/:plural-honorific'));
const actual = expect(buildPronoun(pronouns, 'ae/aer/aer/aers/aerselves/:plural-honorific', translator));
actual.toBeDefined();
actual.toEqual(generatedPronouns.aerPluralHonorific);
});
test('builds generated pronoun from all required morphemes and description', () => {
const actual = expect(buildPronoun(pronouns, 'ae/aer/aer/aers/aerself/:description=Neopronoun “ae” `/ “æ”'));
const actual = expect(buildPronoun(
pronouns,
'ae/aer/aer/aers/aerself/:description=Neopronoun “ae” `/ “æ”',
translator,
));
actual.toBeDefined();
actual.toEqual(generatedPronouns.aerWithDescription);
});
@ -155,35 +171,37 @@ describe('when configured that slashes contain all morphemes', () => {
const actual = expect(buildPronoun(
pronouns,
'ae/aer/:description=Neopronoun “ae” `/ “æ”/:plural/aer/aers/aerselves',
translator,
));
actual.toBeDefined();
actual.toEqual(generatedPronouns.aerPluralWithDescription);
});
test('builds generated pronoun with some morphemes empty', () => {
const actual = expect(buildPronoun(pronouns, 'ae/aer/aer/ /aerself'));
const actual = expect(buildPronoun(pronouns, 'ae/aer/aer/ /aerself', translator));
actual.toBeDefined();
actual.toEqual(generatedPronouns.aerWithEmptyPossessivePronoun);
});
test('builds generated pronoun with morpheme at end empty', () => {
const actual = expect(buildPronoun(pronouns, 'ae/aer/aer/aers/'));
const actual = expect(buildPronoun(pronouns, 'ae/aer/aer/aers/', translator));
actual.toBeDefined();
actual.toEqual(generatedPronouns.aerWithEmptyReflexive);
});
test('builds generated pronoun with some morphemes unset', () => {
const actual = expect(buildPronoun(pronouns, 'ae/aer/aer/~/aerself'));
const actual = expect(buildPronoun(pronouns, 'ae/aer/aer/~/aerself', translator));
actual.toBeDefined();
actual.toEqual(generatedPronouns.aerWithUnsetPossessivePronoun);
});
test('builds nothing if morphemes are missing', () => {
expect(buildPronoun(pronouns, 'ae/aer/aer/aerself')).toBeUndefined();
expect(buildPronoun(pronouns, 'ae/aer/aer/aerself', translator)).toBeUndefined();
});
test('builds nothing if too many morphemes are given', () => {
expect(buildPronoun(pronouns, 'ae/aer/aer/aers/aerself/aer')).toBeUndefined();
expect(buildPronoun(pronouns, 'ae/aer/aer/aers/aerself/aer', translator)).toBeUndefined();
});
test('builds nothing if description too long', () => {
expect(buildPronoun(
pronouns,
'ae/aer/aer/aers/aerself/:description=Neopronoun “ae” `/ “æ” which is my favorite so you should use it too',
translator,
)).toBeNull();
});
});
@ -193,15 +211,15 @@ describe('when configured that slashes contain some morphemes', () => {
});
test('builds generated pronoun from all required morphemes', () => {
const actual = expect(buildPronoun(pronouns, 'ae/aer/aer/aerself'));
const actual = expect(buildPronoun(pronouns, 'ae/aer/aer/aerself', translator));
actual.toBeDefined();
actual.toEqual(generatedPronouns.aerWithUnsetPossessivePronoun);
});
test('builds nothing if morphemes are missing', () => {
expect(buildPronoun(pronouns, 'ae/aer/aer')).toBeUndefined();
expect(buildPronoun(pronouns, 'ae/aer/aer', translator)).toBeUndefined();
});
test('builds nothing if too many morphemes are given', () => {
expect(buildPronoun(pronouns, 'ae/aer/aer/aers/aerself')).toBeUndefined();
expect(buildPronoun(pronouns, 'ae/aer/aer/aers/aerself', translator)).toBeUndefined();
});
});
describe('when configured that slashes cannot contain morphemes', () => {
@ -214,23 +232,23 @@ describe('when configured that slashes cannot contain morphemes', () => {
test.each([3, 4, 5, 6])('builds nothing if %d morphemes are given', (count) => {
const path = pathBase.split('/').slice(0, count)
.join('/');
expect(buildPronoun(pronouns, path.slice(0, count))).toBeUndefined();
expect(buildPronoun(pronouns, path.slice(0, count), translator)).toBeUndefined();
});
});
describe('building generated pronouns from commas', () => {
test('succeeds with all parts present', () => {
const actual = expect(buildPronoun(pronouns, 'ae,aer,aer,aers,aerself,0,'));
const actual = expect(buildPronoun(pronouns, 'ae,aer,aer,aers,aerself,0,', translator));
actual.toBeDefined();
actual.toEqual(generatedPronouns.aer);
});
test('succeeds with description missing present', () => {
const actual = expect(buildPronoun(pronouns, 'ae,aer,aer,aers,aerself,0'));
const actual = expect(buildPronoun(pronouns, 'ae,aer,aer,aers,aerself,0', translator));
actual.toBeDefined();
actual.toEqual(generatedPronouns.aer);
});
test('succeeds with base pronoun and some custom morphemes', () => {
const actual = expect(buildPronoun(pronouns, 'they,!2,aers,!2,'));
const actual = expect(buildPronoun(pronouns, 'they,!2,aers,!2,', translator));
actual.toBeDefined();
actual.toEqual(new Pronoun(
'they/them',
@ -251,19 +269,19 @@ describe('building generated pronouns from commas', () => {
));
});
test('fails when too few parts are given', () => {
expect(buildPronoun(pronouns, 'ae,aer,aer,aers,aerself')).toBeUndefined();
expect(buildPronoun(pronouns, 'ae, translator,aer,aer,aers,aerself', translator)).toBeUndefined();
});
test('fails when many few parts are given', () => {
expect(buildPronoun(pronouns, 'ae,aer,aer,aers,aerself,aersing,0,')).toBeUndefined();
expect(buildPronoun(pronouns, 'ae, translator,aer,aer,aers,aerself,aersing,0,', translator)).toBeUndefined();
});
test('fails when base pronoun is unknown', () => {
expect(buildPronoun(pronouns, 's/he,!2,aers,!2,')).toBeUndefined();
expect(buildPronoun(pronouns, 's/he, translator,!2,aers,!2,', translator)).toBeUndefined();
});
});
describe('builds interchangable pronouns from ampersand', () => {
test('two interchangable pronouns', () => {
const actual = expect(buildPronoun(pronouns, 'he&she'));
const actual = expect(buildPronoun(pronouns, 'he&she', translator));
actual.toBeDefined();
actual.toEqual(new Pronoun(
'he&she',
@ -284,7 +302,7 @@ describe('builds interchangable pronouns from ampersand', () => {
));
});
test('three interchangable pronouns', () => {
const actual = expect(buildPronoun(pronouns, 'he&she&they'));
const actual = expect(buildPronoun(pronouns, 'he&she&they', translator));
actual.toBeDefined();
actual.toEqual(new Pronoun(
'he&she&they',

View File

@ -8,6 +8,11 @@ const translations = {
any: {
short: 'any',
},
slashes: {
plural: 'plural',
pluralHonorific: 'plural-honorific',
description: 'description',
},
},
};
const translator = new Translator(translations, translations, []);
@ -39,7 +44,7 @@ describe('formatting a pronoun with slashes', () => {
});
test('yields no result', () => {
expect(generatedPronouns.aer.toStringSlashes()).toBeNull();
expect(generatedPronouns.aer.toStringSlashes(translator)).toBeNull();
});
});
describe('when configured that slashes contain all morphemes', () => {
@ -48,39 +53,39 @@ describe('formatting a pronoun with slashes', () => {
});
test('chunks contain all morphemes', () => {
expect(generatedPronouns.aer.toStringSlashes())
expect(generatedPronouns.aer.toStringSlashes(translator))
.toEqual('ae/aer/aer/aers/aerself');
});
test('morphemes with control symbols gets escaped', () => {
expect(generatedPronouns.sSlashHe.toStringSlashes())
expect(generatedPronouns.sSlashHe.toStringSlashes(translator))
.toEqual('s`/he/hir/hir/hirs/hirself');
});
test('empty morphemes receive space as placeholder', () => {
expect(generatedPronouns.aerWithEmptyPossessivePronoun.toStringSlashes())
expect(generatedPronouns.aerWithEmptyPossessivePronoun.toStringSlashes(translator))
.toEqual('ae/aer/aer/ /aerself');
});
test('empty morphemes at end receive url-encoded space as placeholder', () => {
expect(generatedPronouns.aerWithEmptyReflexive.toStringSlashes())
expect(generatedPronouns.aerWithEmptyReflexive.toStringSlashes(translator))
.toEqual('ae/aer/aer/aers/%20');
});
test('unset morphemes receive tilde as placeholder', () => {
expect(generatedPronouns.aerWithUnsetPossessivePronoun.toStringSlashes())
expect(generatedPronouns.aerWithUnsetPossessivePronoun.toStringSlashes(translator))
.toEqual('ae/aer/aer/~/aerself');
});
test('adds plural modifier if necessary', () => {
expect(generatedPronouns.aerPlural.toStringSlashes())
expect(generatedPronouns.aerPlural.toStringSlashes(translator))
.toEqual('ae/aer/aer/aers/aerselves/:plural');
});
test('adds plural honorific modifier if necessary', () => {
expect(generatedPronouns.aerPluralHonorific.toStringSlashes())
expect(generatedPronouns.aerPluralHonorific.toStringSlashes(translator))
.toEqual('ae/aer/aer/aers/aerselves/:plural-honorific');
});
test('adds escaped description if necessary', () => {
expect(generatedPronouns.aerWithDescription.toStringSlashes())
expect(generatedPronouns.aerWithDescription.toStringSlashes(translator))
.toEqual('ae/aer/aer/aers/aerself/:description=Neopronoun “ae” `/ “æ”');
});
test('adds multiple modifiers if necessary', () => {
expect(generatedPronouns.aerPluralWithDescription.toStringSlashes())
expect(generatedPronouns.aerPluralWithDescription.toStringSlashes(translator))
.toEqual('ae/aer/aer/aers/aerselves/:plural/:description=Neopronoun “ae” `/ “æ”');
});
});
@ -92,7 +97,7 @@ describe('formatting a pronoun with slashes', () => {
});
test('chunks contain configured morphemes', () => {
expect(generatedPronouns.aer.toStringSlashes()).toEqual('ae/aer/aer/aerself');
expect(generatedPronouns.aer.toStringSlashes(translator)).toEqual('ae/aer/aer/aerself');
});
});
});