generated from knso/webklar-preview-template
App: Sprachen von 9 auf 27 erweitert + RTL-Darstellung repariert
Neue Zielsprachen fuer die groessten Sprachgemeinschaften in Deutschland, u. a. Kurdisch (Kurmanci), Bulgarisch, Kroatisch/Serbisch/Bosnisch, Albanisch, Italienisch, Griechisch, Spanisch, Vietnamesisch, Hindi, Urdu, Paschtu, Tigrinya, Somali, Chinesisch. Liste nach Groesse der Gemeinschaft in Deutschland sortiert. Ausserdem: Sprache bekommt ein rtl-Flag. Arabisch und Farsi wurden bisher linksbuendig dargestellt, was bei diesen Schriften falsch ist — jetzt laufen Kernaussage, Erklaerung, To-dos und Fachbegriff-Erklaerungen bei ar/fa/ur/ps von rechts nach links (der deutsche Fachbegriff bleibt links). Neuer Test sprachen.test.ts schuetzt vor doppelten Codes (die wuerden den Uebersetzungs-Cache ueberschreiben). 35 Tests gruen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8dc9074691
commit
464e9b109f
3 files changed
+77
-10
No files matched your search
@@ -72,6 +72,11 @@ export function AnalyseScreen({ navigation, route }: Props) {
|
|||||||
const checkliste = cache?.checkliste ?? analyse.checkliste;
|
const checkliste = cache?.checkliste ?? analyse.checkliste;
|
||||||
const fachbegriffe = cache?.fachbegriffe ?? analyse.fachbegriffe;
|
const fachbegriffe = cache?.fachbegriffe ?? analyse.fachbegriffe;
|
||||||
|
|
||||||
|
// Arabisch, Farsi, Urdu, Paschtu laufen von rechts nach links. Ohne das
|
||||||
|
// steht der übersetzte Text linksbündig und liest sich falsch.
|
||||||
|
const rtl = !!sprache.rtl;
|
||||||
|
const rtlText = rtl ? ({ writingDirection: 'rtl', textAlign: 'right' } as const) : null;
|
||||||
|
|
||||||
const sprscheWaehlen = async (neu: Sprache) => {
|
const sprscheWaehlen = async (neu: Sprache) => {
|
||||||
setSprachwahlOffen(false);
|
setSprachwahlOffen(false);
|
||||||
setSprache(neu);
|
setSprache(neu);
|
||||||
@@ -221,8 +226,8 @@ export function AnalyseScreen({ navigation, route }: Props) {
|
|||||||
|
|
||||||
{/* ── Das ist passiert ── */}
|
{/* ── Das ist passiert ── */}
|
||||||
<Text style={styles.kicker}>Das ist passiert</Text>
|
<Text style={styles.kicker}>Das ist passiert</Text>
|
||||||
<Text style={styles.kernaussage}>{kernaussage}</Text>
|
<Text style={[styles.kernaussage, rtlText]}>{kernaussage}</Text>
|
||||||
<Text style={styles.erklaerung}>{erklaerung}</Text>
|
<Text style={[styles.erklaerung, rtlText]}>{erklaerung}</Text>
|
||||||
|
|
||||||
{/* ── Das müssen Sie tun ── */}
|
{/* ── Das müssen Sie tun ── */}
|
||||||
{checkliste.length > 0 && (
|
{checkliste.length > 0 && (
|
||||||
@@ -235,7 +240,7 @@ export function AnalyseScreen({ navigation, route }: Props) {
|
|||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
key={i}
|
key={i}
|
||||||
style={styles.todoZeile}
|
style={[styles.todoZeile, rtl && { flexDirection: 'row-reverse' as const }]}
|
||||||
onPress={() => setErledigt((e) => ({ ...e, [i]: !e[i] }))}
|
onPress={() => setErledigt((e) => ({ ...e, [i]: !e[i] }))}
|
||||||
accessibilityRole="checkbox"
|
accessibilityRole="checkbox"
|
||||||
accessibilityState={{ checked: fertig }}
|
accessibilityState={{ checked: fertig }}
|
||||||
@@ -247,7 +252,7 @@ export function AnalyseScreen({ navigation, route }: Props) {
|
|||||||
<Text style={styles.todoNummer}>{i + 1}</Text>
|
<Text style={styles.todoNummer}>{i + 1}</Text>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
<Text style={[styles.todoText, fertig && styles.durchgestrichen]}>{punkt}</Text>
|
<Text style={[styles.todoText, fertig && styles.durchgestrichen, rtlText]}>{punkt}</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -264,7 +269,7 @@ export function AnalyseScreen({ navigation, route }: Props) {
|
|||||||
{fachbegriffe.map((f, i) => (
|
{fachbegriffe.map((f, i) => (
|
||||||
<View key={i} style={styles.begriffKarte}>
|
<View key={i} style={styles.begriffKarte}>
|
||||||
<Text style={styles.begriff}>{f.begriff}</Text>
|
<Text style={styles.begriff}>{f.begriff}</Text>
|
||||||
<Text style={styles.begriffText}>{f.erklaerung}</Text>
|
<Text style={[styles.begriffText, rtlText]}>{f.erklaerung}</Text>
|
||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
+30
-5
@@ -90,18 +90,43 @@ export interface Sprache {
|
|||||||
name: string;
|
name: string;
|
||||||
/** Name in der Sprache selbst (damit Nutzer ihre Sprache erkennen). */
|
/** Name in der Sprache selbst (damit Nutzer ihre Sprache erkennen). */
|
||||||
eigenname: string;
|
eigenname: string;
|
||||||
|
/** Schrift läuft von rechts nach links (Arabisch, Farsi, Urdu, Paschtu). */
|
||||||
|
rtl?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zielsprachen für die Übersetzung — grob nach Größe der jeweiligen
|
||||||
|
* Sprachgemeinschaft in Deutschland sortiert, damit die häufigsten oben
|
||||||
|
* stehen und man nicht lange scrollen muss.
|
||||||
|
*/
|
||||||
export const SPRACHEN: Sprache[] = [
|
export const SPRACHEN: Sprache[] = [
|
||||||
{ code: 'tr', name: 'Türkisch', eigenname: 'Türkçe' },
|
{ code: 'tr', name: 'Türkisch', eigenname: 'Türkçe' },
|
||||||
{ code: 'ar', name: 'Arabisch', eigenname: 'العربية' },
|
{ code: 'ar', name: 'Arabisch', eigenname: 'العربية', rtl: true },
|
||||||
{ code: 'en', name: 'Englisch', eigenname: 'English' },
|
|
||||||
{ code: 'ru', name: 'Russisch', eigenname: 'Русский' },
|
{ code: 'ru', name: 'Russisch', eigenname: 'Русский' },
|
||||||
{ code: 'uk', name: 'Ukrainisch', eigenname: 'Українська' },
|
{ code: 'uk', name: 'Ukrainisch', eigenname: 'Українська' },
|
||||||
{ code: 'fr', name: 'Französisch', eigenname: 'Français' },
|
|
||||||
{ code: 'fa', name: 'Farsi', eigenname: 'فارسی' },
|
|
||||||
{ code: 'ro', name: 'Rumänisch', eigenname: 'Română' },
|
|
||||||
{ code: 'pl', name: 'Polnisch', eigenname: 'Polski' },
|
{ code: 'pl', name: 'Polnisch', eigenname: 'Polski' },
|
||||||
|
{ code: 'ro', name: 'Rumänisch', eigenname: 'Română' },
|
||||||
|
{ code: 'en', name: 'Englisch', eigenname: 'English' },
|
||||||
|
{ code: 'fa', name: 'Farsi / Dari', eigenname: 'فارسی', rtl: true },
|
||||||
|
{ code: 'ku', name: 'Kurdisch (Kurmancî)', eigenname: 'Kurdî' },
|
||||||
|
{ code: 'bg', name: 'Bulgarisch', eigenname: 'Български' },
|
||||||
|
{ code: 'hr', name: 'Kroatisch', eigenname: 'Hrvatski' },
|
||||||
|
{ code: 'sr', name: 'Serbisch', eigenname: 'Српски' },
|
||||||
|
{ code: 'bs', name: 'Bosnisch', eigenname: 'Bosanski' },
|
||||||
|
{ code: 'sq', name: 'Albanisch', eigenname: 'Shqip' },
|
||||||
|
{ code: 'it', name: 'Italienisch', eigenname: 'Italiano' },
|
||||||
|
{ code: 'el', name: 'Griechisch', eigenname: 'Ελληνικά' },
|
||||||
|
{ code: 'es', name: 'Spanisch', eigenname: 'Español' },
|
||||||
|
{ code: 'pt', name: 'Portugiesisch', eigenname: 'Português' },
|
||||||
|
{ code: 'fr', name: 'Französisch', eigenname: 'Français' },
|
||||||
|
{ code: 'hu', name: 'Ungarisch', eigenname: 'Magyar' },
|
||||||
|
{ code: 'vi', name: 'Vietnamesisch', eigenname: 'Tiếng Việt' },
|
||||||
|
{ code: 'hi', name: 'Hindi', eigenname: 'हिन्दी' },
|
||||||
|
{ code: 'ur', name: 'Urdu', eigenname: 'اردو', rtl: true },
|
||||||
|
{ code: 'ps', name: 'Paschtu', eigenname: 'پښتو', rtl: true },
|
||||||
|
{ code: 'ti', name: 'Tigrinya', eigenname: 'ትግርኛ' },
|
||||||
|
{ code: 'so', name: 'Somali', eigenname: 'Soomaali' },
|
||||||
|
{ code: 'zh', name: 'Chinesisch', eigenname: '中文' },
|
||||||
];
|
];
|
||||||
|
|
||||||
/** Untere Tab-Leiste: Fristen-Übersicht · Scannen · Archiv. */
|
/** Untere Tab-Leiste: Fristen-Übersicht · Scannen · Archiv. */
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* Schützt die Sprachliste vor stillen Fehlern. Besonders wichtig: doppelte
|
||||||
|
* Codes — Übersetzungen werden pro Code gecacht (brief.uebersetzungen[code]),
|
||||||
|
* zwei gleiche Codes würden sich gegenseitig überschreiben.
|
||||||
|
*/
|
||||||
|
import { SPRACHEN } from '../../types';
|
||||||
|
|
||||||
|
describe('SPRACHEN', () => {
|
||||||
|
it('hat keine doppelten Sprachcodes', () => {
|
||||||
|
const codes = SPRACHEN.map((s) => s.code);
|
||||||
|
expect(new Set(codes).size).toBe(codes.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('enthält kein Deutsch (das ist die Ausgangssprache, nicht Zielsprache)', () => {
|
||||||
|
expect(SPRACHEN.some((s) => s.code === 'de')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('hat für jede Sprache Code, Name und Eigenname', () => {
|
||||||
|
for (const s of SPRACHEN) {
|
||||||
|
expect(s.code).toMatch(/^[a-z]{2,3}$/);
|
||||||
|
expect(s.name.trim().length).toBeGreaterThan(0);
|
||||||
|
expect(s.eigenname.trim().length).toBeGreaterThan(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('markiert genau die Rechts-nach-links-Sprachen', () => {
|
||||||
|
const rtl = SPRACHEN.filter((s) => s.rtl).map((s) => s.code).sort();
|
||||||
|
expect(rtl).toEqual(['ar', 'fa', 'ps', 'ur']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('bietet die für Deutschland wichtigsten Sprachen an', () => {
|
||||||
|
const codes = SPRACHEN.map((s) => s.code);
|
||||||
|
for (const pflicht of ['tr', 'ar', 'ru', 'uk', 'pl', 'ro', 'en', 'fa']) {
|
||||||
|
expect(codes).toContain(pflicht);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in new issue
Block a user