Merge branch '138-pronoun-parsing-improvements' into 'main'

Improvements to slash pronoun parsing

Closes #138

See merge request PronounsPage/PronounsPage!409
This commit is contained in:
Valentyne Stigloher 2024-01-26 12:34:22 +00:00
commit dac647d3e3
32 changed files with 733 additions and 267 deletions

View File

@ -194,8 +194,11 @@ import { groupBy } from '../src/helpers.js';
export default {
data() {
return {
links: groupBy([...getContactLinks(this.config), ...getSocialLinks(this.config)], (l) => l.group),
supportLinks: [...getSupportLinks()],
links: groupBy(
[...getContactLinks(this.config, this.$translator), ...getSocialLinks(this.config)],
(l) => l.group,
),
supportLinks: [...getSupportLinks(this.$translator)],
versionFrontend: process.env.VERSION,
versionBackend: undefined,
adsVisible: false,

View File

@ -30,7 +30,7 @@ import { getSupportLinks } from '../src/contact.js';
export default {
data() {
return {
links: [...getSupportLinks()],
links: [...getSupportLinks(this.$translator)],
};
},
};

View File

@ -11,7 +11,6 @@
</template>
<script>
import translator from '../src/translator.js';
import { mapState } from 'vuex';
export default {
@ -34,8 +33,8 @@ export default {
},
txt() {
return this.modified
? translator.applyParams(this.translationChanges[this.key], this.params || {})
: translator.translate(this.key, this.params || {});
? this.$translator.applyParams(this.translationChanges[this.key], this.params || {})
: this.$translator.translate(this.key, this.params || {});
},
},
methods: {
@ -47,12 +46,12 @@ export default {
e.preventDefault();
e.stopPropagation();
const base = translator.get(this.key, false, true);
const base = this.$translator.get(this.key, false, true);
const newValue = await this.$editor(
this.modified
? this.translationChanges[this.key]
: translator.get(this.key),
: this.$translator.get(this.key),
{
icon: 'language',
header: this.key,
@ -69,7 +68,7 @@ export default {
);
if (newValue !== undefined) {
this.$store.commit('translate', { key: this.key, newValue });
this.$store.commit('translate', { translator: this.$translator, key: this.key, newValue });
this.$cookies.set('translations', this.$store.state.translationChanges);
}
},

View File

@ -43,6 +43,7 @@ home:
base: 'Based on'
alt: 'You can also enter interchangeable forms in each field separately, eg. <code>him&her</code> = “him” or “her”.'
pronunciation: 'You can also specify the pronunciation after a pipe character, using IPA, eg. <code>faerself|fɛɹsɛlf</code> = “faerself” pronounced as /fɛɹsɛlf/.'
compressedAlternative: 'Use the compressed formatting alternative'
whatisit: 'What''s the deal with pronouns?'
mission:
header: 'Our mission'
@ -81,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

@ -4,30 +4,30 @@ sie,sie/ihr Femininum TRUE sie ihrer ihr sie ihre ihrer ihrer ihre ihr ihres ihr
es,es/sein,es/ihm Neutrum TRUE es seiner ihm es seine seiner seiner seine sein seines seinem seinen sein seines seinem sein das des dem das dieses dieses diesem dieses FALSE FALSE TRUE
es/ihr Neutrum mit femininen Formen FALSE es ihrer ihr es ihre ihrer ihrer ihre ihr ihres ihrem ihren ihr ihres ihrem ihr das der der das dieses dieser dieser dieses FALSE FALSE TRUE Wie {/es=„es/sein“}, wobei Wörter, bei denen das {/es=Neutrum} identisch mit dem {/er=Maskulinum} ist, durch {/sie=feminine} Formen ersetzt werden
es/denen Neutrum mit inflexiven Formen FALSE es derer denen es deren deren deren deren deren deren deren deren deren deren deren deren das des dem das dieses dieses diesem dieses FALSE FALSE TRUE Wie {/es=„es/sein“}, wobei die meisten Wörter, bei denen das {/es=Neutrum} identisch mit dem {/er=Maskulinum} ist, durch inflexive Formen ersetzt werden, wie sie auch in {/dey/denen=„dey/denen“} verwendet werden
dey,dey/denen Neopronomen „dey“ FALSE dey derer denen dey deren deren deren deren deren deren deren deren deren deren deren deren FALSE FALSE TRUE Im Akkusativ ist ebenfalls {/dey/denen/demm=„demm”} üblich. pronoun_a
dey/denen/demm Neopronomen „dey“ FALSE dey derer denen demm deren deren deren deren deren deren deren deren deren deren deren deren FALSE FALSE TRUE Im Akkusativ ist ebenfalls {/dey/denen=„dey”} üblich. pronoun_a
die,die/denen Neopronomen „die“ FALSE die derer denen die deren deren deren deren deren deren deren deren deren deren deren deren FALSE FALSE TRUE
el,el/em Neopronomen „el“ FALSE el|ɛl emser|ɛmzɐ em|ɛm en|ɛn emse|ɛmzə emser|ɛmzɐ emser|ɛmzɐ emse|ɛmzə ems|ɛmz emses|ɛmzəs emsem|ɛmzəm emsen|ɛmzən ems|ɛmz emses|ɛmzəs emsem|ɛmzəm ems|ɛmz FALSE FALSE TRUE
em,em/em Neopronomen „em“ FALSE em|ɛm emser|ɛmzɐ em|ɛm em|ɛm ems|ɛmz ems|ɛmz ems|ɛmz ems|ɛmz ems|ɛmz ems|ɛmz ems|ɛmz ems|ɛmz ems|ɛmz ems|ɛmz ems|ɛmz ems|ɛmz FALSE FALSE TRUE
dey,dey/denen Neopronomen „dey“ FALSE dey derer denen dey deren deren deren deren deren deren deren deren deren deren deren deren ~ ~ ~ ~ ~ ~ ~ ~ FALSE FALSE TRUE Im Akkusativ ist ebenfalls {/dey/denen/demm=„demm”} üblich. pronoun_a
dey/denen/demm Neopronomen „dey“ FALSE dey derer denen demm deren deren deren deren deren deren deren deren deren deren deren deren ~ ~ ~ ~ ~ ~ ~ ~ FALSE FALSE TRUE Im Akkusativ ist ebenfalls {/dey/denen=„dey”} üblich. pronoun_a
die,die/denen Neopronomen „die“ FALSE die derer denen die deren deren deren deren deren deren deren deren deren deren deren deren ~ ~ ~ ~ ~ ~ ~ ~ FALSE FALSE TRUE
el,el/em Neopronomen „el“ FALSE el|ɛl emser|ɛmzɐ em|ɛm en|ɛn emse|ɛmzə emser|ɛmzɐ emser|ɛmzɐ emse|ɛmzə ems|ɛmz emses|ɛmzəs emsem|ɛmzəm emsen|ɛmzən ems|ɛmz emses|ɛmzəs emsem|ɛmzəm ems|ɛmz ~ ~ ~ ~ ~ ~ ~ ~ FALSE FALSE TRUE
em,em/em Neopronomen „em“ FALSE em|ɛm emser|ɛmzɐ em|ɛm em|ɛm ems|ɛmz ems|ɛmz ems|ɛmz ems|ɛmz ems|ɛmz ems|ɛmz ems|ɛmz ems|ɛmz ems|ɛmz ems|ɛmz ems|ɛmz ems|ɛmz ~ ~ ~ ~ ~ ~ ~ ~ FALSE FALSE TRUE
en,en/en Neopronomen „en“ FALSE en|ɛn enser|ɛnzɐ en|ɛn en|ɛn ense|ɛnzə enser|ɛnzɐ enser|ɛnzɐ ense|ɛnzə enses|ɛnzəs enses|ɛnzəs ensem|ɛnzəm ensen|ɛnzən ens|ɛnz enses|ɛnzəs ensem|ɛnzəm ens|ɛnz de|deː ders|dɛrz derm|dɛrm de|deː de|deː dersen|dɛrzən derm|dɛrm de|deː FALSE FALSE TRUE Erstellt vom österreichischen LGBTIQA+-Kongress 2018 in St. Pölten
en/em Neopronomen „en“ FALSE en|ɛn enser|ɛnzɐ em|ɛm en|ɛn ense|ɛnzə enser|ɛnzɐ enser|ɛnzɐ ense|ɛnzə ens|ɛnz enses|ɛnzəs ensem|ɛnzəm ensen|ɛnzən ens|ɛnz enses|ɛnzəs ensem|ɛnzəm ens|ɛnz de|deː ders|dɛrz derm|dɛrm de|deː de|deː dersen|dɛrzən derm|dɛrm de|deː FALSE FALSE TRUE Erstellt vom {https://geschlechtsneutral.net/=Verein für geschlechtsneutrales Deutsch}
ey,ey/emm Neopronomen „ey“ FALSE ey eyser emm emm eyse eyser eyser eyse eys eyses eysem eysen eys eyses eysem eys FALSE FALSE TRUE
et,et/siem Neopronomen „et“ FALSE et sierer siem sien siere sierer sierer siere sier sieres sierem sieren sier sieres sierem sieren dat diesers diem dien dieset diesigen diesetne FALSE FALSE TRUE
hän,hän/sim Neopronomen „hän“ FALSE hän sirer sim sin sire sirer sirer sire sir sires sirem siren sir sires sirem siren FALSE FALSE TRUE
ey,ey/emm Neopronomen „ey“ FALSE ey eyser emm emm eyse eyser eyser eyse eys eyses eysem eysen eys eyses eysem eys ~ ~ ~ ~ ~ ~ ~ ~ FALSE FALSE TRUE
et,et/siem Neopronomen „et“ FALSE et sierer siem sien siere sierer sierer siere sier sieres sierem sieren sier sieres sierem sieren dat diesers diem dien dieset ~ diesigen diesetne FALSE FALSE TRUE
hän,hän/sim Neopronomen „hän“ FALSE hän sirer sim sin sire sirer sirer sire sir sires sirem siren sir sires sirem siren ~ ~ ~ ~ ~ ~ ~ ~ FALSE FALSE TRUE
hen,hen/hen,hen/hem Neopronomen „hen“ FALSE hen henser hem hen hens hens hens hens hens hens hens hens hens hens hens hens dai dais dam dai diesai diesais deisam diesai FALSE FALSE TRUE Aus dem Schwedischen, siehe: {https://sv.pronouns.page/hen=hen/henom}. Auch verwendet im {https://geschlechtsneutralesdeutsch.com/=NoNa-System}.
hie,hie/hein,hie/hiem Neopronomen „hie“ FALSE hie heiner hiem hie heine heiner heiner heine hein heines heinem heinen hein heines heinem hein FALSE FALSE TRUE
iks,iks/iks Neopronomen „iks“ FALSE iks ikser iks iks ikses ikses ikses ikses ikses ikses ikses ikses ikses ikses ikses ikses deks dieseks FALSE FALSE TRUE
ind,ind/inde Neopronomen „ind“ FALSE ind|ɪnd indser|ɪndzɐ inde|ɪndə ind|ɪnd indse|ɪndzə indser|ɪndzɐ indser|ɪndzɐ indse|ɪndzə inds|ɪndz indsens|ɪndzəs indsem|ɪndzəm indsen|ɪndzən inds|ɪndz indsens|ɪndzəns indsem|ɪndzəm inds|ɪndz FALSE FALSE TRUE Vom Wort „Individuum“
mensch,mensch/mensch Neopronomen „mensch” FALSE mensch menscher mensch mensch menschs menschs menschs menschs menschs menschs menschs menschs menschs menschs menschs menschs FALSE FALSE TRUE Oft wird „mensch” auch als {https://de.wikipedia.org/wiki/Generalisierendes_Personalpronomen=generalisierendes Pronomen} benutzt (statt „man”).@Eher als eines von mehreren Pronomen verwendet ({https://nibi.space/pronomen#mensch=laut nibi.space}).
hie,hie/hein,hie/hiem Neopronomen „hie“ FALSE hie heiner hiem hie heine heiner heiner heine hein heines heinem heinen hein heines heinem hein ~ ~ ~ ~ ~ ~ ~ ~ FALSE FALSE TRUE
iks,iks/iks Neopronomen „iks“ FALSE iks ikser iks iks ikses ikses ikses ikses ikses ikses ikses ikses ikses ikses ikses ikses deks ~ ~ ~ dieseks ~ ~ ~ FALSE FALSE TRUE
ind,ind/inde Neopronomen „ind“ FALSE ind|ɪnd indser|ɪndzɐ inde|ɪndə ind|ɪnd indse|ɪndzə indser|ɪndzɐ indser|ɪndzɐ indse|ɪndzə inds|ɪndz indsens|ɪndzəs indsem|ɪndzəm indsen|ɪndzən inds|ɪndz indsens|ɪndzəns indsem|ɪndzəm inds|ɪndz ~ ~ ~ ~ ~ ~ ~ ~ FALSE FALSE TRUE Vom Wort „Individuum“
mensch,mensch/mensch Neopronomen „mensch” FALSE mensch menscher mensch mensch menschs menschs menschs menschs menschs menschs menschs menschs menschs menschs menschs menschs ~ ~ ~ ~ ~ ~ ~ ~ FALSE FALSE TRUE Oft wird „mensch” auch als {https://de.wikipedia.org/wiki/Generalisierendes_Personalpronomen=generalisierendes Pronomen} benutzt (statt „man”).@Eher als eines von mehreren Pronomen verwendet ({https://nibi.space/pronomen#mensch=laut nibi.space}).
nin,nin/nim Neopronomen „nin“ FALSE nin nimser nim nin nimse nimser nimser nimse nims nimses nimsem nimsen nims nimses nimsem nims din dins dim din diesin diesins diesim diesin FALSE FALSE TRUE Erstellt von {https://www.geschlechtsneutral.com/lit/Liminalis-2008-Sylvain-Balzer.pdf=Cabala de Sylvain und Carsten Balzer}
oj,oj/juj,oj/ojm Neopronomen „oj“ FALSE oj jujer ojm ojn juje jujer jujer juje juj jujes jusem jusen jus juses jusem jus FALSE FALSE TRUE Erstellt von {https://www.frumble.de/blog/2021/03/26/ueberlegungen-zu-einer-genderneutralen-deutschen-grammatik=Frumble}. Siehe auch: {/substantive#Ojum=Ojum}.
per,per/per Neopronomen „per“ FALSE per perer per per pers pers pers pers pers pers pers pers pers pers pers pers FALSE FALSE TRUE Vom Wort „Person“
ser,ser/sem Neopronomen „ser“ FALSE ser seser sem sen ses ses ses ses ses ses ses ses ses ses ses ses FALSE FALSE TRUE
oj,oj/juj,oj/ojm Neopronomen „oj“ FALSE oj jujer ojm ojn juje jujer jujer juje juj jujes jusem jusen jus juses jusem jus ~ ~ ~ ~ ~ ~ ~ ~ FALSE FALSE TRUE Erstellt von {https://www.frumble.de/blog/2021/03/26/ueberlegungen-zu-einer-genderneutralen-deutschen-grammatik=Frumble}. Siehe auch: {/substantive#Ojum=Ojum}.
per,per/per Neopronomen „per“ FALSE per perer per per pers pers pers pers pers pers pers pers pers pers pers pers ~ ~ ~ ~ ~ ~ ~ ~ FALSE FALSE TRUE Vom Wort „Person“
ser,ser/sem Neopronomen „ser“ FALSE ser seser sem sen ses ses ses ses ses ses ses ses ses ses ses ses ~ ~ ~ ~ ~ ~ ~ ~ FALSE FALSE TRUE
sier,sier/siem Neopronomen „sier“ FALSE sier|zi:ɐ̯ sieser|zi:sɐ siem|zi:m sien|zi:n siese|zi:zə sieser|zi:zɐ sieser|zi:zɐ siese|zi:zə sies|zi:z sieses|zi:zəs siesem|zi:zəm siesen|zi:zən sies|zi:z sieses|zi:zəs siesem|zi:zəm sies|zi:z dier|di:ɐ̯ dies|di:s diem|di:m dien|di:n dier|di:ɐ̯ dies|di:s diem|di:m dien|di:n FALSE FALSE TRUE Erstellt von {https://www.annaheger.de/pronomen21/=Illi Anna Heger}
they,they/them Äquivalent zu englischem „they“ FALSE they them them them their their their their their their their their their their their their FALSE FALSE TRUE Aus dem Englischen, sieh: {https://en.pronouns.page/they=they/them}
they,they/them Äquivalent zu englischem „they“ FALSE they them them them their their their their their their their their their their their their ~ ~ ~ ~ ~ ~ ~ ~ FALSE FALSE TRUE Aus dem Englischen, sieh: {https://en.pronouns.page/they=they/them}
xier,xier/xiem Neopronomen „xier“ FALSE xier|ksi:ɐ̯ xieser|ksi:sɐ xiem|ksi:m xien|ksi:n xiese|ksi:zə xieser|ksi:ɐ̯sɐ xieser|ksi:ɐ̯sɐ xiese|ksi:zə xies|ksi:z xieses|ksi:zəs xiesem|ksi:zəm xiesen|ksi:zən xies|ksi:z xieses|ksi:zəs xiesem|ksi:zəm xies|ksi:z dier|di:ɐ̯ dies|di:s diem|di:m dien|di:n dier|di:ɐ̯ dies|di:s diem|di:m dien|di:n FALSE FALSE TRUE Erstellt von {https://www.annaheger.de/pronomen33/=Illi Anna Heger}.
zet,zet/zerm Neopronomen „zet“ FALSE zet zerner zerm zern zets zets zets zets zets zets zets zets zets zets zets zets FALSE FALSE TRUE
zet,zet/zerm Neopronomen „zet“ FALSE zet zerner zerm zern zets zets zets zets zets zets zets zets zets zets zets zets ~ ~ ~ ~ ~ ~ ~ ~ FALSE FALSE TRUE
er_sie,er_sie/ihm_ihr Neopronomen „er_sie“ FALSE er_sie seiner_ihrer ihm_ihr ihn_sie seine_ihre seiner_ihrer seiner_ihrer seine_ihre sein_ihr seines_ihres seinem_ihrem seinen_ihren sein_ihr seines_ihres seinem_ihrem sein_ihr der_die des_der dem_der den_die diese_r dieses_dieser diesem_dieser diese_n FALSE FALSE FALSE
er*sie,er*sie/ihm*ihr Neopronomen „er*sie“ FALSE er*sie seiner*ihrer ihm*ihr ihn*sie seine*ihre seiner*ihrer seiner*ihrer seine*ihre sein*ihr seines*ihres seinem*ihrem seinen*ihren sein*ihr seines*ihres seinem*ihrem sein*ihr der*die des*der dem*der den*die diese*r dieses*dieser diesem*dieser diese*n FALSE FALSE FALSE
er:sie,er:sie/ihm:ihr Neopronomen „er:sie“ FALSE er:sie seiner:ihrer ihm:ihr ihn:sie seine:ihre seiner:ihrer seiner:ihrer seine:ihre sein:ihr seines:ihres seinem:ihrem seinen:ihren sein:ihr seines:ihres seinem:ihrem sein:ihr der:die des:der dem:der den:die diese:r dieses:dieser diesem:dieser diese:n FALSE FALSE FALSE
ersie,ersie/ihmihr Neopronomen „ersie“ FALSE ersie seinerihrer ihmihr ihnsie seineihre seinerihrer seinerihrer seineihre seinihr seinesihres seinemihrem seinenihren seinihr seinesihres seinemihrem seinihr derdie desder demder dendie dieserdiese diesesdieser diesemdieser diesediesen FALSE FALSE FALSE
ersie,ersie/ihmihr Neopronomen „ersie“ FALSE ersie seinerihrer ihmihr ihnsie seineihre seinerihrer seinerihrer seineihre seinihr seinesihres seinemihrem seinenihren seinihr seinesihres seinemihrem seinihr derdie desder demder dendie dieserdiese diesesdieser diesemdieser diesediesen FALSE FALSE FALSE

1 key description normative pronoun_n pronoun_g pronoun_d pronoun_a possessive_f_n possessive_f_g possessive_f_d possessive_f_a possessive_m_n possessive_m_g possessive_m_d possessive_m_a possessive_n_n possessive_n_g possessive_n_d possessive_n_a article_n article_g article_d article_a demonstrative_n demonstrative_g demonstrative_d demonstrative_a plural pluralHonorific pronounceable history thirdForm smallForm sourcesInfo
4 es,es/sein,es/ihm Neutrum TRUE es seiner ihm es seine seiner seiner seine sein seines seinem seinen sein seines seinem sein das des dem das dieses dieses diesem dieses FALSE FALSE TRUE
5 es/ihr Neutrum mit femininen Formen FALSE es ihrer ihr es ihre ihrer ihrer ihre ihr ihres ihrem ihren ihr ihres ihrem ihr das der der das dieses dieser dieser dieses FALSE FALSE TRUE Wie {/es=„es/sein“}, wobei Wörter, bei denen das {/es=Neutrum} identisch mit dem {/er=Maskulinum} ist, durch {/sie=feminine} Formen ersetzt werden
6 es/denen Neutrum mit inflexiven Formen FALSE es derer denen es deren deren deren deren deren deren deren deren deren deren deren deren das des dem das dieses dieses diesem dieses FALSE FALSE TRUE Wie {/es=„es/sein“}, wobei die meisten Wörter, bei denen das {/es=Neutrum} identisch mit dem {/er=Maskulinum} ist, durch inflexive Formen ersetzt werden, wie sie auch in {/dey/denen=„dey/denen“} verwendet werden
7 dey,dey/denen Neopronomen „dey“ FALSE dey derer denen dey deren deren deren deren deren deren deren deren deren deren deren deren ~ ~ ~ ~ ~ ~ ~ ~ FALSE FALSE TRUE Im Akkusativ ist ebenfalls {/dey/denen/demm=„demm”} üblich. pronoun_a
8 dey/denen/demm Neopronomen „dey“ FALSE dey derer denen demm deren deren deren deren deren deren deren deren deren deren deren deren ~ ~ ~ ~ ~ ~ ~ ~ FALSE FALSE TRUE Im Akkusativ ist ebenfalls {/dey/denen=„dey”} üblich. pronoun_a
9 die,die/denen Neopronomen „die“ FALSE die derer denen die deren deren deren deren deren deren deren deren deren deren deren deren ~ ~ ~ ~ ~ ~ ~ ~ FALSE FALSE TRUE
10 el,el/em Neopronomen „el“ FALSE el|ɛl emser|ɛmzɐ em|ɛm en|ɛn emse|ɛmzə emser|ɛmzɐ emser|ɛmzɐ emse|ɛmzə ems|ɛmz emses|ɛmzəs emsem|ɛmzəm emsen|ɛmzən ems|ɛmz emses|ɛmzəs emsem|ɛmzəm ems|ɛmz ~ ~ ~ ~ ~ ~ ~ ~ FALSE FALSE TRUE
11 em,em/em Neopronomen „em“ FALSE em|ɛm emser|ɛmzɐ em|ɛm em|ɛm ems|ɛmz ems|ɛmz ems|ɛmz ems|ɛmz ems|ɛmz ems|ɛmz ems|ɛmz ems|ɛmz ems|ɛmz ems|ɛmz ems|ɛmz ems|ɛmz ~ ~ ~ ~ ~ ~ ~ ~ FALSE FALSE TRUE
12 en,en/en Neopronomen „en“ FALSE en|ɛn enser|ɛnzɐ en|ɛn en|ɛn ense|ɛnzə enser|ɛnzɐ enser|ɛnzɐ ense|ɛnzə enses|ɛnzəs enses|ɛnzəs ensem|ɛnzəm ensen|ɛnzən ens|ɛnz enses|ɛnzəs ensem|ɛnzəm ens|ɛnz de|deː ders|dɛrz derm|dɛrm de|deː de|deː dersen|dɛrzən derm|dɛrm de|deː FALSE FALSE TRUE Erstellt vom österreichischen LGBTIQA+-Kongress 2018 in St. Pölten
13 en/em Neopronomen „en“ FALSE en|ɛn enser|ɛnzɐ em|ɛm en|ɛn ense|ɛnzə enser|ɛnzɐ enser|ɛnzɐ ense|ɛnzə ens|ɛnz enses|ɛnzəs ensem|ɛnzəm ensen|ɛnzən ens|ɛnz enses|ɛnzəs ensem|ɛnzəm ens|ɛnz de|deː ders|dɛrz derm|dɛrm de|deː de|deː dersen|dɛrzən derm|dɛrm de|deː FALSE FALSE TRUE Erstellt vom {https://geschlechtsneutral.net/=Verein für geschlechtsneutrales Deutsch}
14 ey,ey/emm Neopronomen „ey“ FALSE ey eyser emm emm eyse eyser eyser eyse eys eyses eysem eysen eys eyses eysem eys ~ ~ ~ ~ ~ ~ ~ ~ FALSE FALSE TRUE
15 et,et/siem Neopronomen „et“ FALSE et sierer siem sien siere sierer sierer siere sier sieres sierem sieren sier sieres sierem sieren dat diesers diem dien dieset ~ diesigen diesetne FALSE FALSE TRUE
16 hän,hän/sim Neopronomen „hän“ FALSE hän sirer sim sin sire sirer sirer sire sir sires sirem siren sir sires sirem siren ~ ~ ~ ~ ~ ~ ~ ~ FALSE FALSE TRUE
17 hen,hen/hen,hen/hem Neopronomen „hen“ FALSE hen henser hem hen hens hens hens hens hens hens hens hens hens hens hens hens dai dais dam dai diesai diesais deisam diesai FALSE FALSE TRUE Aus dem Schwedischen, siehe: {https://sv.pronouns.page/hen=hen/henom}. Auch verwendet im {https://geschlechtsneutralesdeutsch.com/=NoNa-System}.
18 hie,hie/hein,hie/hiem Neopronomen „hie“ FALSE hie heiner hiem hie heine heiner heiner heine hein heines heinem heinen hein heines heinem hein ~ ~ ~ ~ ~ ~ ~ ~ FALSE FALSE TRUE
19 iks,iks/iks Neopronomen „iks“ FALSE iks ikser iks iks ikses ikses ikses ikses ikses ikses ikses ikses ikses ikses ikses ikses deks ~ ~ ~ dieseks ~ ~ ~ FALSE FALSE TRUE
20 ind,ind/inde Neopronomen „ind“ FALSE ind|ɪnd indser|ɪndzɐ inde|ɪndə ind|ɪnd indse|ɪndzə indser|ɪndzɐ indser|ɪndzɐ indse|ɪndzə inds|ɪndz indsens|ɪndzəs indsem|ɪndzəm indsen|ɪndzən inds|ɪndz indsens|ɪndzəns indsem|ɪndzəm inds|ɪndz ~ ~ ~ ~ ~ ~ ~ ~ FALSE FALSE TRUE Vom Wort „Individuum“
21 mensch,mensch/mensch Neopronomen „mensch” FALSE mensch menscher mensch mensch menschs menschs menschs menschs menschs menschs menschs menschs menschs menschs menschs menschs ~ ~ ~ ~ ~ ~ ~ ~ FALSE FALSE TRUE Oft wird „mensch” auch als {https://de.wikipedia.org/wiki/Generalisierendes_Personalpronomen=generalisierendes Pronomen} benutzt (statt „man”).@Eher als eines von mehreren Pronomen verwendet ({https://nibi.space/pronomen#mensch=laut nibi.space}).
22 nin,nin/nim Neopronomen „nin“ FALSE nin nimser nim nin nimse nimser nimser nimse nims nimses nimsem nimsen nims nimses nimsem nims din dins dim din diesin diesins diesim diesin FALSE FALSE TRUE Erstellt von {https://www.geschlechtsneutral.com/lit/Liminalis-2008-Sylvain-Balzer.pdf=Cabala de Sylvain und Carsten Balzer}
23 oj,oj/juj,oj/ojm Neopronomen „oj“ FALSE oj jujer ojm ojn juje jujer jujer juje juj jujes jusem jusen jus juses jusem jus ~ ~ ~ ~ ~ ~ ~ ~ FALSE FALSE TRUE Erstellt von {https://www.frumble.de/blog/2021/03/26/ueberlegungen-zu-einer-genderneutralen-deutschen-grammatik=Frumble}. Siehe auch: {/substantive#Ojum=Ojum}.
24 per,per/per Neopronomen „per“ FALSE per perer per per pers pers pers pers pers pers pers pers pers pers pers pers ~ ~ ~ ~ ~ ~ ~ ~ FALSE FALSE TRUE Vom Wort „Person“
25 ser,ser/sem Neopronomen „ser“ FALSE ser seser sem sen ses ses ses ses ses ses ses ses ses ses ses ses ~ ~ ~ ~ ~ ~ ~ ~ FALSE FALSE TRUE
26 sier,sier/siem Neopronomen „sier“ FALSE sier|zi:ɐ̯ sieser|zi:sɐ siem|zi:m sien|zi:n siese|zi:zə sieser|zi:zɐ sieser|zi:zɐ siese|zi:zə sies|zi:z sieses|zi:zəs siesem|zi:zəm siesen|zi:zən sies|zi:z sieses|zi:zəs siesem|zi:zəm sies|zi:z dier|di:ɐ̯ dies|di:s diem|di:m dien|di:n dier|di:ɐ̯ dies|di:s diem|di:m dien|di:n FALSE FALSE TRUE Erstellt von {https://www.annaheger.de/pronomen21/=Illi Anna Heger}
27 they,they/them Äquivalent zu englischem „they“ FALSE they them them them their their their their their their their their their their their their ~ ~ ~ ~ ~ ~ ~ ~ FALSE FALSE TRUE Aus dem Englischen, sieh: {https://en.pronouns.page/they=they/them}
28 xier,xier/xiem Neopronomen „xier“ FALSE xier|ksi:ɐ̯ xieser|ksi:sɐ xiem|ksi:m xien|ksi:n xiese|ksi:zə xieser|ksi:ɐ̯sɐ xieser|ksi:ɐ̯sɐ xiese|ksi:zə xies|ksi:z xieses|ksi:zəs xiesem|ksi:zəm xiesen|ksi:zən xies|ksi:z xieses|ksi:zəs xiesem|ksi:zəm xies|ksi:z dier|di:ɐ̯ dies|di:s diem|di:m dien|di:n dier|di:ɐ̯ dies|di:s diem|di:m dien|di:n FALSE FALSE TRUE Erstellt von {https://www.annaheger.de/pronomen33/=Illi Anna Heger}.
29 zet,zet/zerm Neopronomen „zet“ FALSE zet zerner zerm zern zets zets zets zets zets zets zets zets zets zets zets zets ~ ~ ~ ~ ~ ~ ~ ~ FALSE FALSE TRUE
30 er_sie,er_sie/ihm_ihr Neopronomen „er_sie“ FALSE er_sie seiner_ihrer ihm_ihr ihn_sie seine_ihre seiner_ihrer seiner_ihrer seine_ihre sein_ihr seines_ihres seinem_ihrem seinen_ihren sein_ihr seines_ihres seinem_ihrem sein_ihr der_die des_der dem_der den_die diese_r dieses_dieser diesem_dieser diese_n FALSE FALSE FALSE
31 er*sie,er*sie/ihm*ihr Neopronomen „er*sie“ FALSE er*sie seiner*ihrer ihm*ihr ihn*sie seine*ihre seiner*ihrer seiner*ihrer seine*ihre sein*ihr seines*ihres seinem*ihrem seinen*ihren sein*ihr seines*ihres seinem*ihrem sein*ihr der*die des*der dem*der den*die diese*r dieses*dieser diesem*dieser diese*n FALSE FALSE FALSE
32 er:sie,er:sie/ihm:ihr Neopronomen „er:sie“ FALSE er:sie seiner:ihrer ihm:ihr ihn:sie seine:ihre seiner:ihrer seiner:ihrer seine:ihre sein:ihr seines:ihres seinem:ihrem seinen:ihren sein:ihr seines:ihres seinem:ihrem sein:ihr der:die des:der dem:der den:die diese:r dieses:dieser diesem:dieser diese:n FALSE FALSE FALSE
33 ersie,ersie/ihmihr Neopronomen „ersie“ FALSE ersie seinerihrer ihmihr ihnsie seineihre seinerihrer seinerihrer seineihre seinihr seinesihres seinemihrem seinenihren seinihr seinesihres seinemihrem seinihr derdie desder demder dendie dieserdiese diesesdieser diesemdieser diesediesen FALSE FALSE FALSE

View File

@ -41,6 +41,7 @@ home:
Du kannst auch die Aussprache spezifizieren, die indem du nach einem senkrechten Strich „|“ (mit der Tastenkombination <kbd>Alt Gr+&lt;</kbd>) hinzufügst.
Benutze dafür die {https://de.wikipedia.org/wiki/Liste_der_IPA-Zeichen=Lautschrift IPA (Internationales Phonetisches Alphabet)}.
Ein Beispiel wäre: <code>sier|zi:ɐ̯</code> = „sier“, ausgesprochen wie /zi:ɐ̯/.
compressedAlternative: 'komprimierte Formattierung verwenden'
whatisit: 'Was ist das Problem mit Pronomen?'
mission:
header: 'Unsere Mission'
@ -85,6 +86,9 @@ pronouns:
comprehensive:
simple: 'häufige'
comprehensive: 'erweitert'
slashes:
plural: 'plural'
description: 'beschreibung'
others: 'Andere Formen'
othersRaw: 'andere'
or: 'oder'

View File

@ -43,6 +43,7 @@ home:
base: 'Based on'
alt: 'You can also enter interchangeable forms in each field separately, eg. <code>him&her</code> = “him” or “her”.'
pronunciation: 'You can also specify the pronunciation after a pipe character, using IPA, eg. <code>faerself|fɛɹsɛlf</code> = “faerself” pronounced as /fɛɹsɛlf/.'
compressedAlternative: 'Use the compressed formatting alternative'
whatisit: 'What''s the deal with pronouns?'
mission:
header: 'Our mission'
@ -78,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

@ -1,15 +1,19 @@
import Vue from 'vue';
import translator from '../src/translator.js';
import { Translator } from '../src/translator.js';
import config from '../data/config.suml';
import { buildDict } from '../src/helpers.js';
import { DateTime, Settings } from 'luxon';
import { decodeTime } from 'ulid';
import translations from '../data/translations.suml';
import baseTranslations from '../locale/_base/translations.suml';
export default ({ app, store }) => {
Vue.prototype.$eventHub = new Vue();
Vue.prototype.$base = process.env.BASE_URL;
const translator = new Translator(translations, baseTranslations, config);
Vue.prototype.$translator = translator;
Vue.prototype.$t = (key, params = {}, warn = false) => translator.translate(key, params, warn);
Vue.prototype.$te = (key, fallback = false) => {

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

@ -28,7 +28,7 @@
<tbody>
<tr v-for="tp in translationProposals">
<td>{{ tp.tKey }}</td>
<td>{{ translator.get(tp.tKey, false, true) }}</td>
<td>{{ $translator.get(tp.tKey, false, true) }}</td>
<td>{{ $te(tp.tKey) ? $t(tp.tKey) : '' }}</td>
<td>{{ tp.tValue }}</td>
<td>
@ -132,7 +132,6 @@
<script>
import { deepSet, head } from '../src/helpers.js';
import translator from '../src/translator.js';
import Suml from 'suml';
export default {
@ -142,11 +141,6 @@ export default {
contributors: await app.$axios.$get('/translations/contributors'),
};
},
data() {
return {
translator,
};
},
head() {
return head({
title: `${this.$t('admin.header')} • Translation proposals`,

View File

@ -30,7 +30,7 @@
<tbody>
<tr v-for="mt in missingTranslations">
<td>{{ mt }}</td>
<td>{{ translator.get(mt, false, true) }}</td>
<td>{{ $translator.get(mt, false, true) }}</td>
<td><T>{{ mt }}</T></td>
</tr>
</tbody>
@ -43,13 +43,11 @@
<script>
import { head } from '../src/helpers.js';
import translator from '../src/translator.js';
export default {
data() {
return {
translator,
missingTranslations: translator.listMissingTranslations(),
missingTranslations: this.$translator.listMissingTranslations(),
};
},
head() {

View File

@ -34,7 +34,8 @@ import Columnist from 'avris-columnist';
export default {
async asyncData({ route }) {
return parseMarkdown((await import(`../data/blog/${route.params.slug || route.meta[0].slug}.md`)).default);
const content = (await import(`../data/blog/${route.params.slug || route.meta[0].slug}.md`)).default;
return parseMarkdown(content, this.$translator);
},
data() {
return {

View File

@ -97,7 +97,10 @@ import { getContactLinks, getSocialLinks } from '../src/contact.js';
export default {
data() {
return {
links: groupBy([...getContactLinks(this.config), ...getSocialLinks(this.config)], (l) => l.group),
links: groupBy(
[...getContactLinks(this.config, this.$translator), ...getSocialLinks(this.config)],
(l) => l.group,
),
};
},
head() {

View File

@ -19,7 +19,7 @@ import parseMarkdown from '../src/parseMarkdown.js';
export default {
async asyncData() {
return parseMarkdown((await import('../LICENSE.md')).default);
return parseMarkdown((await import('../LICENSE.md')).default, this.$translator);
},
head() {
return head({

View File

@ -359,7 +359,6 @@ import config from '../data/config.suml';
import link from '../plugins/link.js';
import { minBirthdate, maxBirthdate, formatDate, parseDate } from '../src/birthdate.js';
import opinions from '../src/opinions.js';
import t from '../src/translator.js';
import { calendar } from '../src/calendar/calendar.js';
const defaultWords = config.profile.defaultWords.map(({ header, values }) => {
@ -385,19 +384,19 @@ function fixArrayObject(arrayObject) {
return Array.isArray(arrayObject) ? arrayObject : Object.values(arrayObject);
}
const opinionsToForm = (opinions) => buildList(function*() {
const opinionsToForm = (opinions, translator) => buildList(function*() {
for (const [key, options] of Object.entries(opinions)) {
yield {
key,
icon: options.icon,
description: options.description || t.get(`profile.opinion.${key}`),
description: options.description || translator.get(`profile.opinion.${key}`),
colour: options.colour || '',
style: options.style || '',
};
}
});
const buildProfile = (profiles, currentLocale) => {
const buildProfile = (profiles, currentLocale, translator) => {
// card in this locale exists
for (const locale in profiles) {
if (!profiles.hasOwnProperty(locale)) {
@ -421,7 +420,7 @@ const buildProfile = (profiles, currentLocale) => {
credentials: profile.credentials,
credentialsLevel: profile.credentialsLevel,
credentialsName: profile.credentialsName,
opinions: opinionsToForm(profile.opinions || {}),
opinions: opinionsToForm(profile.opinions || {}, translator),
circle: profile.circle,
sensitive: profile.sensitive,
markdown: profile.markdown,
@ -453,7 +452,7 @@ const buildProfile = (profiles, currentLocale) => {
credentials: [],
credentialsLevel: null,
credentialsName: null,
opinions: opinionsToForm(profile.opinions || {}),
opinions: opinionsToForm(profile.opinions || {}, translator),
circle: profile.circle,
sensitive: [],
markdown: profile.markdown,
@ -501,7 +500,7 @@ export default {
authorization: `Bearer ${store.state.token}`,
} })).profiles;
const profile = buildProfile(profiles, currentLocale);
const profile = buildProfile(profiles, currentLocale, app.$translator);
return {
...profile,
@ -518,7 +517,7 @@ export default {
},
propagate: [],
flagsAsterisk: process.env.FLAGS_ASTERISK,
defaultOpinions: opinionsToForm(opinions),
defaultOpinions: opinionsToForm(opinions, this.$translator),
isValidLink,
year: calendar.getCurrentYear(),
};
@ -533,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;
@ -628,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);
@ -638,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

@ -75,7 +75,7 @@
v-model="selectedPronoun.description"
class="form-control form-input p-0 form-control-sm"
:size="selectedPronoun.description.length ? selectedPronoun.description.length + 3 : 16"
maxlength="64"
:maxlength="DESCRIPTION_MAXLENGTH"
:placeholder="$t('profile.description')"
>
</template>
@ -128,6 +128,12 @@
</div>
<div v-if="link" class="card-footer">
<LinkInput :link="link" />
<div v-if="config.pronouns.slashes" class="form-check form-switch">
<label>
<input v-model="linkCompressedAlternative" class="form-check-input" type="checkbox">
<T>home.generator.compressedAlternative</T>
</label>
</div>
</div>
</div>
</li>
@ -247,7 +253,7 @@
<script>
import { examples, pronouns, pronounLibrary } from '../src/data.js';
import { ExamplePart } from '../src/classes.js';
import { ExamplePart, Pronoun } from '../src/classes.js';
import Compressor from '../src/compressor.js';
import MORPHEMES from '../data/pronouns/morphemes.js';
import { mapState } from 'vuex';
@ -265,6 +271,7 @@ export default {
// for its' default to prevent a crash
selectedPronoun: pronouns[this.config.pronouns.default].clone(true) ?? 'he',
selectedMorpheme: '',
linkCompressedAlternative: false,
customiseMultiple: false,
multiple: this.config.pronouns.multiple ? this.config.pronouns.multiple.examples[0].split('&') : [],
@ -272,6 +279,8 @@ export default {
customise: this.config.pronouns.autoOpenGenerator || false,
glue: ` ${this.$t('pronouns.or')} `,
DESCRIPTION_MAXLENGTH: Pronoun.DESCRIPTION_MAXLENGTH,
};
},
computed: {
@ -313,12 +322,16 @@ export default {
return null;
}
const slashes = this.selectedPronoun.toStringSlashes();
const commas = this.usedBaseEquals ? this.usedBase : this.longLink;
const slashes = this.selectedPronoun.toStringSlashes(this.$translator);
const link = slashes && slashes.length < commas.length
? slashes
: commas;
let link;
if (this.usedBaseEquals) {
link = this.usedBase;
} else if (slashes && !this.linkCompressedAlternative) {
link = slashes;
} else {
link = this.longLink;
}
return this.addSlash(`${this.$base + (this.config.pronouns.prefix || '')}/${link}`);
},

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

@ -1,6 +1,6 @@
import { Pronoun } from './classes.js';
import Compressor from './compressor.js';
import { buildDict, isEmoji } from './helpers.js';
import { buildDict, isEmoji, unescapeControlSymbols } from './helpers.js';
import MORPHEMES from '../data/pronouns/morphemes.js';
export const addAliasesToPronouns = (pronouns) => {
@ -58,7 +58,79 @@ const buildPronounFromTemplate = (key, template) => {
);
};
export const buildPronoun = (pronouns, 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;
let description = '';
const morphemeChunks = [];
for (const chunk of chunks) {
if (chunk.startsWith(':')) {
if (config.pronouns.plurals && isModifier(chunk, 'pronouns.slashes.plural', translator)) {
plural = true;
} else if (config.pronouns.plurals && config.pronouns.honorifics &&
isModifier(chunk, 'pronouns.slashes.pluralHonorific', translator)) {
pluralHonorific = true;
} else {
const descriptionModifierValue =
extractModifierValue(chunk, 'pronouns.slashes.description', translator);
if (descriptionModifierValue) {
description = unescapeControlSymbols(descriptionModifierValue);
}
}
} else {
if (chunk === '~') {
morphemeChunks.push(null);
} else if (chunk === ' ') {
morphemeChunks.push('');
} else {
morphemeChunks.push(unescapeControlSymbols(chunk));
}
}
}
if (description.length > Pronoun.DESCRIPTION_MAXLENGTH) {
return null;
}
const slashMorphemes = config.pronouns.slashes === true
? MORPHEMES
: config.pronouns.slashes;
if (slashMorphemes && morphemeChunks.length === slashMorphemes.length) {
return new Pronoun(
`${morphemeChunks[0]}/${morphemeChunks[1]}`,
description,
false,
buildDict(function*() {
for (const m of MORPHEMES) {
const index = slashMorphemes.indexOf(m);
yield [m, index === -1 ? null : morphemeChunks[index]];
}
}),
[plural],
[pluralHonorific],
[],
'__generator__',
false,
);
}
};
export const buildPronoun = (pronouns, path, translator) => {
if (!path) {
return null;
}
@ -125,29 +197,8 @@ export const buildPronoun = (pronouns, path) => {
pronoun = buildPronounFromTemplate(path.substring(1), config.pronouns.null);
}
const p = path.split('/').filter((s) => !!s);
if (!pronoun && config.pronouns.slashes !== false) {
const slashMorphemes = config.pronouns.slashes === true
? MORPHEMES
: config.pronouns.slashes;
if (slashMorphemes && p.length === slashMorphemes.length) {
pronoun = new Pronoun(
`${p[0]}/${p[1]}`,
'',
false,
buildDict(function*() {
for (const m of MORPHEMES) {
const index = slashMorphemes.indexOf(m);
yield [m, index === -1 ? '' : p[index]];
}
}),
[p[p.length - 1].endsWith('selves')], // TODO English specific, extract somewhere
[false],
[],
'__generator__',
false,
);
}
return buildPronounFromSlashes(config, path, translator);
}
return pronoun;
@ -166,7 +217,19 @@ export const parsePronouns = (pronounsRaw) => {
t.normative,
buildDict(function* () {
for (const morpheme of MORPHEMES) {
yield [morpheme, t[morpheme]];
let value;
if (t[morpheme] === null) {
// empty cells are parsed as null in dynamic parse mode,
// but most of the time an empty string is intended
value = '';
} else if (t[morpheme] === '~') {
// to really describe that a pronoun does not support a morpheme,
// tilde is used to describe null as in yaml.
value = null;
} else {
value = t[morpheme];
}
yield [morpheme, value];
}
}),
[t.plural],

View File

@ -1,4 +1,4 @@
import { buildDict, buildList, capitalise, escapePronunciationString } from './helpers.js';
import { buildDict, buildList, capitalise, escapeControlSymbols, escapePronunciationString } from './helpers.js';
import MORPHEMES from '../data/pronouns/morphemes.js';
const config = process.env.CONFIG || global.config;
@ -295,6 +295,8 @@ const escape = (s) => {
};
export class Pronoun {
static DESCRIPTION_MAXLENGTH = 64;
constructor(canonicalName, description, normative, morphemes, plural, pluralHonorific, aliases = [], history = '', pronounceable = true, thirdForm = null, smallForm = null, sourcesInfo = null) {
this.canonicalName = canonicalName;
this.description = description || '';
@ -305,7 +307,7 @@ export class Pronoun {
if (!morphemes.hasOwnProperty(m)) {
continue;
}
const [morpheme, pronunciation] = morphemes[m] ? morphemes[m].split('|') : [null, null];
const [morpheme, pronunciation] = typeof morphemes[m] === 'string' ? morphemes[m].split('|') : [null, null];
this.morphemes[m] = morpheme;
this.pronunciations[m] = pronunciation;
}
@ -403,17 +405,13 @@ export class Pronoun {
morpheme = morpheme.substring(1);
}
if (!this.morphemes[morpheme]) {
if (this.morphemes[morpheme] === null) {
return null;
}
const options = this.morphemes[morpheme].split('&');
const result = options[counter % options.length];
if (result === '') {
return null;
}
return capital ? capitalise(result) : result;
}
@ -469,16 +467,41 @@ export class Pronoun {
return this.toArray().join(',');
}
toStringSlashes() {
if (!config.pronouns.slashes || this.description) {
toStringSlashes(translator) {
if (!config.pronouns.slashes) {
return null;
}
let chunks;
if (Array.isArray(config.pronouns.slashes)) {
return config.pronouns.slashes.map((m) => this.morphemes[m]).join('/');
chunks = config.pronouns.slashes.map((m) => this.morphemes[m]);
} else {
chunks = Object.values(this.morphemes);
}
chunks = chunks.map((chunk) => {
if (chunk === null) {
return '~';
} else if (chunk === '') {
// use an extra space because double slashes get replaced by a single one during a request
return ' ';
} else {
return escapeControlSymbols(chunk);
}
});
if (this.plural[0]) {
chunks.push(`:${translator.translate('pronouns.slashes.plural')}`);
}
if (this.pluralHonorific[0]) {
chunks.push(`:${translator.translate('pronouns.slashes.pluralHonorific')}`);
}
if (this.description) {
const escapedDescription = escapeControlSymbols(this.description);
chunks.push(`:${translator.translate('pronouns.slashes.description')}=${escapedDescription}`);
}
return Object.values(this.morphemes).join('/');
// encode a trailing space so that it does not get removed during a request
return chunks.join('/').replace(/ $/, encodeURI(' '));
}
static from(data) {
@ -557,7 +580,7 @@ export class Pronoun {
if (data.length !== MORPHEMES.length + extraFields ||
data[0].length === 0 ||
data[data.length - 1].length > 64 ||
data[data.length - 1].length > Pronoun.DESCRIPTION_MAXLENGTH ||
data.slice(1, data.length - extraFields).filter((s) => s.length > 24).length
) {
return null;
@ -569,7 +592,7 @@ export class Pronoun {
}
return new Pronoun(
m[MORPHEMES[0]],
`${m[MORPHEMES[0]]}/${m[MORPHEMES[1]]}`,
data[data.length - 1],
false,
m,

View File

@ -1,5 +1,3 @@
import translator from './translator.js';
export const contact = {
all: {
email: {
@ -110,26 +108,28 @@ const AVATARS = {
calendar: 'calendarqueer.png',
};
const supportLinks = {
all: {
bank: {
icon: 'money-check-alt',
url: 'https://bunq.me/PronounsPage',
headline: translator.translate('support.bankAccount'),
// tooltip: translator.translate('support.bankAccountOwner'),
const supportLinks = (translator) => {
return {
all: {
bank: {
icon: 'money-check-alt',
url: 'https://bunq.me/PronounsPage',
headline: translator.translate('support.bankAccount'),
// tooltip: translator.translate('support.bankAccountOwner'),
},
kofi: {
icon: 'coffee',
url: 'https://ko-fi.com/radajezykaneutralnego',
headline: 'Ko-Fi',
},
paypal: {
icon: 'paypal',
iconSet: 'b',
url: 'https://paypal.me/RJNeutralnego',
headline: 'PayPal',
},
},
kofi: {
icon: 'coffee',
url: 'https://ko-fi.com/radajezykaneutralnego',
headline: 'Ko-Fi',
},
paypal: {
icon: 'paypal',
iconSet: 'b',
url: 'https://paypal.me/RJNeutralnego',
headline: 'PayPal',
},
},
};
};
function* getLink(links, group) {
@ -151,7 +151,7 @@ function* getLink(links, group) {
}
}
export function* getContactLinks(config) {
export function* getContactLinks(config, translator) {
if (config.faq.enabled) {
yield {
url: `/${config.faq.route}`,
@ -178,6 +178,6 @@ export function* getSocialLinks(config) {
yield* getLink(socialLinks, 'shop');
}
export function* getSupportLinks() {
yield* getLink(supportLinks, 'all');
export function* getSupportLinks(translator) {
yield* getLink(supportLinks(translator), 'all');
}

View File

@ -268,6 +268,22 @@ const escapeChars = {
export const escapeHtml = (text) => text.replace(/[&<>"]/g, (tag) => escapeChars[tag] || tag);
export const escapeControlSymbols = (text) => {
if (text === null) {
return null;
}
// a backtick is used because browsers replace backslashes
// with forward slashes if they are not url-encoded
return text.replaceAll(/[`&/|,]/g, '`$&');
};
export const unescapeControlSymbols = (text) => {
if (text === null) {
return null;
}
return text.replaceAll(/`(.)/g, '$1');
};
export const escapePronunciationString = (text) => {
return text.replaceAll('\\', '\\\\')
.replaceAll('/', '\\/');

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,5 +1,3 @@
import translator from './translator.js';
// TODO make generic
const census_groups = {
location_poland: 'Osoby mieszkające w Polsce',
@ -63,7 +61,7 @@ const fetchJson = (_, filename, key) => {
return c;
};
const generateToC = (content) => (_) => {
const generateToC = (content, translator) => (_) => {
const tags = [];
let curentLevel = 2;
let needsClosing = false;
@ -99,7 +97,7 @@ const generateToC = (content) => (_) => {
`;
};
export default async function parseMarkdown(markdown) {
export default async function parseMarkdown(markdown, translator) {
try {
let content = `<div>${
markdown
@ -127,7 +125,7 @@ export default async function parseMarkdown(markdown) {
.replace(/{\/wide_table}/g, '</div>')
}</div>`
;
content = content.replace(/{table_of_contents}/g, generateToC(content));
content = content.replace(/{table_of_contents}/g, generateToC(content, translator));
const titleMatch = content.match('<h1[^>]*>(.+?)</h1>');
const title = titleMatch ? titleMatch[1] : null;

View File

@ -1,10 +1,7 @@
import { deepGet } from './helpers.js';
import config from '../data/config.suml';
import translations from '../data/translations.suml';
import baseTranslations from '../locale/_base/translations.suml';
import { listMissingTranslations } from './missingTranslations.js';
class Translator {
export class Translator {
constructor(translations, baseTranslations, config) {
this.translations = translations;
this.baseTranslations = baseTranslations;
@ -58,5 +55,3 @@ class Translator {
return listMissingTranslations(this.translations, this.baseTranslations, this.config);
}
}
export default new Translator(translations, baseTranslations, config);

View File

@ -1,4 +1,3 @@
import translator from '../src/translator.js';
import { buildDict, parseUserJwt } from '../src/helpers.js';
export const state = () => ({
@ -69,7 +68,7 @@ export const mutations = {
translationPause(state) {
state.translationMode = false;
},
translate(state, { key, newValue }) {
translate(state, { translator, key, newValue }) {
if (newValue !== translator.get(key)) {
const translationChanges = { ...state.translationChanges };
translationChanges[key] = newValue;

View File

@ -1,7 +1,16 @@
import { beforeEach, describe, expect, jest, test } from '@jest/globals';
import { mockTranslations } from './fixtures/translations.js';
import { Translator } from '../src/translator.js';
mockTranslations({});
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', () => {
@ -12,7 +21,7 @@ jest.unstable_mockModule('../data/pronouns/morphemes.js', () => {
const { Pronoun } = await import('../src/classes.js');
const { buildPronoun } = await import('../src/buildPronoun.js');
const { default: pronouns } = await import('./fixtures/pronouns.js');
const { default: pronouns, generated: generatedPronouns } = await import('./fixtures/pronouns.js');
beforeEach(() => {
global.config.pronouns = {
@ -24,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);
});
@ -49,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(
'💙',
@ -85,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',
@ -106,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', () => {
@ -115,45 +124,85 @@ 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`);
});
});
});
describe('when configured that slashes contain all morphemes', () => {
beforeEach(() => {
global.config.pronouns.honorifics = true;
global.config.pronouns.slashes = true;
});
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(new Pronoun(
'ae/aer',
'',
false,
{
pronoun_subject: 'ae',
pronoun_object: 'aer',
possessive_determiner: 'aer',
possessive_pronoun: 'aers',
reflexive: 'aerself',
},
[false],
[false],
[],
'__generator__',
false,
actual.toEqual(generatedPronouns.aer);
});
test('unescapes morphemes', () => {
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', 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', 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” `/ “æ”',
translator,
));
actual.toBeDefined();
actual.toEqual(generatedPronouns.aerWithDescription);
});
test('builds generated pronoun from all required morphemes and modifiers', () => {
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', 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/', translator));
actual.toBeDefined();
actual.toEqual(generatedPronouns.aerWithEmptyReflexive);
});
test('builds generated pronoun with some morphemes unset', () => {
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();
});
});
describe('when configured that slashes contain some morphemes', () => {
@ -162,31 +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(new Pronoun(
'ae/aer',
'',
false,
{
pronoun_subject: 'ae',
pronoun_object: 'aer',
possessive_determiner: 'aer',
possessive_pronoun: null,
reflexive: 'aerself',
},
[false],
[false],
[],
'__generator__',
false,
));
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', () => {
@ -199,56 +232,56 @@ 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();
});
});
test('builds generated pronouns from commas', () => {
const actual = expect(buildPronoun(pronouns, 'ae,aer,aer,aers,aerself,0,'));
actual.toBeDefined();
actual.toEqual(new Pronoun(
'ae',
'',
false,
{
pronoun_subject: 'ae',
pronoun_object: 'aer',
possessive_determiner: 'aer',
possessive_pronoun: 'aers',
reflexive: 'aerself',
},
[false],
[false],
[],
'__generator__',
false,
));
});
test('builds generated pronouns from commas with compressed parts', () => {
const actual = expect(buildPronoun(pronouns, 'they,!2,aers,!2,'));
actual.toBeDefined();
actual.toEqual(new Pronoun(
'they',
'',
false,
{
pronoun_subject: 'they',
pronoun_object: 'them',
possessive_determiner: 'their',
possessive_pronoun: 'aers',
reflexive: 'themselves',
},
[true],
[false],
[],
'__generator__',
false,
));
describe('building generated pronouns from commas', () => {
test('succeeds with all parts present', () => {
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', 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,', translator));
actual.toBeDefined();
actual.toEqual(new Pronoun(
'they/them',
'',
false,
{
pronoun_subject: 'they',
pronoun_object: 'them',
possessive_determiner: 'their',
possessive_pronoun: 'aers',
reflexive: 'themselves',
},
[true],
[false],
[],
'__generator__',
false,
));
});
test('fails when too few parts are given', () => {
expect(buildPronoun(pronouns, 'ae, translator,aer,aer,aers,aerself', translator)).toBeUndefined();
});
test('fails when many few parts are given', () => {
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, 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',
@ -269,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

@ -1,19 +1,106 @@
import { describe, expect, test } from '@jest/globals';
import { beforeEach, describe, expect, test } from '@jest/globals';
import { MergedPronounGroup, PronounGroup, PronounLibrary } from '../src/classes.js';
import { mockTranslations } from './fixtures/translations.js';
import { Example, ExamplePart, MergedPronounGroup, PronounGroup, PronounLibrary } from '../src/classes.js';
import { Translator } from '../src/translator.js';
const translations = {
pronouns: {
any: {
short: 'any',
},
slashes: {
plural: 'plural',
pluralHonorific: 'plural-honorific',
description: 'description',
},
},
};
mockTranslations(translations);
const translator = new Translator(translations, translations, []);
const { default: pronouns } = await import('./fixtures/pronouns.js');
const { default: translator } = await import('../src/translator.js');
const { default: pronouns, generated: generatedPronouns } = await import('./fixtures/pronouns.js');
describe('required morphemes for an example', () => {
const exampleParts = [
new ExamplePart(false, 'This house is '),
new ExamplePart(true, 'possessive_pronoun'),
];
const example = new Example(exampleParts, exampleParts);
test('are present if all morphemes are present', () => {
expect(example.requiredMorphemesPresent(pronouns.they)).toBe(true);
});
test('are present even if one morpheme is empty', () => {
expect(example.requiredMorphemesPresent(generatedPronouns.aerWithEmptyPossessivePronoun)).toBe(true);
});
test('are missing if one morpheme is null', () => {
expect(example.requiredMorphemesPresent(generatedPronouns.aerWithUnsetPossessivePronoun)).toBe(false);
});
});
describe('formatting a pronoun with slashes', () => {
describe('when slashes are not configured', () => {
beforeEach(() => {
global.config.pronouns = {};
});
test('yields no result', () => {
expect(generatedPronouns.aer.toStringSlashes(translator)).toBeNull();
});
});
describe('when configured that slashes contain all morphemes', () => {
beforeEach(() => {
global.config.pronouns = { slashes: true };
});
test('chunks contain all morphemes', () => {
expect(generatedPronouns.aer.toStringSlashes(translator))
.toEqual('ae/aer/aer/aers/aerself');
});
test('morphemes with control symbols gets escaped', () => {
expect(generatedPronouns.sSlashHe.toStringSlashes(translator))
.toEqual('s`/he/hir/hir/hirs/hirself');
});
test('empty morphemes receive space as placeholder', () => {
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(translator))
.toEqual('ae/aer/aer/aers/%20');
});
test('unset morphemes receive tilde as placeholder', () => {
expect(generatedPronouns.aerWithUnsetPossessivePronoun.toStringSlashes(translator))
.toEqual('ae/aer/aer/~/aerself');
});
test('adds plural modifier if necessary', () => {
expect(generatedPronouns.aerPlural.toStringSlashes(translator))
.toEqual('ae/aer/aer/aers/aerselves/:plural');
});
test('adds plural honorific modifier if necessary', () => {
expect(generatedPronouns.aerPluralHonorific.toStringSlashes(translator))
.toEqual('ae/aer/aer/aers/aerselves/:plural-honorific');
});
test('adds escaped description if necessary', () => {
expect(generatedPronouns.aerWithDescription.toStringSlashes(translator))
.toEqual('ae/aer/aer/aers/aerself/:description=Neopronoun “ae” `/ “æ”');
});
test('adds multiple modifiers if necessary', () => {
expect(generatedPronouns.aerPluralWithDescription.toStringSlashes(translator))
.toEqual('ae/aer/aer/aers/aerselves/:plural/:description=Neopronoun “ae” `/ “æ”');
});
});
describe('when configured that slashes contain some morphemes', () => {
beforeEach(() => {
global.config.pronouns = {
slashes: ['pronoun_subject', 'pronoun_object', 'possessive_determiner', 'reflexive'],
};
});
test('chunks contain configured morphemes', () => {
expect(generatedPronouns.aer.toStringSlashes(translator)).toEqual('ae/aer/aer/aerself');
});
});
});
describe('when merging pronoun groups by key', () => {
test('groups without keys are not assigned a merged group', () => {

View File

@ -14,11 +14,8 @@ const he = new Pronoun(
[false],
[false],
['he/him'],
'',
null,
true,
'',
'',
'',
);
const she = new Pronoun(
@ -35,11 +32,8 @@ const she = new Pronoun(
[false],
[false],
['she/her'],
'',
null,
true,
'',
'',
'',
);
const they = new Pronoun(
@ -56,11 +50,10 @@ const they = new Pronoun(
[true],
[true],
['they/them'],
'',
null,
true,
'',
'themselves',
'',
null,
'reflexive',
);
export default {
@ -68,3 +61,170 @@ export default {
she,
they,
};
const aer = new Pronoun(
'ae/aer',
'',
false,
{
pronoun_subject: 'ae',
pronoun_object: 'aer',
possessive_determiner: 'aer',
possessive_pronoun: 'aers',
reflexive: 'aerself',
},
[false],
[false],
[],
'__generator__',
false,
);
const aerPlural = new Pronoun(
'ae/aer',
'',
false,
{
pronoun_subject: 'ae',
pronoun_object: 'aer',
possessive_determiner: 'aer',
possessive_pronoun: 'aers',
reflexive: 'aerselves',
},
[true],
[false],
[],
'__generator__',
false,
);
const aerPluralHonorific = new Pronoun(
'ae/aer',
'',
false,
{
pronoun_subject: 'ae',
pronoun_object: 'aer',
possessive_determiner: 'aer',
possessive_pronoun: 'aers',
reflexive: 'aerselves',
},
[false],
[true],
[],
'__generator__',
false,
);
const aerWithDescription = new Pronoun(
'ae/aer',
'Neopronoun “ae” / “æ”',
false,
{
pronoun_subject: 'ae',
pronoun_object: 'aer',
possessive_determiner: 'aer',
possessive_pronoun: 'aers',
reflexive: 'aerself',
},
[false],
[false],
[],
'__generator__',
false,
);
const aerPluralWithDescription = new Pronoun(
'ae/aer',
'Neopronoun “ae” / “æ”',
false,
{
pronoun_subject: 'ae',
pronoun_object: 'aer',
possessive_determiner: 'aer',
possessive_pronoun: 'aers',
reflexive: 'aerselves',
},
[true],
[false],
[],
'__generator__',
false,
);
const aerWithEmptyPossessivePronoun = new Pronoun(
'ae/aer',
'',
false,
{
pronoun_subject: 'ae',
pronoun_object: 'aer',
possessive_determiner: 'aer',
possessive_pronoun: '',
reflexive: 'aerself',
},
[false],
[false],
[],
'__generator__',
false,
);
const aerWithEmptyReflexive = new Pronoun(
'ae/aer',
'',
false,
{
pronoun_subject: 'ae',
pronoun_object: 'aer',
possessive_determiner: 'aer',
possessive_pronoun: 'aers',
reflexive: '',
},
[false],
[false],
[],
'__generator__',
false,
);
const aerWithUnsetPossessivePronoun = new Pronoun(
'ae/aer',
'',
false,
{
pronoun_subject: 'ae',
pronoun_object: 'aer',
possessive_determiner: 'aer',
possessive_pronoun: null,
reflexive: 'aerself',
},
[false],
[false],
[],
'__generator__',
false,
);
const sSlashHe = new Pronoun(
's/he/hir',
'',
false,
{
pronoun_subject: 's/he',
pronoun_object: 'hir',
possessive_determiner: 'hir',
possessive_pronoun: 'hirs',
reflexive: 'hirself',
},
[false],
[false],
[],
'__generator__',
false,
);
export const generated = {
aer,
aerPlural,
aerPluralHonorific,
aerWithDescription,
aerPluralWithDescription,
aerWithEmptyPossessivePronoun,
aerWithEmptyReflexive,
aerWithUnsetPossessivePronoun,
sSlashHe,
};

View File

@ -1,15 +0,0 @@
import { jest } from '@jest/globals';
const __dirname = new URL('.', import.meta.url).pathname;
export const mockTranslations = (translations) => {
jest.unstable_mockModule(`${__dirname}/../../data/translations.suml`, () => {
return { default: translations };
});
jest.unstable_mockModule(`${__dirname}/../../locale/_base/translations.suml`, () => {
return { default: {} };
});
jest.unstable_mockModule(`${__dirname}/../../data/config.suml`, () => {
return { default: {} };
});
};

View File

@ -1,6 +1,57 @@
import { describe, expect, test } from '@jest/globals';
import { convertPronunciationStringToSsml, escapePronunciationString } from '../src/helpers.js';
import {
escapeControlSymbols,
unescapeControlSymbols,
convertPronunciationStringToSsml,
escapePronunciationString,
} from '../src/helpers.js';
const controlSymbols = [
{
description: 'slashes',
unescaped: 'w/o n/A',
escaped: 'w`/o n`/A',
},
{
description: 'ampersands',
unescaped: 'me & you',
escaped: 'me `& you',
},
{
description: 'backticks',
unescaped: '`code` in markdown',
escaped: '``code`` in markdown',
},
{
description: 'commas',
unescaped: 'me, you',
escaped: 'me`, you',
},
{
description: 'pipes',
unescaped: '0 | 1',
escaped: '0 `| 1',
},
];
describe('when escaping control symbols', () => {
test('safely handles null', () => {
expect(escapeControlSymbols(null)).toBeNull();
});
test.each(controlSymbols)('$description get escaped with `', ({ unescaped, escaped }) => {
expect(escapeControlSymbols(unescaped)).toBe(escaped);
});
});
describe('when unescaping control symbols', () => {
test('safely handles null', () => {
expect(unescapeControlSymbols(null)).toBeNull();
});
test.each(controlSymbols)('$description get unescaped', ({ unescaped, escaped }) => {
expect(unescapeControlSymbols(escaped)).toBe(unescaped);
});
});
describe('when escaping pronunciation', () => {
test.each([