First commit, done on M6 from the PLAN.MD
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
{
|
||||
"app.title": "The Desk",
|
||||
"dash.title": "Ministerial Briefing",
|
||||
"dash.confidential": "Strictly confidential",
|
||||
"dash.econ": "Economic figures",
|
||||
"dash.intel": "Intelligence summary",
|
||||
"dash.classified": "Classified",
|
||||
"ind.order": "Order",
|
||||
"ind.stability": "Stability",
|
||||
"ind.civilRights": "Civil rights",
|
||||
"ind.opinion": "Public opinion",
|
||||
"ind.economy": "Economy",
|
||||
"ind.hardPower": "Hard power",
|
||||
"ind.softPower": "Soft power",
|
||||
"ind.unrest": "Unrest",
|
||||
"ind.armyLoyalty": "Army loyalty",
|
||||
"opinion.0": "Angry",
|
||||
"opinion.1": "Hostile",
|
||||
"opinion.2": "Uneasy",
|
||||
"opinion.3": "Neutral",
|
||||
"opinion.4": "Content",
|
||||
"opinion.5": "Happy",
|
||||
"opinion.6": "Ecstatic",
|
||||
"unrest.0": "Calm",
|
||||
"unrest.1": "Restless",
|
||||
"unrest.2": "Tense",
|
||||
"unrest.3": "Volatile",
|
||||
"unrest.4": "Explosive",
|
||||
"army.0": "Mutinous",
|
||||
"army.1": "Wavering",
|
||||
"army.2": "Steady",
|
||||
"army.3": "Loyal",
|
||||
"econ.gdp": "GDP",
|
||||
"econ.growth": "Growth",
|
||||
"econ.unemployment": "Unemployment",
|
||||
"econ.inflation": "Inflation",
|
||||
"econ.debt": "Debt / GDP",
|
||||
"econ.deficit": "Deficit / GDP",
|
||||
"econ.rate": "Interest rate",
|
||||
"econ.gdpUnit": "bn {currency}",
|
||||
"cal.dayCount": "Day {day} of {total}",
|
||||
"cal.briefing": "Next briefing",
|
||||
"cal.review": "Budget review",
|
||||
"cal.inDays": "in {n} days",
|
||||
"cal.today": "today",
|
||||
"cal.tomorrow": "tomorrow",
|
||||
"cal.pause": "Pause",
|
||||
"cal.speed": "Speed {n}×",
|
||||
"inbox.title": "In-tray",
|
||||
"inbox.idle": "Nothing awaits your signature. The country carries on.",
|
||||
"inbox.overTitle": "End of term",
|
||||
"inbox.overBody": "Your term has ended. (The final review arrives in a later build.)",
|
||||
"news.edition": "Edition of {date}",
|
||||
"sector.education": "Education",
|
||||
"sector.health": "Health",
|
||||
"sector.social": "Social & pensions",
|
||||
"sector.police": "Police",
|
||||
"sector.army": "Army",
|
||||
"sector.justice": "Justice",
|
||||
"sector.industry": "Industry & energy",
|
||||
"sector.transport": "Public transport",
|
||||
"sector.infrastructure": "Infrastructure",
|
||||
"sector.housing": "Housing",
|
||||
"sector.research": "Research",
|
||||
"sector.culture": "Culture & media",
|
||||
"sector.foreignAffairs": "Foreign affairs",
|
||||
"sector.environment": "Environment",
|
||||
"sector.administration": "Administration",
|
||||
"tax.income": "Income tax",
|
||||
"tax.corporate": "Corporate tax",
|
||||
"tax.vat": "VAT",
|
||||
"ledger.title": "Treasury ledger",
|
||||
"ledger.spending": "Spending, % of GDP",
|
||||
"ledger.spendingHint": "The tick on each line marks what the sector needs.",
|
||||
"ledger.totalAlloc": "Allocated to sectors",
|
||||
"ledger.revenue": "Revenue: tax rates",
|
||||
"ledger.revenueHint": "The tick marks the rate where revenue peaks. Beyond it, you collect less.",
|
||||
"ledger.yield": "{n} % of GDP",
|
||||
"ledger.quality": "Quality {now} → {target}",
|
||||
"ledger.summary": "Treasury summary",
|
||||
"ledger.sumRevenue": "Revenue",
|
||||
"ledger.sumSectors": "Sector spending",
|
||||
"ledger.sumInterest": "Debt interest",
|
||||
"ledger.sumSpending": "Total spending",
|
||||
"ledger.sumSurplus": "Surplus",
|
||||
"ledger.sumDeficit": "Deficit",
|
||||
"ledger.perYear": "{n} bn {currency} per year",
|
||||
"ledger.projection": "Next four quarters, if nothing else changes",
|
||||
"ledger.now": "Now",
|
||||
"ledger.months": "+{n} mo",
|
||||
"ledger.reset": "Reset",
|
||||
"ledger.confirm": "Sign and file",
|
||||
"ledger.error": "The Treasury rejected this budget.",
|
||||
"kind.memo": "Memorandum",
|
||||
"kind.telegram": "Telegram",
|
||||
"kind.petition": "Petition",
|
||||
"kind.report": "Report",
|
||||
"kind.letter": "Letter",
|
||||
"card.approved": "Approved",
|
||||
"card.rejected": "Rejected",
|
||||
"card.keys": "Keys: Y approve · N reject",
|
||||
"card.from": "From",
|
||||
"profile.title": "Sworn in",
|
||||
"profile.lead": "Before you take office, the Press Office needs one thing for the official records.",
|
||||
"profile.body": "How should the press refer to you?",
|
||||
"profile.he": "He / Him",
|
||||
"profile.she": "She / Her",
|
||||
"profile.they": "They / Them",
|
||||
"profile.note": "This only changes how you are named in the history books.",
|
||||
"alert.urgent": "Urgent telegram",
|
||||
"alert.ack": "Understood",
|
||||
"ind.regime": "Hold on power",
|
||||
"regime.0": "Secure",
|
||||
"regime.1": "Shaky",
|
||||
"regime.2": "Critical",
|
||||
"end.verdict": "The historians' verdict",
|
||||
"end.score": "Score {n} / 100",
|
||||
"end.grade": "Grade",
|
||||
"end.served": "Served {days} days",
|
||||
"end.decisions": "Decisions that defined the term",
|
||||
"end.noDecisions": "No decision was signed.",
|
||||
"end.chose": "You chose: {label}",
|
||||
"end.again": "Play again",
|
||||
"end.averages": "avg",
|
||||
"end.final": "end",
|
||||
"cal.menu": "Menu",
|
||||
"menu.secret": "Top secret",
|
||||
"menu.tagline": "A country. Five years. Every decision carries your signature.",
|
||||
"menu.paused": "Game paused",
|
||||
"menu.continue": "Continue",
|
||||
"menu.resume": "Resume",
|
||||
"menu.new": "New game",
|
||||
"menu.load": "Load game",
|
||||
"menu.save": "Save game",
|
||||
"menu.export": "Export",
|
||||
"menu.import": "Import",
|
||||
"menu.settings": "Settings",
|
||||
"menu.credits": "Credits",
|
||||
"menu.quit": "Main menu",
|
||||
"menu.back": "Back",
|
||||
"menu.info": "{country} · day {day}",
|
||||
"new.title": "New game",
|
||||
"new.name": "Name of the country",
|
||||
"new.scenario": "Scenario",
|
||||
"new.pronouns": "How should the press refer to you?",
|
||||
"new.start": "Take office",
|
||||
"load.title": "Load game",
|
||||
"load.auto": "Autosave",
|
||||
"load.slot": "Slot {n}",
|
||||
"load.empty": "Empty",
|
||||
"load.info": "{country} · day {day}{date}",
|
||||
"load.finished": "finished",
|
||||
"load.unreadable": "Unreadable save",
|
||||
"load.load": "Load",
|
||||
"load.delete": "Delete",
|
||||
"load.sure": "Sure?",
|
||||
"load.replace": "Loading replaces the game in progress.",
|
||||
"save.title": "Save game",
|
||||
"save.here": "Save here",
|
||||
"save.overwrite": "Overwrite",
|
||||
"save.done": "Saved to slot {n}.",
|
||||
"save.failed": "The browser refused the save. Use Export to keep your game.",
|
||||
"export.title": "Export",
|
||||
"export.hint": "Copy this text or download the file. You can import it later, on any device.",
|
||||
"export.copy": "Copy",
|
||||
"export.copied": "Copied.",
|
||||
"export.copyFailed": "Select the text and copy it by hand.",
|
||||
"export.download": "Download .sav",
|
||||
"import.title": "Import",
|
||||
"import.hint": "Paste a save string, or choose a .sav file.",
|
||||
"import.file": "Choose a file",
|
||||
"import.go": "Import",
|
||||
"import.error.format": "That does not look like a save string.",
|
||||
"import.error.checksum": "The save is damaged or incomplete (checksum mismatch).",
|
||||
"import.error.tooNew": "This save comes from a newer version of the game.",
|
||||
"import.error.version": "This save is from a version that cannot be converted.",
|
||||
"import.error.invalid": "The contents of this save are not valid.",
|
||||
"import.error.scenario": "This save uses a scenario that is not installed.",
|
||||
"import.error.empty": "Nothing to import yet.",
|
||||
"settings.title": "Settings",
|
||||
"settings.language": "Language",
|
||||
"settings.slow": "Slow time",
|
||||
"settings.slowHint": "One in-game day takes 60 seconds instead of 10.",
|
||||
"settings.storage": "This browser is not keeping saves between visits (private window or blocked storage). Use Export to keep your game.",
|
||||
"credits.title": "Credits",
|
||||
"credits.fiction": "Valdoria and everyone in it are fictional.",
|
||||
"credits.tech": "Built with Go and plain JavaScript, with no third-party libraries.",
|
||||
"credits.fonts": "Fonts: Special Elite (Apache License 2.0), IM Fell English and Courier Prime (SIL Open Font License). The licence files are shipped with the game."
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
{
|
||||
"app.title": "Le Bureau",
|
||||
"dash.title": "Note ministérielle",
|
||||
"dash.confidential": "Strictement confidentiel",
|
||||
"dash.econ": "Chiffres économiques",
|
||||
"dash.intel": "Synthèse du renseignement",
|
||||
"dash.classified": "Classifié",
|
||||
"ind.order": "Ordre public",
|
||||
"ind.stability": "Stabilité",
|
||||
"ind.civilRights": "Droits civiques",
|
||||
"ind.opinion": "Opinion publique",
|
||||
"ind.economy": "Économie",
|
||||
"ind.hardPower": "Puissance militaire",
|
||||
"ind.softPower": "Influence",
|
||||
"ind.unrest": "Agitation",
|
||||
"ind.armyLoyalty": "Loyauté de l'armée",
|
||||
"opinion.0": "En colère",
|
||||
"opinion.1": "Hostile",
|
||||
"opinion.2": "Inquiète",
|
||||
"opinion.3": "Neutre",
|
||||
"opinion.4": "Satisfaite",
|
||||
"opinion.5": "Heureuse",
|
||||
"opinion.6": "Extatique",
|
||||
"unrest.0": "Calme",
|
||||
"unrest.1": "Agité",
|
||||
"unrest.2": "Tendu",
|
||||
"unrest.3": "Volatil",
|
||||
"unrest.4": "Explosif",
|
||||
"army.0": "Mutinée",
|
||||
"army.1": "Hésitante",
|
||||
"army.2": "Stable",
|
||||
"army.3": "Loyale",
|
||||
"econ.gdp": "PIB",
|
||||
"econ.growth": "Croissance",
|
||||
"econ.unemployment": "Chômage",
|
||||
"econ.inflation": "Inflation",
|
||||
"econ.debt": "Dette / PIB",
|
||||
"econ.deficit": "Déficit / PIB",
|
||||
"econ.rate": "Taux d'intérêt",
|
||||
"econ.gdpUnit": "Md {currency}",
|
||||
"cal.dayCount": "Jour {day} sur {total}",
|
||||
"cal.briefing": "Prochain dossier",
|
||||
"cal.review": "Revue budgétaire",
|
||||
"cal.inDays": "dans {n} jours",
|
||||
"cal.today": "aujourd'hui",
|
||||
"cal.tomorrow": "demain",
|
||||
"cal.pause": "Pause",
|
||||
"cal.speed": "Vitesse {n}×",
|
||||
"inbox.title": "Corbeille d'arrivée",
|
||||
"inbox.idle": "Rien n'attend votre signature. Le pays poursuit sa route.",
|
||||
"inbox.overTitle": "Fin de mandat",
|
||||
"inbox.overBody": "Votre mandat est terminé. (Le bilan final arrive dans une prochaine version.)",
|
||||
"news.edition": "Édition du {date}",
|
||||
"sector.education": "Éducation",
|
||||
"sector.health": "Santé",
|
||||
"sector.social": "Social, retraites",
|
||||
"sector.police": "Police",
|
||||
"sector.army": "Armée",
|
||||
"sector.justice": "Justice",
|
||||
"sector.industry": "Industrie, énergie",
|
||||
"sector.transport": "Transports publics",
|
||||
"sector.infrastructure": "Infrastructures",
|
||||
"sector.housing": "Logement",
|
||||
"sector.research": "Recherche",
|
||||
"sector.culture": "Culture, médias",
|
||||
"sector.foreignAffairs": "Diplomatie",
|
||||
"sector.environment": "Environnement",
|
||||
"sector.administration": "Administration",
|
||||
"tax.income": "Impôt revenu",
|
||||
"tax.corporate": "Impôt sociétés",
|
||||
"tax.vat": "TVA",
|
||||
"ledger.title": "Registre du Trésor",
|
||||
"ledger.spending": "Dépenses, % du PIB",
|
||||
"ledger.spendingHint": "Le repère sur chaque ligne marque le besoin du secteur.",
|
||||
"ledger.totalAlloc": "Alloué aux secteurs",
|
||||
"ledger.revenue": "Recettes : taux d'imposition",
|
||||
"ledger.revenueHint": "Le repère marque le taux où les recettes sont maximales. Au-delà, vous collectez moins.",
|
||||
"ledger.yield": "{n} % du PIB",
|
||||
"ledger.quality": "Qualité {now} → {target}",
|
||||
"ledger.summary": "Synthèse du Trésor",
|
||||
"ledger.sumRevenue": "Recettes",
|
||||
"ledger.sumSectors": "Dépenses sectorielles",
|
||||
"ledger.sumInterest": "Intérêts de la dette",
|
||||
"ledger.sumSpending": "Dépenses totales",
|
||||
"ledger.sumSurplus": "Excédent",
|
||||
"ledger.sumDeficit": "Déficit",
|
||||
"ledger.perYear": "{n} Md {currency} par an",
|
||||
"ledger.projection": "Quatre prochains trimestres, sans autre changement",
|
||||
"ledger.now": "Auj.",
|
||||
"ledger.months": "+{n} m",
|
||||
"ledger.reset": "Réinitialiser",
|
||||
"ledger.confirm": "Signer et classer",
|
||||
"ledger.error": "Le Trésor a rejeté ce budget.",
|
||||
"kind.memo": "Mémorandum",
|
||||
"kind.telegram": "Télégramme",
|
||||
"kind.petition": "Pétition",
|
||||
"kind.report": "Rapport",
|
||||
"kind.letter": "Lettre",
|
||||
"card.approved": "Approuvé",
|
||||
"card.rejected": "Rejeté",
|
||||
"card.keys": "Touches : Y approuver · N rejeter",
|
||||
"card.from": "De la part de",
|
||||
"profile.title": "Prestation de serment",
|
||||
"profile.lead": "Avant votre entrée en fonction, le service de presse a besoin d'une précision pour les registres officiels.",
|
||||
"profile.body": "Comment la presse doit-elle s'adresser à vous ?",
|
||||
"profile.he": "Il / Lui",
|
||||
"profile.she": "Elle / Elle",
|
||||
"profile.they": "Iel / Ielle",
|
||||
"profile.note": "Cela ne change que la manière dont vous serez nommé dans les livres d'histoire.",
|
||||
"alert.urgent": "Télégramme urgent",
|
||||
"alert.ack": "Compris",
|
||||
"ind.regime": "Emprise sur le pouvoir",
|
||||
"regime.0": "Solide",
|
||||
"regime.1": "Fragile",
|
||||
"regime.2": "Critique",
|
||||
"end.verdict": "Le verdict des historiens",
|
||||
"end.score": "Score {n} / 100",
|
||||
"end.grade": "Note",
|
||||
"end.served": "{days} jours au pouvoir",
|
||||
"end.decisions": "Les décisions qui ont marqué le mandat",
|
||||
"end.noDecisions": "Aucune décision n'a été signée.",
|
||||
"end.chose": "Votre choix : {label}",
|
||||
"end.again": "Rejouer",
|
||||
"end.averages": "moy.",
|
||||
"end.final": "fin",
|
||||
"cal.menu": "Menu",
|
||||
"menu.secret": "Secret défense",
|
||||
"menu.tagline": "Un pays. Cinq ans. Chaque décision porte votre signature.",
|
||||
"menu.paused": "Partie en pause",
|
||||
"menu.continue": "Continuer",
|
||||
"menu.resume": "Reprendre",
|
||||
"menu.new": "Nouvelle partie",
|
||||
"menu.load": "Charger une partie",
|
||||
"menu.save": "Sauvegarder",
|
||||
"menu.export": "Exporter",
|
||||
"menu.import": "Importer",
|
||||
"menu.settings": "Réglages",
|
||||
"menu.credits": "Crédits",
|
||||
"menu.quit": "Menu principal",
|
||||
"menu.back": "Retour",
|
||||
"menu.info": "{country} · jour {day}",
|
||||
"new.title": "Nouvelle partie",
|
||||
"new.name": "Nom du pays",
|
||||
"new.scenario": "Scénario",
|
||||
"new.pronouns": "Comment la presse doit-elle s'adresser à vous ?",
|
||||
"new.start": "Prendre ses fonctions",
|
||||
"load.title": "Charger une partie",
|
||||
"load.auto": "Sauvegarde auto",
|
||||
"load.slot": "Emplacement {n}",
|
||||
"load.empty": "Vide",
|
||||
"load.info": "{country} · jour {day}{date}",
|
||||
"load.finished": "terminée",
|
||||
"load.unreadable": "Sauvegarde illisible",
|
||||
"load.load": "Charger",
|
||||
"load.delete": "Supprimer",
|
||||
"load.sure": "Sûr ?",
|
||||
"load.replace": "Charger remplace la partie en cours.",
|
||||
"save.title": "Sauvegarder",
|
||||
"save.here": "Sauvegarder ici",
|
||||
"save.overwrite": "Écraser",
|
||||
"save.done": "Sauvegardé dans l'emplacement {n}.",
|
||||
"save.failed": "Le navigateur a refusé la sauvegarde. Utilisez Exporter pour garder votre partie.",
|
||||
"export.title": "Exporter",
|
||||
"export.hint": "Copiez ce texte ou téléchargez le fichier. Vous pourrez l'importer plus tard, sur n'importe quel appareil.",
|
||||
"export.copy": "Copier",
|
||||
"export.copied": "Copié.",
|
||||
"export.copyFailed": "Sélectionnez le texte et copiez-le à la main.",
|
||||
"export.download": "Télécharger le .sav",
|
||||
"import.title": "Importer",
|
||||
"import.hint": "Collez une chaîne de sauvegarde, ou choisissez un fichier .sav.",
|
||||
"import.file": "Choisir un fichier",
|
||||
"import.go": "Importer",
|
||||
"import.error.format": "Cela ne ressemble pas à une chaîne de sauvegarde.",
|
||||
"import.error.checksum": "La sauvegarde est endommagée ou incomplète (somme de contrôle invalide).",
|
||||
"import.error.tooNew": "Cette sauvegarde vient d'une version plus récente du jeu.",
|
||||
"import.error.version": "Cette sauvegarde vient d'une version qui ne peut pas être convertie.",
|
||||
"import.error.invalid": "Le contenu de cette sauvegarde n'est pas valide.",
|
||||
"import.error.scenario": "Cette sauvegarde utilise un scénario qui n'est pas installé.",
|
||||
"import.error.empty": "Rien à importer pour l'instant.",
|
||||
"settings.title": "Réglages",
|
||||
"settings.language": "Langue",
|
||||
"settings.slow": "Temps ralenti",
|
||||
"settings.slowHint": "Un jour en jeu dure 60 secondes au lieu de 10.",
|
||||
"settings.storage": "Ce navigateur ne conserve pas les sauvegardes entre deux visites (fenêtre privée ou stockage bloqué). Utilisez Exporter pour garder votre partie.",
|
||||
"credits.title": "Crédits",
|
||||
"credits.fiction": "La Valdorie et tous ses habitants sont fictifs.",
|
||||
"credits.tech": "Réalisé avec Go et du JavaScript pur, sans bibliothèque tierce.",
|
||||
"credits.fonts": "Polices : Special Elite (licence Apache 2.0), IM Fell English et Courier Prime (licence SIL Open Font). Les fichiers de licence sont fournis avec le jeu."
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// UI strings live in en.json / fr.json. Content fields (cards, newspaper) are
|
||||
// { en, fr } objects read through tx().
|
||||
|
||||
const dict = {};
|
||||
let lang = "en";
|
||||
|
||||
export async function initI18n(initial = "en") {
|
||||
for (const l of ["en", "fr"]) {
|
||||
dict[l] = await (await fetch(`js/i18n/${l}.json`)).json();
|
||||
}
|
||||
setLang(initial);
|
||||
}
|
||||
|
||||
export function setLang(l) {
|
||||
lang = dict[l] ? l : "en";
|
||||
document.documentElement.lang = lang;
|
||||
}
|
||||
|
||||
export const getLang = () => lang;
|
||||
|
||||
export function t(key, vars) {
|
||||
let s = dict[lang]?.[key] ?? dict.en?.[key] ?? key;
|
||||
if (vars) for (const [k, v] of Object.entries(vars)) s = s.replaceAll(`{${k}}`, v);
|
||||
return s;
|
||||
}
|
||||
|
||||
// Pick the current language from a { en, fr } content field.
|
||||
export const tx = (obj) => obj?.[lang] ?? obj?.en ?? "";
|
||||
|
||||
// Pronoun tokens for text that refers to the player: {sub} {Sub} {obj} {pos} {is} {has} {was}
|
||||
// in English, {sub} {Sub} {tonic} {e} (agreement ending) in French. Text that needs none of
|
||||
// them should avoid pronouns altogether.
|
||||
const PRONOUN_FORMS = {
|
||||
en: {
|
||||
he: { sub: "he", Sub: "He", obj: "him", pos: "his", is: "is", has: "has", was: "was" },
|
||||
she: { sub: "she", Sub: "She", obj: "her", pos: "her", is: "is", has: "has", was: "was" },
|
||||
they: { sub: "they", Sub: "They", obj: "them", pos: "their", is: "are", has: "have", was: "were" },
|
||||
},
|
||||
fr: {
|
||||
he: { sub: "il", Sub: "Il", tonic: "lui", e: "" },
|
||||
she: { sub: "elle", Sub: "Elle", tonic: "elle", e: "e" },
|
||||
they: { sub: "iel", Sub: "Iel", tonic: "ielle", e: "·e" },
|
||||
},
|
||||
};
|
||||
|
||||
export function tp(text, pronouns = "they") {
|
||||
const forms = PRONOUN_FORMS[lang]?.[pronouns] ?? PRONOUN_FORMS[lang]?.they ?? {};
|
||||
return text.replace(/\{(\w+)\}/g, (match, key) => forms[key] ?? match);
|
||||
}
|
||||
|
||||
// A historian's title: English is one string, French has a form per pronoun set.
|
||||
export function tt(title, pronouns = "they") {
|
||||
const v = title?.[lang] ?? title?.en ?? "";
|
||||
return typeof v === "string" ? v : (v[pronouns] ?? v.they);
|
||||
}
|
||||
|
||||
export function fmt(n, digits = 1) {
|
||||
return new Intl.NumberFormat(lang, { minimumFractionDigits: digits, maximumFractionDigits: digits }).format(n);
|
||||
}
|
||||
|
||||
// ISO date (YYYY-MM-DD, treated as UTC) → long localized parts.
|
||||
export function dateParts(iso) {
|
||||
const d = new Date(`${iso}T00:00:00Z`);
|
||||
const f = (opts) => new Intl.DateTimeFormat(lang, { timeZone: "UTC", ...opts }).format(d);
|
||||
return {
|
||||
weekday: f({ weekday: "long" }),
|
||||
day: f({ day: "numeric" }),
|
||||
month: f({ month: "long" }),
|
||||
year: f({ year: "numeric" }),
|
||||
full: f({ dateStyle: "full" }),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user