Merge remote-tracking branch 'origin/main'

This commit is contained in:
Andrea Vos 2023-12-31 12:32:22 +01:00
commit 8437c47bf7
8 changed files with 144 additions and 35 deletions

View File

@ -36,8 +36,11 @@ home:
button: 'Zeige den Generator' button: 'Zeige den Generator'
header2: 'Generiere einen Link' header2: 'Generiere einen Link'
base: 'Ausgehend von' base: 'Ausgehend von'
alt: 'Du kannst auch austauschbare Formen in jedes Feld eintragen, z.B. <code>er&sie</code> = „er“ or „sie“.' alt: 'Du kannst auch austauschbare Formen in jedes Feld eintragen, z.B. <code>er&sie</code> = „er“ oder „sie“.'
pronunciation: 'Du kannst auch die Aussprache spezifizieren, die indem du nach einem senkrechten Strich „|“ (mit der Tastenkombination [Alt Gr] und [<>] ) hinzufügst. Benutze dafür die Lautschrift IPA (Internationales Phonetisches Alphabet). Ein Beispiel wäre: <code>sier|siˑʁ</code> = „sier“, ausgesprochen wie /siˑʁ/.' pronunciation: >
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:ɐ̯/.
whatisit: 'Was ist das Problem mit Pronomen?' whatisit: 'Was ist das Problem mit Pronomen?'
mission: mission:
header: 'Unsere Mission' header: 'Unsere Mission'
@ -246,10 +249,10 @@ faq:
Online ist es noch einfacher: Füge einfach deine Pronomen (oder einen Link zu Beispielen von unserer Website) zu deiner „Bio“ hinzu. Online ist es noch einfacher: Füge einfach deine Pronomen (oder einen Link zu Beispielen von unserer Website) zu deiner „Bio“ hinzu.
- > - >
Denke auch daran, dass manche Menschen je nach Situation einen anderen Namen und andere Pronomen verwenden könnten. Denke auch daran, dass manche Menschen je nach Situation einen anderen Namen und andere Pronomen verwenden könnten.
Sie hatten vielleicht vor manchen Freund*innen oder Arbeitskolleg*innen noch kein Coming Out, aber bei anderen Freund*innen fühlen sie sich wohl und gehen damit offen um. Sie hatten vielleicht vor manchen Freund*innen oder Arbeitskolleg*innen noch kein Coming-out, aber bei anderen Freund*innen fühlen sie sich wohl und gehen damit offen um.
Sei rücksichtsvoll. Du kannst zum Beispiel fragen: „Welche Pronomen soll ich vor deiner*m Chef*in benutzen?“, etc. Sei rücksichtsvoll. Du kannst zum Beispiel fragen: „Welche Pronomen soll ich vor deiner*m Chef*in benutzen?“, etc.
- > - >
Manche Menschen geben mehrere Pronomen an, z.B. „{/er&sie=er/sie}“ oder „{/sier&er=sier/er}“. Manche Menschen geben mehrere Pronomen an, z.B. „{/er&sie=er oder sie}“ oder auch „{/sier&er=sier oder er}“.
Das bedeutet, dass sie alle diese Formen mögen. Normalerweise ist die erste die bevorzugte. Das bedeutet, dass sie alle diese Formen mögen. Normalerweise ist die erste die bevorzugte.
who-uses-it: who-uses-it:
question: 'Benutzt das überhaupt irgendwer?' question: 'Benutzt das überhaupt irgendwer?'
@ -386,7 +389,7 @@ contact:
language: > language: >
Wir sind ein internationales Team - die Personen, die eine bestimmte Sprachversion erstellt haben, sind Wir sind ein internationales Team - die Personen, die eine bestimmte Sprachversion erstellt haben, sind
möglicherweise nicht dieselben, die deine Nachricht lesen werden. Daher würden wir es begrüßen, wenn du uns in möglicherweise nicht dieselben, die deine Nachricht lesen werden. Daher würden wir es begrüßen, wenn du uns in
<strong>Englisch or Polnisch</strong> kontaktierst. <strong>Englisch oder Polnisch</strong> kontaktierst.
team: team:
name: 'Das „Rat für neutrale Sprache“ Kollektiv' name: 'Das „Rat für neutrale Sprache“ Kollektiv'
nameShort: 'Kollektiv' nameShort: 'Kollektiv'
@ -569,7 +572,7 @@ profile:
names: 'Namen' names: 'Namen'
pronouns: 'Pronomen' pronouns: 'Pronomen'
pronounsInfo: > pronounsInfo: >
Du kannst entweder ein <strong>Pronomen</strong> (z.B. „sier“ or „sie/ihr“) oder einen <strong>Link</strong> (z.B. „https://de.pronouns.page/dey“) Du kannst entweder ein <strong>Pronomen</strong> (z.B. „sier“ oder „sie/ihr“) oder einen <strong>Link</strong> (z.B. „https://de.pronouns.page/dey“)
oder <strong>vier benutzerdefinierten Formen</strong> (z.B. „xier/xies/xiem/xien“). oder <strong>vier benutzerdefinierten Formen</strong> (z.B. „xier/xies/xiem/xien“).
Du kannst auch den {/pronomen#generator=<strong>Generator</strong>} verwenden, um die Sätze mit benutzerdefinierten Formen auszufüllen. Du kannst auch den {/pronomen#generator=<strong>Generator</strong>} verwenden, um die Sätze mit benutzerdefinierten Formen auszufüllen.
pronounsNotFound: 'Nicht erkennbares Format. Bitte beachte die oben genannte Anweisung.' pronounsNotFound: 'Nicht erkennbares Format. Bitte beachte die oben genannte Anweisung.'
@ -673,7 +676,7 @@ profile:
relationship: 'Verhältnis (z.B. „Partner*in“, „beste*r Freund*in“)' relationship: 'Verhältnis (z.B. „Partner*in“, „beste*r Freund*in“)'
mutual: 'Diese Verbindung besteht in beide Richtungen' mutual: 'Diese Verbindung besteht in beide Richtungen'
yourMentions: yourMentions:
header: 'Mein Umfeld Erwähnungen ' header: 'Erwähnungen aus deinem Umfeld'
description: > description: >
Hier ist die Liste der Personen, die dich zu ihrem Umfeld hinzugefügt haben. Wenn du sie auch zu deinem Hier ist die Liste der Personen, die dich zu ihrem Umfeld hinzugefügt haben. Wenn du sie auch zu deinem
Umfeld hinzufügst, wird auf beiden Karten ein Häkchen [s:shield-check] angezeigt. Umfeld hinzufügst, wird auf beiden Karten ein Häkchen [s:shield-check] angezeigt.
@ -739,6 +742,43 @@ profile:
success: 'Deine Visitenkarten wurden erfolgreich wiederhergestellt! Die Seite wird nun neugeladen.' success: 'Deine Visitenkarten wurden erfolgreich wiederhergestellt! Die Seite wird nun neugeladen.'
error: error:
signature: 'Ungültige Signatur, kann die Integrität der Sicherungsdatei nicht verifizieren' signature: 'Ungültige Signatur, kann die Integrität der Sicherungsdatei nicht verifizieren'
markdown:
enable: 'Verwende Formattierung mit Markdown in deiner Visitenkarte'
features: 'Zeige unterstützte Markdownsyntax'
examples:
- 'ein **fettgedruckter** Text'
- 'ein _kursiver_ Text'
- 'ein `Code`-Fragment'
- 'ein ~~durchgestrichener~~ Text'
- 'ein ==markierter== Text'
- 'ein ^hochgestellter^ Text'
- 'ein ~tiefgestellter~ Text'
- >
benutze einen umgedrehten Schrägstrich `\`, um \_Formatierungszeichen\_ \*\*abzuschalten\*\*,
insbesondere bei Gendersternen (wie Autor\*in)
calendar:
header: 'Kalender'
info:
- >
In diesem Abschnitt kannst du deinen eigenen Kalender erstellen,
welcher in deiner Visitenkarte erscheint und den du zu deiner Kalenderapp hinzufügen kannst.
- >
Wähle aus der Liste von öffentlich gefeierten Ereignissen des Queeren Kalenders
oder füge deine eigenen persönlichen Ereignisse hinzu.
Beispiele sind Jahrestage des Coming-outs, der Beginn einer HRT, der Tag einer Namensänderung, Jahrestage in deinen Beziehungen etc.
customEvents:
header: 'Persönliche Ereignisse'
disclaimer: 'Dieses Ereignis ist ein persönliches Ereignis, welches von der Person dieser Visitenkarte erstellt wurde'
name: 'Ereignisname (z.B. „HRT begonnen“)'
month: 'Monat'
day: 'Tag'
comment: 'Kommentar (optional)'
validation:
missingName: 'Ereignisname ist erforderlich'
missingDate: 'Datum ist erforderlich'
invalidDate: 'Datum ist ungültig'
publicEvents:
header: 'Ereignisse aus dem Queeren Kalender'
share: 'Teilen' share: 'Teilen'
@ -1160,6 +1200,8 @@ calendar:
list: 'Ereignisliste' list: 'Ereignisliste'
link: 'Link' link: 'Link'
full: 'Ganzer Kalender' full: 'Ganzer Kalender'
onlyFirstDays: 'Version, bei der bei langen Ereignissen nur der jeweils erste Tag eingetragen ist'
start: 'Beginn'
supportBanners: supportBanners:
ukraine: ukraine:

View File

@ -126,7 +126,7 @@ export default [
'footer.source', 'footer.source',
'error.invalidImage', 'error.invalidImage',
'profile.banner', 'profile.banner',
'pronoun.example', 'profile.example',
'calendar.onlyFirstDays', 'calendar.onlyFirstDays',
'calendar.start', 'calendar.start',
'calendar.events.nonmonogamy_visibility_day', 'calendar.events.nonmonogamy_visibility_day',

View File

@ -358,11 +358,10 @@ contact:
team: team:
name: 'The “Neutral Language Council” collective' # TODO name: 'The “Neutral Language Council” collective' # TODO
nameShort: 'Collective' nameShort: 'Collective'
description: # TODO description: > # TODO
- > We are a queer collective dedicated to assembling, researching, shaping and promoting
We are a queer collective dedicated to assembling, researching, shaping and promoting gender neutral and nonbinary language.
gender neutral and nonbinary language. We also support actions towards equality and social justice.
We also support actions towards equality and social justice.
logo: 'Logo of the collective is a combination of the transgender symbol and a speech bubble that symbolises language.' # TODO logo: 'Logo of the collective is a combination of the transgender symbol and a speech bubble that symbolises language.' # TODO
members: 'Current members' # TODO members: 'Current members' # TODO
member: 'Member of the collective' # TODO member: 'Member of the collective' # TODO

View File

@ -84,7 +84,7 @@ export default ({ app, store }) => {
} }
app.router.afterEach(() => { app.router.afterEach(() => {
if (typeof window !== 'undefined' && window.fusetag) { if (typeof window !== 'undefined' && window.fusetag && window.fusetag.pageInit) {
window.fusetag.pageInit(); window.fusetag.pageInit();
} }
}); });

View File

@ -19,6 +19,16 @@ export const buildList = (fn, ...args) => {
return list; return list;
} }
export const deepGet = (obj, path) => {
let value = obj;
for (let part of path.split('.')) {
value = value[part];
if (value === undefined) { break; }
}
return value;
}
export const head = ({title, description, banner, noindex = false, keywords}) => { export const head = ({title, description, banner, noindex = false, keywords}) => {
const meta = { meta: [] }; const meta = { meta: [] };

View File

@ -4,19 +4,10 @@ import Plausible from 'plausible-api';
import fetch from 'node-fetch'; import fetch from 'node-fetch';
import { ulid } from 'ulid'; import { ulid } from 'ulid';
import expectedTranslations from '../locale/expectedTranslations.js'; import expectedTranslations from '../locale/expectedTranslations.js';
import { deepGet } from './helpers.js';
import fs from 'fs'; import fs from 'fs';
import Suml from 'suml'; import Suml from 'suml';
const deepGet = (obj, path) => {
let value = obj;
for (let part of path.split('.')) {
value = value[part];
if (value === undefined) { break; }
}
return value;
}
const formatDate = d => `${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, '0')}-${d.getDate().toString().padStart(2, '0')}`; const formatDate = d => `${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, '0')}-${d.getDate().toString().padStart(2, '0')}`;
export const buildChart = (rows, cumulative = true) => { export const buildChart = (rows, cumulative = true) => {

View File

@ -1,3 +1,4 @@
import { deepGet } from './helpers.js';
import translations from '../data/translations.suml'; import translations from '../data/translations.suml';
import baseTranslations from '../locale/_base/translations.suml'; import baseTranslations from '../locale/_base/translations.suml';
import expectedTranslations from '../locale/expectedTranslations.js'; import expectedTranslations from '../locale/expectedTranslations.js';
@ -17,17 +18,14 @@ class Translator {
} }
get(key, warn = false, base = false, useFallback = true) { get(key, warn = false, base = false, useFallback = true) {
let value = base ? this.baseTranslations : this.translations; const translations = base ? this.baseTranslations : this.translations;
for (let part of key.split('.')) { const value = deepGet(translations, key);
value = value[part]; if (value === undefined) {
if (value === undefined) { if (warn) {
if (warn) { console.error('Cannot find translation: ' + key);
console.error('Cannot find translation: ' + key); }
} if (!base && useFallback) {
if (!base && useFallback) { return this.get(key, warn, true);
return this.get(key, warn, true);
}
return undefined;
} }
} }
return value; return value;

69
test/translations.test.js Normal file
View File

@ -0,0 +1,69 @@
import { expect, test } from '@jest/globals';
import { loadSumlFromBase } from '../server/loader.js';
import { deepGet } from '../src/helpers.js';
import expectedTranslations from '../locale/expectedTranslations.js';
import locales from '../locale/locales.js';
const baseTranslations = loadSumlFromBase('locale/_base/translations');
const typeFlexibleKeys = new Set(['home.generator.alt']);
function specificTypeOf(value) {
if (typeof(value) === 'object') {
if (value === null) {
return null;
} else if (Array.isArray(value)) {
return 'array';
} else {
return 'object';
}
} else {
return typeof(value);
}
}
function toMatchBaseTranslationsSchema(actual) {
const messages = recursivelyValidateSchema(actual, baseTranslations);
if (messages.length > 0) {
return {
message: () =>
`expected translations to match schema of base translations\n\n${messages.join('\n')}`,
pass: false,
}
} else {
return {
message: () =>
'expected translations to mismatch schema of base translations',
pass: true,
}
}
}
function recursivelyValidateSchema(actual, base, parentKey = '') {
const messages = [];
for (const [property, value] of Object.entries(actual)) {
const key = parentKey ? `${parentKey}.${property}` : property;
if (base[property] === undefined || base[property] === null) {
continue;
}
if (value !== null && !typeFlexibleKeys.has(key) && specificTypeOf(value) !== specificTypeOf(base[property])) {
messages.push(`${key} has type ${specificTypeOf(value)}, expected ${specificTypeOf(base[property])}`);
}
if (specificTypeOf(value) === 'object') {
messages.push(...recursivelyValidateSchema(value, base[property], key));
}
}
return messages;
}
test.each(locales)('translations of $code match schema of base translations', ({ code }) => {
const translations = loadSumlFromBase(`locale/${code}/translations`);
expect(translations).toMatchBaseTranslationSchema();
});
test('expected translations are defined in base translations', () => {
const undefinedTranslations = expectedTranslations.filter(key => deepGet(baseTranslations, key) === undefined);
expect(undefinedTranslations).toEqual([]);
});
expect.extend({ toMatchBaseTranslationSchema: toMatchBaseTranslationsSchema });