feat(i18n): add French language support and enhance localization
All checks were successful
Deploy to production / deploy (push) Successful in 2m48s
All checks were successful
Deploy to production / deploy (push) Successful in 2m48s
- Introduced French as a supported language across the application, updating locale files and adding translations for various components. - Enhanced language handling logic to accommodate French, ensuring proper detection and fallback mechanisms. - Updated UI elements to include French language options, improving accessibility for French-speaking users. - Refactored SEO handling to include French in hreflang links, enhancing search engine indexing for multilingual content. - Added new scripts for managing French translations and ensuring consistency across language files.
This commit is contained in:
@@ -4,9 +4,9 @@
|
||||
<div class="falukant-branch">
|
||||
<section class="branch-hero surface-card">
|
||||
<div>
|
||||
<span class="branch-kicker">Niederlassung</span>
|
||||
<span class="branch-kicker">{{ $t('falukant.branch.heroEyebrow') }}</span>
|
||||
<h2>{{ $t('falukant.branch.title') }}</h2>
|
||||
<p>Produktion, Lager, Verkauf und Transport in einer spielweltbezogenen Steuerfläche.</p>
|
||||
<p>{{ $t('falukant.branch.heroIntro') }}</p>
|
||||
<div class="branch-hero__meta">
|
||||
<span class="branch-hero__badge">
|
||||
{{ $t('falukant.branch.currentCertificate') }}: {{ currentCertificate ?? '---' }}
|
||||
@@ -880,14 +880,14 @@ export default {
|
||||
|
||||
conditionLabel(value) {
|
||||
const v = Number(value) || 0;
|
||||
if (v >= 95) return 'Ausgezeichnet'; // 95–100
|
||||
if (v >= 72) return 'Sehr gut'; // 72–94
|
||||
if (v >= 54) return 'Gut'; // 54–71
|
||||
if (v >= 39) return 'Mäßig'; // 39–53
|
||||
if (v >= 22) return 'Schlecht'; // 22–38
|
||||
if (v >= 6) return 'Sehr schlecht'; // 6–21
|
||||
if (v >= 1) return 'Katastrophal'; // 1–5
|
||||
return 'Unbekannt';
|
||||
if (v >= 95) return this.$t('falukant.conditionBand.excellent');
|
||||
if (v >= 72) return this.$t('falukant.conditionBand.veryGood');
|
||||
if (v >= 54) return this.$t('falukant.conditionBand.good');
|
||||
if (v >= 39) return this.$t('falukant.conditionBand.moderate');
|
||||
if (v >= 22) return this.$t('falukant.conditionBand.bad');
|
||||
if (v >= 6) return this.$t('falukant.conditionBand.veryBad');
|
||||
if (v >= 1) return this.$t('falukant.conditionBand.catastrophic');
|
||||
return this.$t('falukant.conditionBand.unknown');
|
||||
},
|
||||
|
||||
speedLabel(value) {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<div class="family-content">
|
||||
<section class="family-hero surface-card">
|
||||
<div>
|
||||
<span class="family-kicker">Familie</span>
|
||||
<span class="family-kicker">{{ $t('falukant.family.title') }}</span>
|
||||
<h2>{{ $t('falukant.family.title') }}</h2>
|
||||
<p>{{ $t('falukant.family.heroIntro') }}</p>
|
||||
</div>
|
||||
|
||||
@@ -213,14 +213,14 @@ export default {
|
||||
},
|
||||
conditionLabel(value) {
|
||||
const v = Number(value) || 0;
|
||||
if (v >= 95) return 'Ausgezeichnet'; // 95–100
|
||||
if (v >= 72) return 'Sehr gut'; // 72–94
|
||||
if (v >= 54) return 'Gut'; // 54–71
|
||||
if (v >= 39) return 'Mäßig'; // 39–53
|
||||
if (v >= 22) return 'Schlecht'; // 22–38
|
||||
if (v >= 6) return 'Sehr schlecht'; // 6–21
|
||||
if (v >= 1) return 'Katastrophal'; // 1–5
|
||||
return 'Unbekannt';
|
||||
if (v >= 95) return this.$t('falukant.conditionBand.excellent');
|
||||
if (v >= 72) return this.$t('falukant.conditionBand.veryGood');
|
||||
if (v >= 54) return this.$t('falukant.conditionBand.good');
|
||||
if (v >= 39) return this.$t('falukant.conditionBand.moderate');
|
||||
if (v >= 22) return this.$t('falukant.conditionBand.bad');
|
||||
if (v >= 6) return this.$t('falukant.conditionBand.veryBad');
|
||||
if (v >= 1) return this.$t('falukant.conditionBand.catastrophic');
|
||||
return this.$t('falukant.conditionBand.unknown');
|
||||
},
|
||||
houseStyle(position, picSize) {
|
||||
const columns = 3;
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<div v-else class="advance-section">
|
||||
<p style="color: red;">Fehler: Keine nächste Titel-Information verfügbar. Bitte Seite neu laden.</p>
|
||||
<p style="color: red;">{{ $t('falukant.nobility.advanceNoNext') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -149,10 +149,10 @@
|
||||
const base = this.$t('falukant.nobility.errors.unmet');
|
||||
this.$root.$refs.errorDialog?.open(`${base}\n${items}`);
|
||||
} else {
|
||||
this.$root.$refs.errorDialog?.open('tr:falukant.nobility.errors.generic');
|
||||
this.$root.$refs.errorDialog?.open(this.$t('falukant.nobility.errors.generic'));
|
||||
}
|
||||
} else {
|
||||
this.$root.$refs.errorDialog?.open('tr:falukant.nobility.errors.generic');
|
||||
this.$root.$refs.errorDialog?.open(this.$t('falukant.nobility.errors.generic'));
|
||||
}
|
||||
} finally {
|
||||
this.isAdvancing = false;
|
||||
@@ -171,35 +171,20 @@
|
||||
const amount = ['money', 'cost'].includes(type)
|
||||
? this.formatCost(numericValue)
|
||||
: rawValue;
|
||||
if (type === 'house_position') {
|
||||
const label = this.housePositionLabel(numericValue);
|
||||
return this.$t('falukant.nobility.requirement.house_position', { label });
|
||||
}
|
||||
if (type === 'house_condition') {
|
||||
const quality = this.formatHouseCondition(numericValue);
|
||||
return this.$t('falukant.nobility.requirement.house_condition', { quality });
|
||||
}
|
||||
const key = `falukant.nobility.requirement.${type}`;
|
||||
const translated = this.$t(key, { amount });
|
||||
if (translated && translated !== key && !['house_position', 'house_condition'].includes(type)) {
|
||||
if (translated && translated !== key) {
|
||||
return translated;
|
||||
}
|
||||
switch (type) {
|
||||
case 'money':
|
||||
return `Vermögen mindestens ${amount}`;
|
||||
case 'cost':
|
||||
return `Kosten: ${amount}`;
|
||||
case 'branches':
|
||||
return `Mindestens ${amount} Niederlassungen`;
|
||||
case 'reputation':
|
||||
return `Beliebtheit mindestens ${amount}`;
|
||||
case 'house_position':
|
||||
return `Hausstand mindestens ${this.getHousePositionLabel(numericValue)}`;
|
||||
case 'house_condition':
|
||||
return `Hauszustand mindestens ${this.formatHouseCondition(numericValue)}`;
|
||||
case 'office_rank_any':
|
||||
return `Höchstes politisches oder kirchliches Amt mindestens Rang ${amount}`;
|
||||
case 'office_rank_political':
|
||||
return `Höchstes politisches Amt mindestens Rang ${amount}`;
|
||||
case 'lover_count_min':
|
||||
return `Mindestens ${amount} Liebhaber oder Mätressen`;
|
||||
case 'lover_count_max':
|
||||
return `Höchstens ${amount} Liebhaber oder Mätressen`;
|
||||
default:
|
||||
return `${type}: ${amount}`;
|
||||
}
|
||||
return this.$t('falukant.nobility.requirement.unknown', { type, amount });
|
||||
},
|
||||
formatOfficeInfo(info, source) {
|
||||
if (!info?.name) {
|
||||
@@ -207,32 +192,25 @@
|
||||
}
|
||||
const baseKey = source === 'church' ? 'falukant.church.offices' : 'falukant.politics.positions';
|
||||
const label = this.$te(`${baseKey}.${info.name}`) ? this.$t(`${baseKey}.${info.name}`) : info.name;
|
||||
return `${label} (Rang ${info.rank})`;
|
||||
return this.$t('falukant.nobility.officeWithRank', { label, rank: info.rank });
|
||||
},
|
||||
getHousePositionLabel(position) {
|
||||
const labels = {
|
||||
1: 'Unter der Brücke',
|
||||
2: 'eine Strohhütte',
|
||||
3: 'ein Holzhaus',
|
||||
4: 'ein Hinterhofzimmer',
|
||||
5: 'ein kleines Familienhaus',
|
||||
6: 'ein Stadthaus',
|
||||
7: 'eine Villa',
|
||||
8: 'ein Herrenhaus',
|
||||
9: 'ein Schloss'
|
||||
};
|
||||
return labels[position] || `Haus-Stufe ${position}`;
|
||||
housePositionLabel(position) {
|
||||
const k = `falukant.nobility.housePosition.${position}`;
|
||||
if (this.$te(k)) {
|
||||
return this.$t(k);
|
||||
}
|
||||
return this.$t('falukant.nobility.housePosition.fallback', { level: position });
|
||||
},
|
||||
formatHouseCondition(value) {
|
||||
if (Number.isNaN(value)) {
|
||||
return value;
|
||||
return String(value);
|
||||
}
|
||||
if (value >= 0.95) return 'nahezu makellos';
|
||||
if (value >= 0.9) return 'sehr gut';
|
||||
if (value >= 0.8) return 'gut';
|
||||
if (value >= 0.7) return 'ordentlich';
|
||||
if (value >= 0.6) return 'brauchbar';
|
||||
return `${Math.round(value * 100)} %`;
|
||||
if (value >= 0.95) return this.$t('falukant.nobility.houseConditionQuality.nearPerfect');
|
||||
if (value >= 0.9) return this.$t('falukant.nobility.houseConditionQuality.veryGood');
|
||||
if (value >= 0.8) return this.$t('falukant.nobility.houseConditionQuality.good');
|
||||
if (value >= 0.7) return this.$t('falukant.nobility.houseConditionQuality.decent');
|
||||
if (value >= 0.6) return this.$t('falukant.nobility.houseConditionQuality.usable');
|
||||
return this.$t('falukant.nobility.houseConditionPercent', { pct: Math.round(value * 100) });
|
||||
},
|
||||
formatDate(isoString) {
|
||||
const d = new Date(isoString);
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
<StatusBar />
|
||||
<section class="falukant-hero surface-card">
|
||||
<div>
|
||||
<span class="falukant-kicker">Falukant</span>
|
||||
<span class="falukant-kicker">{{ $t('sectionBar.sections.falukant') }}</span>
|
||||
<h2>{{ $t('falukant.overview.title') }}</h2>
|
||||
<p>Dein Stand in Wirtschaft, Familie und Besitz in einer verdichteten Übersicht.</p>
|
||||
<p>{{ $t('falukant.overview.heroIntro') }}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -54,22 +54,22 @@
|
||||
<article class="summary-card surface-card">
|
||||
<span class="summary-card__label">{{ $t('falukant.overview.metadata.certificate') }}</span>
|
||||
<strong>{{ falukantUser?.certificate ?? '---' }}</strong>
|
||||
<p>Bestimmt, welche Produktkategorien du derzeit herstellen darfst.</p>
|
||||
<p>{{ $t('falukant.overview.summary.certificateHint') }}</p>
|
||||
</article>
|
||||
<article class="summary-card surface-card">
|
||||
<span class="summary-card__label">Niederlassungen</span>
|
||||
<span class="summary-card__label">{{ $t('falukant.overview.summary.branches') }}</span>
|
||||
<strong>{{ branchCount }}</strong>
|
||||
<p>Direkter Zugriff auf deine wichtigsten Geschäftsstandorte.</p>
|
||||
<p>{{ $t('falukant.overview.summary.branchesHint') }}</p>
|
||||
</article>
|
||||
<article class="summary-card surface-card">
|
||||
<span class="summary-card__label">Produktionen aktiv</span>
|
||||
<span class="summary-card__label">{{ $t('falukant.overview.summary.productions') }}</span>
|
||||
<strong>{{ productionCount }}</strong>
|
||||
<p>Laufende Produktionen, die zeitnah Abschluss oder Kontrolle brauchen.</p>
|
||||
<p>{{ $t('falukant.overview.summary.productionsHint') }}</p>
|
||||
</article>
|
||||
<article class="summary-card surface-card">
|
||||
<span class="summary-card__label">Lagerpositionen</span>
|
||||
<span class="summary-card__label">{{ $t('falukant.overview.summary.stock') }}</span>
|
||||
<strong>{{ stockEntryCount }}</strong>
|
||||
<p>Verdichteter Blick auf Warenbestand über alle Regionen.</p>
|
||||
<p>{{ $t('falukant.overview.summary.stockHint') }}</p>
|
||||
</article>
|
||||
<article v-if="falukantUser?.debtorsPrison?.active" class="summary-card surface-card">
|
||||
<span class="summary-card__label">{{ $t('falukant.bank.debtorsPrison.creditworthiness') }}</span>
|
||||
@@ -85,7 +85,7 @@
|
||||
<section v-if="falukantUser?.character" class="falukant-routine-grid">
|
||||
<article
|
||||
v-for="action in routineActions"
|
||||
:key="action.title"
|
||||
:key="action.route"
|
||||
class="routine-card surface-card"
|
||||
>
|
||||
<span class="routine-card__eyebrow">{{ action.kicker }}</span>
|
||||
@@ -309,7 +309,7 @@
|
||||
<span>{{ $t(`falukant.overview.branches.level.${branch.branchType.labelTr}`) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="button-secondary" @click="openBranch(branch.id)">Öffnen</button>
|
||||
<button type="button" class="button-secondary" @click="openBranch(branch.id)">{{ $t('falukant.overview.summary.open') }}</button>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
@@ -495,33 +495,33 @@ export default {
|
||||
routineActions() {
|
||||
return [
|
||||
{
|
||||
kicker: 'Routine',
|
||||
title: 'Niederlassung öffnen',
|
||||
description: 'Die schnellste Route zu Produktion, Lager, Verkauf und Transport.',
|
||||
cta: 'Zu den Betrieben',
|
||||
kicker: this.$t('falukant.overview.routine.branch.kicker'),
|
||||
title: this.$t('falukant.overview.routine.branch.title'),
|
||||
description: this.$t('falukant.overview.routine.branch.description'),
|
||||
cta: this.$t('falukant.overview.routine.branch.cta'),
|
||||
route: 'BranchView',
|
||||
},
|
||||
{
|
||||
kicker: 'Überblick',
|
||||
title: 'Finanzen prüfen',
|
||||
description: 'Kontostand, Verlauf und wirtschaftliche Entwicklung ohne lange Suche.',
|
||||
cta: 'Geldhistorie',
|
||||
kicker: this.$t('falukant.overview.routine.finance.kicker'),
|
||||
title: this.$t('falukant.overview.routine.finance.title'),
|
||||
description: this.$t('falukant.overview.routine.finance.description'),
|
||||
cta: this.$t('falukant.overview.routine.finance.cta'),
|
||||
route: 'MoneyHistoryView',
|
||||
secondary: true,
|
||||
},
|
||||
{
|
||||
kicker: 'Charakter',
|
||||
title: 'Familie und Nachfolge',
|
||||
description: 'Wichtige persönliche Entscheidungen und Haushaltsstatus gesammelt.',
|
||||
cta: 'Familie öffnen',
|
||||
kicker: this.$t('falukant.overview.routine.family.kicker'),
|
||||
title: this.$t('falukant.overview.routine.family.title'),
|
||||
description: this.$t('falukant.overview.routine.family.description'),
|
||||
cta: this.$t('falukant.overview.routine.family.cta'),
|
||||
route: 'FalukantFamily',
|
||||
secondary: true,
|
||||
},
|
||||
{
|
||||
kicker: 'Besitz',
|
||||
title: 'Haus und Umfeld',
|
||||
description: 'Wohnsitz und alltäglicher Status als eigener Arbeitsbereich.',
|
||||
cta: 'Zum Haus',
|
||||
kicker: this.$t('falukant.overview.routine.house.kicker'),
|
||||
title: this.$t('falukant.overview.routine.house.title'),
|
||||
description: this.$t('falukant.overview.routine.house.description'),
|
||||
cta: this.$t('falukant.overview.routine.house.cta'),
|
||||
route: 'HouseView',
|
||||
secondary: true,
|
||||
},
|
||||
@@ -774,7 +774,7 @@ export default {
|
||||
await this.fetchAllStock();
|
||||
await this.fetchProductions();
|
||||
}
|
||||
showSuccess(this, 'Erbe wurde übernommen.');
|
||||
showSuccess(this, this.$t('falukant.overview.heirSelection.success'));
|
||||
} catch (error) {
|
||||
console.error('Error selecting heir:', error);
|
||||
showError(this, this.$t('falukant.overview.heirSelection.error'));
|
||||
|
||||
@@ -149,7 +149,7 @@
|
||||
:value="e.id"
|
||||
:disabled="e.alreadyApplied || e.canApplyByAge === false"
|
||||
/>
|
||||
<span>Für diese Kandidatur vormerken</span>
|
||||
<span>{{ $t('falukant.politics.bookmarkCandidate') }}</span>
|
||||
</label>
|
||||
</article>
|
||||
</div>
|
||||
@@ -552,10 +552,10 @@ export default {
|
||||
{ votes: singlePayload }
|
||||
);
|
||||
await this.loadElections();
|
||||
showSuccess(this, 'Stimme erfolgreich abgegeben.');
|
||||
showSuccess(this, this.$t('falukant.politics.voteSuccess'));
|
||||
} catch (err) {
|
||||
console.error(`Error submitting vote for election ${electionId}`, err);
|
||||
showApiError(this, err, 'Fehler beim Abgeben der Stimme');
|
||||
showApiError(this, err, this.$t('falukant.politics.voteError'));
|
||||
}
|
||||
},
|
||||
|
||||
@@ -573,10 +573,10 @@ export default {
|
||||
{ votes: payload }
|
||||
);
|
||||
await this.loadElections();
|
||||
showSuccess(this, 'Alle Stimmen erfolgreich abgegeben.');
|
||||
showSuccess(this, this.$t('falukant.politics.voteAllSuccess'));
|
||||
} catch (err) {
|
||||
console.error('Error submitting all votes', err);
|
||||
showApiError(this, err, 'Fehler beim Abgeben der Stimmen');
|
||||
showApiError(this, err, this.$t('falukant.politics.voteAllError'));
|
||||
}
|
||||
},
|
||||
|
||||
@@ -632,7 +632,7 @@ export default {
|
||||
this.selectedApplications = this.openPolitics
|
||||
.filter(e => e.alreadyApplied || appliedIds.includes(e.id))
|
||||
.map(e => e.id);
|
||||
showSuccess(this, 'Kandidatur erfolgreich vorgemerkt.');
|
||||
showSuccess(this, this.$t('falukant.politics.applyBookmarkSuccess'));
|
||||
} catch (err) {
|
||||
console.error('Error submitting applications', err);
|
||||
if (err?.response?.data?.error === 'too_young') {
|
||||
|
||||
Reference in New Issue
Block a user