Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Translation support rework #289

Merged
merged 7 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions gui-src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ <h2 data-localize="select_world">Select World</h2>

<!-- Updated Tooltip Structure -->
<div class="tooltip" style="width: 100%;">
<button type="button" onclick="openWorldPicker()" style="padding: 10px; line-height: 1.2; width: 100%;" data-localize="choose_world">
Choose World
<button type="button" onclick="openWorldPicker()" style="padding: 10px; line-height: 1.2; width: 100%;">
<span id="choose_world">Choose World</span>
<br>
<span id="selected-world" style="font-size: 0.8em; color: #fecc44; display: block; margin-top: 4px;" data-localize="no_world_selected">
No world selected
Expand Down Expand Up @@ -108,7 +108,7 @@ <h2 data-localize="customization_settings">Customization Settings</h2>
<!-- Bounding Box Input -->
<div class="bbox-input-container">
<label for="bbox-coords" data-localize="custom_bounding_box">Custom Bounding Box:</label>
<input type="text" id="bbox-coords" name="bbox-coords" maxlength="55" style="width: 280px;" autocomplete="one-time-code" placeholder="Format: lat,lng,lat,lng">
<input type="text" id="bbox-coords" name="bbox-coords" maxlength="55" style="width: 280px;" placeholder="Format: lat,lng,lat,lng">
</div>

<!-- Floodfill Timeout Input -->
Expand Down Expand Up @@ -142,10 +142,10 @@ <h2 data-localize="license_and_credits">License and Credits</h2>
<!-- Footer -->
<footer class="footer">
<a href="https://github.com/louis-e/arnis" target="_blank" class="footer-link" data-localize="footer_text">
© <span id="current-year"></span> Arnis v<span id="version-placeholder">x.x.x</span> by louis-e
© {year} Arnis v{version} by louis-e
</a>
</footer>
</main>
</body>

</html>
</html>
231 changes: 96 additions & 135 deletions gui-src/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ if (window.__TAURI__) {
invoke = dummyFunc;
}

const DEFAULT_LOCALE_PATH = `./locales/en.json`;

