feat: add more localisation support

This commit is contained in:
Anna 2021-10-10 13:29:55 -04:00
parent 146abcfc9a
commit 1d67debfd0
13 changed files with 2908 additions and 2734 deletions

View File

@ -2,6 +2,6 @@ author: ascclemens
name: Remote Party Finder Uploader
description: |-
Uploads the PF listings you retrieve to the crowdsourced Remote
Party Finder website (https://rpf.annaclemens.io/).
punchline: Crowdsourced PF website
Party Finder website (https://xivpf.com/).
punchline: Crowdsourced PF website (upload PF listings to xivpf.com)
repo_url: https://git.sr.ht/~jkcclemens/remote-party-finder

View File

@ -11,7 +11,13 @@ using Lumina.Text;
namespace SourceGenerator {
internal class Program {
private static void Main(string[] args) {
var data = new GameData(args[0]);
var data = new Dictionary<Language, GameData>(4);
foreach (var lang in Languages.Keys) {
data[lang] = new GameData(args[0], new LuminaOptions {
DefaultExcelLanguage = lang,
});
}
var prog = new Program(data);
File.WriteAllText(Path.Join(args[1], "duties.rs"), prog.GenerateDuties());
@ -23,9 +29,9 @@ namespace SourceGenerator {
File.WriteAllText(Path.Join(args[1], "treasure_maps.rs"), prog.GenerateTreasureMaps());
}
private GameData Data { get; }
private Dictionary<Language, GameData> Data { get; }
private Program(GameData data) {
private Program(Dictionary<Language, GameData> data) {
this.Data = data;
}
@ -46,8 +52,8 @@ namespace SourceGenerator {
[Language.French] = "fr",
};
private string? GetLocalisedStruct<T>(uint rowId, Func<T, SeString?> nameFunc, uint indent = 0) where T : ExcelRow {
var def = this.Data.GetExcelSheet<T>()!.GetRow(rowId)!;
private string? GetLocalisedStruct<T>(uint rowId, Func<T, SeString?> nameFunc, uint indent = 0, bool capitalise = false) where T : ExcelRow {
var def = this.Data[Language.English].GetExcelSheet<T>()!.GetRow(rowId)!;
var defName = nameFunc(def)?.TextValue();
if (string.IsNullOrEmpty(defName)) {
return null;
@ -58,11 +64,14 @@ namespace SourceGenerator {
sb.Append("LocalisedText {\n");
foreach (var (language, key) in Languages) {
var row = this.Data.GetExcelSheet<T>(language)?.GetRow(rowId);
var row = this.Data[language].GetExcelSheet<T>(language)?.GetRow(rowId);
var name = row == null
? defName
: nameFunc(row)?.TextValue().Replace("\"", "\\\"");
name ??= defName;
if (capitalise) {
name = name[..1].ToUpperInvariant() + name[1..];
}
for (var i = 0; i < indent + 4; i++) {
sb.Append(' ');
@ -74,7 +83,7 @@ namespace SourceGenerator {
for (var i = 0; i < indent; i++) {
sb.Append(' ');
}
sb.Append('}');
return sb.ToString();
@ -95,7 +104,7 @@ namespace SourceGenerator {
sb.Append("#[allow(unused)]\n");
sb.Append("#[repr(u32)]\n");
sb.Append("pub enum ContentKind {\n");
foreach (var kind in this.Data.GetExcelSheet<ContentType>()!) {
foreach (var kind in this.Data[Language.English].GetExcelSheet<ContentType>()!) {
var name = kind.Name.TextValue().Replace(" ", "");
if (name.Length > 0) {
sb.Append($" {name} = {kind.RowId},\n");
@ -109,7 +118,7 @@ namespace SourceGenerator {
sb.Append(" fn from_u32(kind: u32) -> Self {\n");
sb.Append(" match kind {\n");
foreach (var kind in this.Data.GetExcelSheet<ContentType>()!) {
foreach (var kind in this.Data[Language.English].GetExcelSheet<ContentType>()!) {
var name = kind.Name.TextValue().Replace(" ", "");
if (name.Length > 0) {
sb.Append($" {kind.RowId} => Self::{name},\n");
@ -122,7 +131,7 @@ namespace SourceGenerator {
sb.Append(" pub fn as_u32(self) -> u32 {\n");
sb.Append(" match self {\n");
foreach (var kind in this.Data.GetExcelSheet<ContentType>()!) {
foreach (var kind in this.Data[Language.English].GetExcelSheet<ContentType>()!) {
var name = kind.Name.TextValue().Replace(" ", "");
if (name.Length > 0) {
sb.Append($" Self::{name} => {kind.RowId},\n");
@ -138,20 +147,19 @@ namespace SourceGenerator {
sb.Append("lazy_static::lazy_static! {\n");
sb.Append(" pub static ref DUTIES: HashMap<u32, DutyInfo> = maplit::hashmap! {\n");
foreach (var cfc in this.Data.GetExcelSheet<ContentFinderCondition>()!) {
foreach (var cfc in this.Data[Language.English].GetExcelSheet<ContentFinderCondition>()!) {
if (cfc.RowId == 0) {
continue;
}
var name = this.GetLocalisedStruct<ContentFinderCondition>(cfc.RowId, row => row.Name, 12);
var name = this.GetLocalisedStruct<ContentFinderCondition>(cfc.RowId, row => row.Name, 12, true);
if (name == null) {
continue;
}
name = name[..1].ToUpperInvariant() + name[1..];
var highEnd = cfc.HighEndDuty ? "true" : "false";
var contentType = cfc.ContentType.Value;
var contentKind = contentType?.Name?.TextValue()?.Replace(" ", "");
var contentKind = contentType?.Name?.TextValue().Replace(" ", "");
if (string.IsNullOrEmpty(contentKind)) {
contentKind = $"Other({contentType?.RowId ?? 0})";
}
@ -175,7 +183,7 @@ namespace SourceGenerator {
sb.Append("lazy_static::lazy_static! {\n");
sb.Append(" pub static ref JOBS: HashMap<u32, ClassJob> = maplit::hashmap! {\n");
foreach (var cj in this.Data.GetExcelSheet<ClassJob>()!) {
foreach (var cj in this.Data[Language.English].GetExcelSheet<ClassJob>()!) {
if (cj.RowId == 0) {
continue;
}
@ -218,7 +226,7 @@ namespace SourceGenerator {
sb.Append("lazy_static::lazy_static! {\n");
sb.Append(" pub static ref ROULETTES: HashMap<u32, RouletteInfo> = maplit::hashmap! {\n");
foreach (var cr in this.Data.GetExcelSheet<ContentRoulette>()!) {
foreach (var cr in this.Data[Language.English].GetExcelSheet<ContentRoulette>()!) {
if (cr.RowId == 0) {
continue;
}
@ -250,7 +258,7 @@ namespace SourceGenerator {
sb.Append("lazy_static::lazy_static! {\n");
sb.Append(" pub static ref WORLDS: HashMap<u32, World> = maplit::hashmap! {\n");
foreach (var world in this.Data.GetExcelSheet<World>()!) {
foreach (var world in this.Data[Language.English].GetExcelSheet<World>()!) {
if (world.RowId == 0 || !world.IsPublic || world.DataCenter.Row == 0) {
continue;
}
@ -274,7 +282,7 @@ namespace SourceGenerator {
sb.Append("\nlazy_static::lazy_static! {\n");
sb.Append(" pub static ref TERRITORY_NAMES: HashMap<u32, LocalisedText> = maplit::hashmap! {\n");
foreach (var tt in this.Data.GetExcelSheet<TerritoryType>()!) {
foreach (var tt in this.Data[Language.English].GetExcelSheet<TerritoryType>()!) {
if (tt.RowId == 0 || tt.PlaceName.Row == 0) {
continue;
}
@ -302,7 +310,7 @@ namespace SourceGenerator {
sb.Append("\nlazy_static::lazy_static! {\n");
sb.Append(" pub static ref AUTO_TRANSLATE: HashMap<(u32, u32), LocalisedText> = maplit::hashmap! {\n");
foreach (var row in this.Data.GetExcelSheet<Completion>()!) {
foreach (var row in this.Data[Language.English].GetExcelSheet<Completion>()!) {
var lookup = row.LookupTable.TextValue();
if (lookup is not ("" or "@")) {
// TODO: do lookup
@ -326,19 +334,26 @@ namespace SourceGenerator {
sb.Append(" pub static ref TREASURE_MAPS: HashMap<u32, LocalisedText> = maplit::hashmap! {\n");
sb.Append(" 0 => LocalisedText {\n");
sb.Append(" en: \"All Levels\",\n");
sb.Append(" ja: \"All Levels\",\n");
sb.Append(" de: \"All Levels\",\n");
sb.Append(" fr: \"All Levels\",\n");
sb.Append(" ja: \"レベルを指定しない\",\n");
sb.Append(" de: \"Jede Stufe\",\n");
sb.Append(" fr: \"Tous niveaux\",\n");
sb.Append(" },\n");
var i = 1;
foreach (var row in this.Data.GetExcelSheet<TreasureHuntRank>()!) {
foreach (var row in this.Data[Language.English].GetExcelSheet<TreasureHuntRank>()!) {
// IS THIS RIGHT?
if (row.TreasureHuntTexture != 0) {
continue;
}
var name = this.GetLocalisedStruct<TreasureHuntRank>(row.RowId, row => row.KeyItemName.Value?.Name, 8);
SeString? GetMapName(TreasureHuntRank thr) {
var name = thr.KeyItemName.Value?.Name;
return string.IsNullOrEmpty(name?.TextValue())
? thr.ItemName.Value?.Name
: name;
}
var name = this.GetLocalisedStruct<TreasureHuntRank>(row.RowId, GetMapName, 8);
if (!string.IsNullOrEmpty(name)) {
sb.Append($" {i++} => {name},\n");
}

View File

@ -84,6 +84,11 @@ details > summary {
margin-top: 1em;
}
#container > .settings {
display: flex;
justify-content: space-between;
}
#listings > .no-listings {
margin-top: 1em;
}

View File

@ -3,8 +3,9 @@
const state = {
allowed: [],
centre: "All",
centre: 'All',
list: null,
lang: 'en',
};
function addJsClass() {
@ -54,12 +55,14 @@
state.allowed.push(option.value);
}
console.log(`allowed includes ${option.value} = ${state.allowed.includes(option.value)}`)
option.selected = state.allowed.includes(option.value);
}
let dataCentre = document.getElementById('data-centre-filter');
dataCentre.value = state.centre;
let language = document.getElementById('language');
language.value = state.lang;
}
function setUpList() {
@ -143,9 +146,19 @@
});
}
function setUpLanguage() {
let language = document.getElementById('language');
language.addEventListener('change', () => {
state.lang = language.value;
document.cookie = `lang=${encodeURIComponent(language.value)};path=/;max-age=31536000;samesite=lax`;
window.location.reload();
});
}
addJsClass();
saveLoadState();
reflectState();
setUpLanguage();
state.list = setUpList();
setUpDataCentreFilter();
setUpCategoryFilter();

View File

@ -21,16 +21,21 @@ use std::{
str::FromStr,
};
#[derive(Debug)]
pub struct LocalisedText {
pub en: &'static str,
pub ja: &'static str,
pub de: &'static str,
pub fr: &'static str,
#[derive(Debug, Copy, Clone)]
pub enum Language {
English,
Japanese,
German,
French,
}
impl LocalisedText {
pub fn from_codes(&self, val: &str) -> &'static str {
impl Language {
pub fn from_codes(val: Option<&str>) -> Self {
let val = match val {
Some(v) => v,
None => return Self::English,
};
let mut parts: Vec<(&str, f32)> = val.split(',')
.map(|part| {
let sub_parts: Vec<&str> = part.split(';').collect();
@ -45,20 +50,39 @@ impl LocalisedText {
.collect();
parts.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(Ordering::Less));
if parts.len() == 0 {
return self.en;
return Self::English;
}
for (lang, _) in parts {
let first = lang.split('-').next().unwrap();
match first {
"en" => return self.en,
"ja" => return self.ja,
"de" => return self.de,
"fr" => return self.fr,
"en" => return Self::English,
"ja" => return Self::Japanese,
"de" => return Self::German,
"fr" => return Self::French,
_ => {},
}
}
self.en
Self::English
}
}
#[derive(Debug)]
pub struct LocalisedText {
pub en: &'static str,
pub ja: &'static str,
pub de: &'static str,
pub fr: &'static str,
}
impl LocalisedText {
pub fn text(&self, lang: &Language) -> &'static str {
match lang {
Language::English => self.en,
Language::Japanese => self.ja,
Language::German => self.de,
Language::French => self.fr,
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -5,105 +5,105 @@ lazy_static::lazy_static! {
pub static ref TREASURE_MAPS: HashMap<u32, LocalisedText> = maplit::hashmap! {
0 => LocalisedText {
en: "All Levels",
ja: "All Levels",
de: "All Levels",
fr: "All Levels",
ja: "レベルを指定しない",
de: "Jede Stufe",
fr: "Tous niveaux",
},
1 => LocalisedText {
en: "Leather Treasure Map",
ja: "Leather Treasure Map",
de: "Leather Treasure Map",
fr: "Leather Treasure Map",
ja: "古ぼけた地図G1",
de: "Leder-Schatzkarte",
fr: "Carte au trésor en cuir",
},
2 => LocalisedText {
en: "Leather Treasure Map",
ja: "Leather Treasure Map",
de: "Leather Treasure Map",
fr: "Leather Treasure Map",
ja: "古ぼけた地図G1",
de: "Leder-Schatzkarte",
fr: "Carte au trésor en cuir",
},
3 => LocalisedText {
en: "Goatskin Treasure Map",
ja: "Goatskin Treasure Map",
de: "Goatskin Treasure Map",
fr: "Goatskin Treasure Map",
ja: "古ぼけた地図G2",
de: "Steinbockleder-Schatzkarte",
fr: "Carte au trésor en peau de bouquetin",
},
4 => LocalisedText {
en: "Toadskin Treasure Map",
ja: "Toadskin Treasure Map",
de: "Toadskin Treasure Map",
fr: "Toadskin Treasure Map",
ja: "古ぼけた地図G3",
de: "Krötenleder-Schatzkarte",
fr: "Carte au trésor en peau de crapaud",
},
5 => LocalisedText {
en: "Boarskin Treasure Map",
ja: "Boarskin Treasure Map",
de: "Boarskin Treasure Map",
fr: "Boarskin Treasure Map",
ja: "古ぼけた地図G4",
de: "Keilerleder-Schatzkarte",
fr: "Carte au trésor en peau de sanglier",
},
6 => LocalisedText {
en: "Peisteskin Treasure Map",
ja: "Peisteskin Treasure Map",
de: "Peisteskin Treasure Map",
fr: "Peisteskin Treasure Map",
ja: "古ぼけた地図G5",
de: "Basiliskenleder-Schatzkarte",
fr: "Carte au trésor en peau de peiste",
},
7 => LocalisedText {
en: "Leather Buried Treasure Map",
ja: "Leather Buried Treasure Map",
de: "Leather Buried Treasure Map",
fr: "Leather Buried Treasure Map",
ja: "隠された地図G1",
de: "Kryptische Karte",
fr: "Carte au trésor secrète en cuir",
},
8 => LocalisedText {
en: "Archaeoskin Treasure Map",
ja: "Archaeoskin Treasure Map",
de: "Archaeoskin Treasure Map",
fr: "Archaeoskin Treasure Map",
ja: "古ぼけた地図G6",
de: "Archaeoleder-Schatzkarte",
fr: "Carte au trésor en peau d'archéornis",
},
9 => LocalisedText {
en: "Wyvernskin Treasure Map",
ja: "Wyvernskin Treasure Map",
de: "Wyvernskin Treasure Map",
fr: "Wyvernskin Treasure Map",
ja: "古ぼけた地図G7",
de: "Wyvernleder-Schatzkarte",
fr: "Carte au trésor en peau de wyverne",
},
10 => LocalisedText {
en: "Dragonskin Treasure Map",
ja: "Dragonskin Treasure Map",
de: "Dragonskin Treasure Map",
fr: "Dragonskin Treasure Map",
ja: "古ぼけた地図G8",
de: "Drachenleder-Schatzkarte",
fr: "Carte au trésor en peau de dragon",
},
11 => LocalisedText {
en: "Gaganaskin Treasure Map",
ja: "Gaganaskin Treasure Map",
de: "Gaganaskin Treasure Map",
fr: "Gaganaskin Treasure Map",
ja: "古ぼけた地図G9",
de: "Gaganaleder-Schatzkarte",
fr: "Carte au trésor en peau de gagana",
},
12 => LocalisedText {
en: "Gazelleskin Treasure Map",
ja: "Gazelleskin Treasure Map",
de: "Gazelleskin Treasure Map",
fr: "Gazelleskin Treasure Map",
ja: "古ぼけた地図G10",
de: "Gazellenleder-Schatzkarte",
fr: "Carte au trésor en peau de gazelle",
},
13 => LocalisedText {
en: "Seemingly Special Treasure Map",
ja: "Seemingly Special Treasure Map",
de: "Seemingly Special Treasure Map",
fr: "Seemingly Special Treasure Map",
ja: "古ぼけた地図S1",
de: "Exotenleder-Schatzkarte",
fr: "Carte au trésor inhabituelle I",
},
14 => LocalisedText {
en: "Gliderskin Treasure Map",
ja: "Gliderskin Treasure Map",
de: "Gliderskin Treasure Map",
fr: "Gliderskin Treasure Map",
ja: "古ぼけた地図G11",
de: "Smilodonleder-Schatzkarte",
fr: "Carte au trésor en peau de smilodon",
},
15 => LocalisedText {
en: "Zonureskin Treasure Map",
ja: "Zonureskin Treasure Map",
de: "Zonureskin Treasure Map",
fr: "Zonureskin Treasure Map",
ja: "古ぼけた地図G12",
de: "Glaucusleder-Schatzkarte",
fr: "Carte au trésor en peau de glaucus",
},
16 => LocalisedText {
en: "Presumably Special Treasure Map",
ja: "Presumably Special Treasure Map",
de: "Presumably Special Treasure Map",
fr: "Presumably Special Treasure Map",
ja: "古ぼけた地図S2",
de: "Mythenleder-Schatzkarte",
fr: "Carte au trésor inhabituelle II",
},
};
}

View File

@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use sestring::SeString;
use crate::ffxiv::duties::{ContentKind, DutyInfo};
use crate::ffxiv::{Language, LocalisedText};
#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct PartyFinderListing {
@ -45,32 +46,57 @@ impl PartyFinderListing {
self.search_area.contains(SearchAreaFlags::DATA_CENTRE)
}
pub fn duty_name(&self, codes: &str) -> Cow<str> {
pub fn duty_name(&self, lang: &Language) -> Cow<str> {
match (&self.duty_type, &self.category) {
(DutyType::Other, DutyCategory::Fates) => {
if let Some(name) = crate::ffxiv::TERRITORY_NAMES.get(&u32::from(self.duty)) {
return Cow::from(name.from_codes(codes));
return Cow::from(name.text(lang));
}
return Cow::from("Fates");
return Cow::from("FATEs");
}
(DutyType::Other, DutyCategory::TheHunt) => return Cow::from("The Hunt"),
(DutyType::Other, DutyCategory::Duty) if self.duty == 0 => return Cow::from("None"),
(DutyType::Other, DutyCategory::DeepDungeons) if self.duty == 1 => return Cow::from("The Palace of the Dead"),
(DutyType::Other, DutyCategory::DeepDungeons) if self.duty == 2 => return Cow::from("Heaven-on-High"),
(DutyType::Other, DutyCategory::TheHunt) => return Cow::from(match lang {
Language::English => "The Hunt",
Language::Japanese => "モブハント",
Language::German => "Hohe Jagd",
Language::French => "Contrats de chasse",
}),
(DutyType::Other, DutyCategory::Duty) if self.duty == 0 => return Cow::from(match lang {
Language::English => "None",
Language::Japanese => "設定なし",
Language::German => "Nicht festgelegt",
Language::French => "Non spécifiée",
}),
(DutyType::Other, DutyCategory::DeepDungeons) if self.duty == 1 => return Cow::from(match lang {
Language::English => "The Palace of the Dead",
Language::Japanese => "死者の宮殿",
Language::German => "Palast der Toten",
Language::French => "Palais des morts",
}),
(DutyType::Other, DutyCategory::DeepDungeons) if self.duty == 2 => return Cow::from(match lang {
Language::English => "Heaven-on-High",
Language::Japanese => "アメノミハシラ",
Language::German => "Himmelssäule",
Language::French => "Pilier des Cieux",
}),
(DutyType::Normal, _) => {
if let Some(info) = crate::ffxiv::DUTIES.get(&u32::from(self.duty)) {
return Cow::from(info.name.from_codes(codes));
return Cow::from(info.name.text(lang));
}
}
(DutyType::Roulette, _) => {
if let Some(info) = crate::ffxiv::ROULETTES.get(&u32::from(self.duty)) {
return Cow::from(info.name.from_codes(codes));
return Cow::from(info.name.text(lang));
}
}
(_, DutyCategory::QuestBattles) => return Cow::from("Quest Battles"),
(_, DutyCategory::QuestBattles) => return Cow::from(match lang {
Language::English => "Quest Battles",
Language::Japanese => "クエストバトル",
Language::German => "Auftragskampf",
Language::French => "Batailles de quête",
}),
(_, DutyCategory::TreasureHunt) => if let Some(name) = crate::ffxiv::TREASURE_MAPS.get(&u32::from(self.duty)) {
return Cow::from(name.from_codes(codes));
return Cow::from(name.text(lang));
}
_ => {}
}
@ -547,23 +573,98 @@ impl PartyFinderCategory {
}
}
pub fn name(self) -> &'static str {
pub fn name(self) -> LocalisedText {
match self {
Self::DutyRoulette => "Duty Roulette",
Self::Dungeons => "Dungeons",
Self::Guildhests => "Guildhests",
Self::Trials => "Trials",
Self::Raids => "Raids",
Self::HighEndDuty => "High-end Duty",
Self::Pvp => "PvP",
Self::QuestBattles => "Quest Battles",
Self::Fates => "FATEs",
Self::TreasureHunt => "Treasure Hunt",
Self::TheHunt => "The Hunt",
Self::GatheringForays => "Gathering Forays",
Self::DeepDungeons => "Deep Dungeons",
Self::AdventuringForays => "Adventuring Forays",
Self::None => "None",
Self::DutyRoulette => LocalisedText {
en: "Duty Roulette",
ja: "コンテンツルーレット",
de: "Zufallsinhalte",
fr: "Missions aléatoires",
},
Self::Dungeons => LocalisedText {
en: "Dungeons",
ja: "ダンジョン",
de: "Dungeons",
fr: "Donjons",
},
Self::Guildhests => LocalisedText {
en: "Guildhests",
ja: "ギルドオーダー",
de: "Gildengeheiße",
fr: "Opérations de guilde",
},
Self::Trials => LocalisedText {
en: "Trials",
ja: "討伐・討滅戦",
de: "Prüfungen",
fr: "Défis",
},
Self::Raids => LocalisedText {
en: "Raids",
ja: "レイド",
de: "Raids",
fr: "Raids",
},
Self::HighEndDuty => LocalisedText {
en: "High-end Duty",
ja: "高難易度コンテンツ",
de: "Schwierige Inhalte",
fr: "Missions à difficulté élevée",
},
Self::Pvp => LocalisedText {
en: "PvP",
ja: "PvP",
de: "PvP",
fr: "JcJ",
},
Self::QuestBattles => LocalisedText {
en: "Quest Battles",
ja: "クエストバトル",
de: "Auftragskampf",
fr: "Batailles de quête",
},
Self::Fates => LocalisedText {
en: "FATEs",
ja: "F.A.T.E.",
de: "FATEs",
fr: "ALÉA",
},
Self::TreasureHunt => LocalisedText {
en: "Treasure Hunt",
ja: "トレジャーハント",
de: "Schatzsuche",
fr: "Chasse aux trésors",
},
Self::TheHunt => LocalisedText {
en: "The Hunt",
ja: "モブハント ",
de: "Hohe Jagd",
fr: "Contrats de chasse",
},
Self::GatheringForays => LocalisedText {
en: "Gathering Forays",
ja: "採集活動",
de: "Sammeln",
fr: "Récolte",
},
Self::DeepDungeons => LocalisedText {
en: "Deep Dungeons",
ja: "ディープダンジョン",
de: "Tiefe Gewölbe",
fr: "Donjons sans fond",
},
Self::AdventuringForays => LocalisedText {
en: "Adventuring Forays",
ja: "特殊フィールド探索",
de: "Feldexkursion",
fr: "Missions d'exploration",
},
Self::None => LocalisedText {
en: "None",
ja: "設定なし",
de: "Nicht festgelegt",
fr: "Non spécifiée",
},
}
}
}

View File

@ -1,18 +1,19 @@
use sestring::{Payload, SeString};
use crate::ffxiv::Language;
pub trait SeStringExt {
fn full_text(&self, codes: &str) -> String;
fn full_text(&self, lang: &Language) -> String;
}
impl SeStringExt for SeString {
fn full_text(&self, codes: &str) -> String {
fn full_text(&self, lang: &Language) -> String {
self.0.iter()
.flat_map(|payload| {
match payload {
Payload::Text(t) => Some(&*t.0),
Payload::AutoTranslate(at) => crate::ffxiv::AUTO_TRANSLATE
.get(&(u32::from(at.group), at.key))
.map(|text| text.from_codes(codes)),
.map(|text| text.text(lang)),
_ => None,
}
})

View File

@ -1,6 +1,7 @@
use askama::Template;
use crate::listing_container::QueriedListing;
use std::borrow::Borrow;
use crate::ffxiv::Language;
use crate::sestring_ext::SeStringExt;
use crate::listing::PartyFinderCategory;
@ -8,5 +9,5 @@ use crate::listing::PartyFinderCategory;
#[template(path = "listings.html")]
pub struct ListingsTemplate {
pub containers: Vec<QueriedListing>,
pub codes: String,
pub lang: Language,
}

View File

@ -11,6 +11,7 @@ use warp::{Filter, Reply};
use warp::filters::BoxedFilter;
use warp::http::Uri;
use crate::config::Config;
use crate::ffxiv::Language;
use crate::listing::PartyFinderListing;
use crate::listing_container::{ListingContainer, QueriedListing};
use crate::template::listings::ListingsTemplate;
@ -124,7 +125,7 @@ fn index() -> BoxedFilter<(impl Reply, )> {
fn listings(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
async fn logic(state: Arc<State>, codes: Option<String>) -> std::result::Result<impl Reply, Infallible> {
use mongodb::bson::doc;
let codes = codes.unwrap_or_else(|| String::from("en"));
let lang = Language::from_codes(codes.as_deref());
let res = state
.collection()
@ -186,14 +187,14 @@ fn listings(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
Ok(ListingsTemplate {
containers,
codes,
lang,
})
}
Err(e) => {
eprintln!("{:#?}", e);
Ok(ListingsTemplate {
containers: Default::default(),
codes,
lang,
})
}
})
@ -202,10 +203,13 @@ fn listings(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
let route = warp::path("listings")
.and(warp::path::end())
.and(
warp::filters::header::optional::<String>("accept-language")
.or(warp::filters::cookie::optional::<String>("lang"))
warp::cookie::<String>("lang")
.or(warp::header::<String>("accept-language"))
.unify()
.map(Some)
.or(warp::any().map(|| None))
.unify()
)
.unify()
.and_then(move |codes: Option<String>| logic(Arc::clone(&state), codes));
warp::get().and(route).boxed()

View File

@ -12,43 +12,53 @@ Remote Party Finder
{% block body %}
<div id="container">
<div class="requires-js">
<input type="search" class="search" placeholder="Search"/>
<select id="data-centre-filter">
<option value="All">All</option>
<optgroup label="North America">
<option value="Aether">Aether</option>
<option value="Crystal">Crystal</option>
<option value="Primal">Primal</option>
</optgroup>
<optgroup label="Europe">
<option value="Chaos">Chaos</option>
<option value="Light">Light</option>
</optgroup>
<optgroup label="Japan">
<option value="Elemental">Elemental</option>
<option value="Gaia">Gaia</option>
<option value="Mana">Mana</option>
</optgroup>
<optgroup label="Oceania">
<option disabled value="">Not yet lmao</option>
</optgroup>
</select>
<details class="filter-controls">
<summary>Advanced</summary>
<div>
<div class="control">
<label>
Categories
<select multiple id="category-filter">
{%- for category in PartyFinderCategory::ALL %}
<option value="{{ category.as_str() }}">{{ category.name() }}</option>
{%- endfor %}
</select>
</label>
<div class="requires-js settings">
<div class="left">
<input type="search" class="search" placeholder="Search"/>
<select id="data-centre-filter">
<option value="All">All</option>
<optgroup label="North America">
<option value="Aether">Aether</option>
<option value="Crystal">Crystal</option>
<option value="Primal">Primal</option>
</optgroup>
<optgroup label="Europe">
<option value="Chaos">Chaos</option>
<option value="Light">Light</option>
</optgroup>
<optgroup label="Japan">
<option value="Elemental">Elemental</option>
<option value="Gaia">Gaia</option>
<option value="Mana">Mana</option>
</optgroup>
<optgroup label="Oceania">
<option disabled value="">Not yet lmao</option>
</optgroup>
</select>
<details class="filter-controls">
<summary>Advanced</summary>
<div>
<div class="control">
<label>
Categories
<select multiple id="category-filter">
{%- for category in PartyFinderCategory::ALL %}
<option value="{{ category.as_str() }}">{{ category.name().text(lang) }}</option>
{%- endfor %}
</select>
</label>
</div>
</div>
</div>
</details>
</details>
</div>
<div class="right">
<select id="language">
<option value="en">English</option>
<option value="ja">日本語</option>
<option value="de">Deutsch</option>
<option value="fr">Français</option>
</select>
</div>
</div>
<div id="listings" class="list">
{%- if containers.is_empty() %}
@ -68,9 +78,9 @@ Remote Party Finder
{%- else %}
{%- let duty_class = " local" %}
{%- endif %}
<div class="duty{{ duty_class }}">{{ listing.duty_name(codes.as_str()) }}</div>
<div class="duty{{ duty_class }}">{{ listing.duty_name(lang) }}</div>
<div class="description">
{%- let desc = listing.description.full_text(codes.as_str()) %}
{%- let desc = listing.description.full_text(lang) %}
{%- if desc.trim().is_empty() -%}
<em>None</em>
{%- else -%}
@ -120,7 +130,7 @@ Remote Party Finder
</div>
<div class="right meta">
<div class="item creator">
<span class="text">{{ listing.name.full_text(codes.as_str()) }} @ {{ listing.home_world_string() }}</span>
<span class="text">{{ listing.name.full_text(lang) }} @ {{ listing.home_world_string() }}</span>
<span title="Creator">
<svg class="icon" viewBox="0 0 32 32">
<use href="/assets/icons.svg#user"></use>