// Initialize elements and start the demo progress
window.addEventListener("DOMContentLoaded", async () => {
registerMessageEvent();
Expand All @@ -18,151 +20,107 @@ window.addEventListener("DOMContentLoaded", async () => {
initSettings();
initWorldPicker();
handleBboxInput();
const language = detectLanguage();
const localization = await loadLocalization(language);
const localization = await getLocalization();
await applyLocalization(localization);
initFooter();
await checkForUpdates();
});

async function loadLocalization(language) {
const response = await fetch(`./locales/${language}.json`);
const localization = await response.json();
return localization;
function invalidJSON(response) {
// Workaround for Tauri always falling back to index.html for asset loading
return !response.ok || response.headers.get("Content-Type") === "text/html";
}

async function applyLocalization(localization) {
const selectLocationElement = document.querySelector("h2[data-localize='select_location']");
if (selectLocationElement) {
selectLocationElement.textContent = localization.select_location;
}

const bboxTextElement = document.getElementById("bbox-text");
if (bboxTextElement) {
bboxTextElement.textContent = localization.zoom_in_and_choose;
}

const selectWorldElement = document.querySelector("h2[data-localize='select_world']");
if (selectWorldElement) {
selectWorldElement.textContent = localization.select_world;
}

const chooseWorldButton = document.querySelector("button[data-localize='choose_world']");
if (chooseWorldButton) {
chooseWorldButton.firstChild.textContent = localization.choose_world;
}

const selectedWorldElement = document.getElementById("selected-world");
if (selectedWorldElement) {
selectedWorldElement.textContent = localization.no_world_selected;
}

const startButtonElement = document.getElementById("start-button");
if (startButtonElement) {
startButtonElement.textContent = localization.start_generation;
}

const progressElement = document.querySelector("h2[data-localize='progress']");
if (progressElement) {
progressElement.textContent = localization.progress;
}
async function getLocalization() {
const lang = navigator.language;
let response = await fetch(`./locales/${lang}.json`);

const chooseWorldModalTitle = document.querySelector("h2[data-localize='choose_world_modal_title']");
if (chooseWorldModalTitle) {
chooseWorldModalTitle.textContent = localization.choose_world_modal_title;
}

const selectExistingWorldButton = document.querySelector("button[data-localize='select_existing_world']");
if (selectExistingWorldButton) {
selectExistingWorldButton.textContent = localization.select_existing_world;
}

const generateNewWorldButton = document.querySelector("button[data-localize='generate_new_world']");
if (generateNewWorldButton) {
generateNewWorldButton.textContent = localization.generate_new_world;
}
// Try with only first part of language code
if (invalidJSON(response)) {
response = await fetch(`./locales/${lang.split('-')[0]}.json`);

const customizationSettingsTitle = document.querySelector("h2[data-localize='customization_settings']");
if (customizationSettingsTitle) {
customizationSettingsTitle.textContent = localization.customization_settings;
}

const winterModeLabel = document.querySelector("label[data-localize='winter_mode']");
if (winterModeLabel) {
winterModeLabel.textContent = localization.winter_mode;
}

const worldScaleLabel = document.querySelector("label[data-localize='world_scale']");
if (worldScaleLabel) {
worldScaleLabel.textContent = localization.world_scale;
// Fallback to default English localization
if (invalidJSON(response)) {
response = await fetch(DEFAULT_LOCALE_PATH);
}
}

const customBoundingBoxLabel = document.querySelector("label[data-localize='custom_bounding_box']");
if (customBoundingBoxLabel) {
customBoundingBoxLabel.textContent = localization.custom_bounding_box;
}
const localization = await response.json();
return localization;
}

const floodfillTimeoutLabel = document.querySelector("label[data-localize='floodfill_timeout']");
if (floodfillTimeoutLabel) {
floodfillTimeoutLabel.textContent = localization.floodfill_timeout;
async function localizeElement(json, elementObject, localizedStringKey) {
const element =
(!elementObject.element || elementObject.element === "")
? document.querySelector(elementObject.selector) : elementObject.element;
const attribute = localizedStringKey.startsWith("placeholder_") ? "placeholder" : "textContent";

if (element) {
if (localizedStringKey in json) {
element[attribute] = json[localizedStringKey];
} else {
// Fallback to default (English) string
const response = await fetch(DEFAULT_LOCALE_PATH);
const defaultJson = await response.json();
element[attribute] = defaultJson[localizedStringKey];
}
}
}

const groundLevelLabel = document.querySelector("label[data-localize='ground_level']");
if (groundLevelLabel) {
groundLevelLabel.textContent = localization.ground_level;
}
async function applyLocalization(localization) {
const localizationElements = {
"h2[data-localize='select_location']": "select_location",
"#bbox-text": "zoom_in_and_choose",
"h2[data-localize='select_world']": "select_world",
"span[id='choose_world']": "choose_world",
"#selected-world": "no_world_selected",
"#start-button": "start_generation",
"h2[data-localize='progress']": "progress",
"h2[data-localize='choose_world_modal_title']": "choose_world_modal_title",
"button[data-localize='select_existing_world']": "select_existing_world",
"button[data-localize='generate_new_world']": "generate_new_world",
"h2[data-localize='customization_settings']": "customization_settings",
"label[data-localize='winter_mode']": "winter_mode",
"label[data-localize='world_scale']": "world_scale",
"label[data-localize='custom_bounding_box']": "custom_bounding_box",
"label[data-localize='floodfill_timeout']": "floodfill_timeout",
"label[data-localize='ground_level']": "ground_level",
".footer-link": "footer_text",
"button[data-localize='license_and_credits']": "license_and_credits",
"h2[data-localize='license_and_credits']": "license_and_credits",

// Placeholder strings
"input[id='bbox-coords']": "placeholder_bbox",
"input[id='floodfill-timeout']": "placeholder_floodfill",
"input[id='ground-level']": "placeholder_ground"
};

const footerLinkElement = document.querySelector(".footer-link");
if (footerLinkElement) {
footerLinkElement.innerHTML = localization.footer_text.replace("{year}", '<span id="current-year"></span>').replace("{version}", '<span id="version-placeholder"></span>');
for (const selector in localizationElements) {
localizeElement(localization, { selector: selector }, localizationElements[selector]);
}

// Update error messages
window.localization = localization;
}

function detectLanguage() {
const lang = navigator.language || navigator.userLanguage;
const langCode = lang.split('-')[0];
switch (langCode) {
case 'es':
return 'es';
case 'ru':
return 'ru';
case 'de':
return 'de';
case 'zh':
return 'zh';
case 'uk':
return 'ua';
case 'pl':
return 'pl';
case 'ko':
return 'ko';
case 'sv':
return 'sv';
default:
return 'en';
}
}

// Function to initialize the footer with the current year and version
async function initFooter() {
const currentYear = new Date().getFullYear();
const currentYearElement = document.getElementById("current-year");
if (currentYearElement) {
currentYearElement.textContent = currentYear;
}
let version = "x.x.x";

try {
const version = await invoke('gui_get_version');
const versionPlaceholder = document.getElementById("version-placeholder");
if (versionPlaceholder) {
versionPlaceholder.textContent = version;
}
version = await invoke('gui_get_version');
} catch (error) {
console.error("Failed to fetch version:", error);
}

const footerElement = document.querySelector(".footer-link");
if (footerElement) {
footerElement.textContent =
footerElement.textContent
.replace("{year}", currentYear)
.replace("{version}", version);
}
}

// Function to check for updates and display a notification if available
Expand All @@ -180,7 +138,7 @@ async function checkForUpdates() {
updateMessage.style.display = "block";
updateMessage.style.textDecoration = "none";

updateMessage.textContent = window.localization.new_version_available;
localizeElement(window.localization, { element: updateMessage }, "new_version_available");
footer.style.marginTop = "15px";
footer.appendChild(updateMessage);
}
Expand Down Expand Up @@ -345,17 +303,17 @@ function handleBboxInput() {
map_container.contentWindow.location.reload();

// Update the info text
bboxInfo.textContent = window.localization.custom_selection_confirmed;
localizeElement(window.localization, { element: bboxInfo }, "custom_selection_confirmed");
bboxInfo.style.color = "#7bd864";
} else {
// Valid numbers but invalid order or range
bboxInfo.textContent = window.localization.error_coordinates_out_of_range;
localizeElement(window.localization, { element: bboxInfo }, "error_coordinates_out_of_range");
bboxInfo.style.color = "#fecc44";
selectedBBox = "";
}
} else {
// Input doesn't match the required format
bboxInfo.textContent = window.localization.invalid_format;
localizeElement(window.localization, { element: bboxInfo }, "invalid_format");
bboxInfo.style.color = "#fecc44";
selectedBBox = "";
}
Expand Down Expand Up @@ -414,13 +372,13 @@ function displayBboxInfoText(bboxText) {
const selectedSize = calculateBBoxSize(lng1, lat1, lng2, lat2);

if (selectedSize > threshold2) {
bboxInfo.textContent = window.localization.area_too_large;
localizeElement(window.localization, { element: bboxInfo }, "area_too_large");
bboxInfo.style.color = "#fa7878";
} else if (selectedSize > threshold1) {
bboxInfo.textContent = window.localization.area_extensive;
localizeElement(window.localization, { element: bboxInfo }, "area_extensive");
bboxInfo.style.color = "#fecc44";
} else {
bboxInfo.textContent = window.localization.selection_confirmed;
localizeElement(window.localization, { element: bboxInfo }, "selection_confirmed");
bboxInfo.style.color = "#7bd864";
}
}
Expand All @@ -443,16 +401,17 @@ async function selectWorld(generate_new_world) {
}

function handleWorldSelectionError(errorCode) {
const errorMessages = {
1: window.localization.minecraft_directory_not_found,
2: window.localization.world_in_use,
3: window.localization.failed_to_create_world,
4: window.localization.no_world_selected_error
const errorKeys = {
1: "minecraft_directory_not_found",
2: "world_in_use",
3: "failed_to_create_world",
4: "no_world_selected_error"
};

const errorMessage = errorMessages[errorCode] || "Unknown error";
document.getElementById('selected-world').textContent = errorMessage;
document.getElementById('selected-world').style.color = "#fa7878";
const errorKey = errorKeys[errorCode] || "unknown_error";
const selectedWorld = document.getElementById('selected-world');
localizeElement(window.localization, { element: selectedWorld }, errorKey);
selectedWorld.style.color = "#fa7878";
worldPath = "";
console.error(errorCode);
}
Expand All @@ -465,14 +424,16 @@ async function startGeneration() {
}

if (!selectedBBox || selectedBBox == "0.000000 0.000000 0.000000 0.000000") {
document.getElementById('bbox-info').textContent = window.localization.select_location_first;
document.getElementById('bbox-info').style.color = "#fa7878";
const bboxInfo = document.getElementById('bbox-info');
localizeElement(window.localization, { element: bboxInfo }, "select_location_first");
bboxInfo.style.color = "#fa7878";
return;
}

if (!worldPath || worldPath === "") {
document.getElementById('selected-world').textContent = window.localization.select_minecraft_world_first;
document.getElementById('selected-world').style.color = "#fa7878";
const selectedWorld = document.getElementById('selected-world');
localizeElement(window.localization, { element: selectedWorld }, "select_minecraft_world_first");
selectedWorld.style.color = "#fa7878";
return;
}

Expand Down
2 changes: 0 additions & 2 deletions gui-src/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
"custom_selection_confirmed": "Benutzerdefinierte Auswahl bestätigt!",
"error_coordinates_out_of_range": "Fehler: Koordinaten sind außerhalb des Bereichs oder falsch geordnet (Lat vor Lng erforderlich).",
"invalid_format": "Ungültiges Format. Bitte verwende 'lat,lng,lat,lng' oder 'lat lng lat lng'.",
"select_a_location_first": "Wähle zuerst einen Standort aus!",
"select_a_minecraft_world_first": "Wähle zuerst eine Minecraft Welt aus!",
"generation_process_started": "Generierungsprozess gestartet.",
"winter_mode": "Wintermodus:",
"world_scale": "Weltmaßstab:",
Expand Down
Loading
Loading