mirror of
https://github.com/badlogic/heissepreise.git
synced 2024-06-23 00:45:41 +02:00
Translate all static html files into English and Czech
This commit is contained in:
parent
e9b8e403da
commit
d2ae0cc575
71
bundle.js
Executable file → Normal file
71
bundle.js
Executable file → Normal file
|
@ -4,6 +4,7 @@ const chokidar = require("chokidar");
|
|||
const esbuild = require("esbuild");
|
||||
const { exec } = require("child_process");
|
||||
const { promisify } = require("util");
|
||||
const i18n = require("./site/i18n");
|
||||
|
||||
function deleteDirectory(directory) {
|
||||
if (fs.existsSync(directory)) {
|
||||
|
@ -19,39 +20,69 @@ function deleteDirectory(directory) {
|
|||
}
|
||||
}
|
||||
|
||||
function replaceFileContents(string, fileDir) {
|
||||
const pattern = /%%([^%]+)%%|\/\/\s*include\s*"([^"]+)"/g;
|
||||
/**
|
||||
* Process file content by resolving includes and translation placeholders.
|
||||
*
|
||||
* @param {string} fileContent original file content
|
||||
* @param {string} fileDir path of the directory of the file
|
||||
* @param {string} locale language code which should be used to resolve translation placeholders
|
||||
* @returns {string}
|
||||
*/
|
||||
function replaceFileContents(fileContent, fileDir, locale) {
|
||||
const pattern = /%%([^%]+)%%|__(.+?)__/g;
|
||||
|
||||
return string.replace(pattern, (_, filename1, filename2) => {
|
||||
if (filename2 != null) console.error("include is used!!!!!");
|
||||
const filename = filename1 || filename2;
|
||||
const filenamePath = path.join(fileDir, filename);
|
||||
try {
|
||||
const data = fs.readFileSync(filenamePath, "utf8");
|
||||
const replacedData = replaceFileContents(data, path.dirname(filenamePath));
|
||||
return replacedData;
|
||||
} catch (error) {
|
||||
console.error(`Error reading file "${filenamePath}":`, error);
|
||||
return "";
|
||||
return fileContent.replace(pattern, (_, filename, translationKey) => {
|
||||
if (filename != undefined) {
|
||||
const filenamePath = path.join(fileDir, filename);
|
||||
try {
|
||||
const data = fs.readFileSync(filenamePath, "utf8");
|
||||
const replacedData = replaceFileContents(data, path.dirname(filenamePath), locale);
|
||||
return replacedData;
|
||||
} catch (error) {
|
||||
console.error(`Error reading file "${filenamePath}":`, error);
|
||||
return "";
|
||||
}
|
||||
} else if (translationKey != undefined) {
|
||||
return i18n.translateWithLocale(locale, translationKey);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy inputFile to outputFile, possibly modifying it in the process.
|
||||
*
|
||||
* @param {string} inputFile path
|
||||
* @param {string} outputFile path
|
||||
* @param {function(string, boolean, string):boolean} filter takes path, whether it is a directory and data (or null if directory), returns true if the file should be left out
|
||||
*/
|
||||
function processFile(inputFile, outputFile, filter) {
|
||||
const fileDir = path.dirname(inputFile);
|
||||
if (inputFile.includes(".mp3")) {
|
||||
let extension = path.extname(inputFile);
|
||||
if (extension == ".html") {
|
||||
const data = fs.readFileSync(inputFile, "utf8");
|
||||
if (filter(inputFile, false, data)) return;
|
||||
for (const locale of i18n.locales) {
|
||||
const replacedData = replaceFileContents(data, path.dirname(inputFile), locale);
|
||||
if (locale == i18n.defaultLocale) {
|
||||
fs.writeFileSync(outputFile, replacedData);
|
||||
}
|
||||
let pathWithLanguageCode = outputFile.substring(0, outputFile.length - extension.length) + "." + locale + extension;
|
||||
fs.writeFileSync(pathWithLanguageCode, replacedData);
|
||||
}
|
||||
} else {
|
||||
const data = fs.readFileSync(inputFile);
|
||||
if (filter(inputFile, false, data)) return;
|
||||
fs.writeFileSync(outputFile, data);
|
||||
} else {
|
||||
const data = fs.readFileSync(inputFile, "utf8");
|
||||
if (filter(inputFile, false, data)) return;
|
||||
const replacedData = replaceFileContents(data, fileDir);
|
||||
fs.writeFileSync(outputFile, replacedData);
|
||||
}
|
||||
console.log(`${inputFile} -> ${outputFile}`);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} inputDir path to the input directory, traversed recursively, outputDir and files/directories starting with _ are automatically skipped
|
||||
* @param {string} outputDir path to the output directory
|
||||
* @param {boolean} deleteOutput whether the contents of output directory should be deleted first
|
||||
* @param {function(string, boolean, string):boolean} filter takes path, whether it is a directory and data (or null if directory), returns true if the file should be left out
|
||||
*/
|
||||
function generateSite(inputDir, outputDir, deleteOutput, filter) {
|
||||
if (deleteOutput) {
|
||||
deleteDirectory(outputDir);
|
||||
|
|
50
server.js
50
server.js
|
@ -7,6 +7,7 @@ const csv = require("./site/js/misc");
|
|||
const chokidar = require("chokidar");
|
||||
const express = require("express");
|
||||
const compression = require("compression");
|
||||
const i18n = require("./site/i18n");
|
||||
|
||||
function copyItemsToSite(dataDir) {
|
||||
const items = analysis.readJSON(`${dataDir}/latest-canonical.json.${analysis.FILE_COMPRESSOR}`);
|
||||
|
@ -45,6 +46,7 @@ function parseArguments() {
|
|||
const args = process.argv.slice(2);
|
||||
let port = process.env.PORT !== undefined && process.env.PORT != "" ? parseInt(process.env.PORT) : 3000;
|
||||
let liveReload = process.env.NODE_ENV === "development" || false;
|
||||
let skipDataUpdate = false;
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i] === "-p" || args[i] === "--port") {
|
||||
port = parseInt(args[i + 1]);
|
||||
|
@ -54,6 +56,8 @@ function parseArguments() {
|
|||
throw new Error("Live reload is only supported in development mode");
|
||||
}
|
||||
liveReload = true;
|
||||
} else if (args[i] === "--skip-data-update") {
|
||||
skipDataUpdate = true;
|
||||
} else if (args[i] === "-h" || args[i] === "--help") {
|
||||
console.log("Usage: node server.js [-p|--port PORT] [-l|--live-reload]");
|
||||
console.log();
|
||||
|
@ -64,7 +68,7 @@ function parseArguments() {
|
|||
}
|
||||
}
|
||||
|
||||
return { port, liveReload };
|
||||
return { port, liveReload, skipDataUpdate };
|
||||
}
|
||||
|
||||
function setupLogging() {
|
||||
|
@ -81,7 +85,7 @@ function setupLogging() {
|
|||
|
||||
(async () => {
|
||||
const dataDir = "data";
|
||||
const { port, liveReload } = parseArguments();
|
||||
const { port, liveReload, skipDataUpdate } = parseArguments();
|
||||
|
||||
if (!fs.existsSync(dataDir)) {
|
||||
fs.mkdirSync(dataDir);
|
||||
|
@ -102,25 +106,43 @@ function setupLogging() {
|
|||
setupLogging();
|
||||
bundle.bundle("site", outputDir, liveReload);
|
||||
|
||||
analysis.migrateCompression(dataDir, ".json", ".json.br");
|
||||
analysis.migrateCompression(dataDir, ".json.gz", ".json.br");
|
||||
if (!skipDataUpdate) {
|
||||
analysis.migrateCompression(dataDir, ".json", ".json.br");
|
||||
analysis.migrateCompression(dataDir, ".json.gz", ".json.br");
|
||||
|
||||
if (fs.existsSync(`${dataDir}/latest-canonical.json.${analysis.FILE_COMPRESSOR}`)) {
|
||||
copyItemsToSite(dataDir);
|
||||
analysis.updateData(dataDir, (_newItems) => {
|
||||
if (fs.existsSync(`${dataDir}/latest-canonical.json.${analysis.FILE_COMPRESSOR}`)) {
|
||||
copyItemsToSite(dataDir);
|
||||
analysis.updateData(dataDir, (_newItems) => {
|
||||
copyItemsToSite(dataDir);
|
||||
});
|
||||
} else {
|
||||
await analysis.updateData(dataDir);
|
||||
copyItemsToSite(dataDir);
|
||||
}
|
||||
scheduleFunction(5, 0, 0, async () => {
|
||||
items = await analysis.updateData(dataDir);
|
||||
copyItemsToSite(dataDir);
|
||||
});
|
||||
} else {
|
||||
await analysis.updateData(dataDir);
|
||||
copyItemsToSite(dataDir);
|
||||
}
|
||||
scheduleFunction(5, 0, 0, async () => {
|
||||
items = await analysis.updateData(dataDir);
|
||||
copyItemsToSite(dataDir);
|
||||
});
|
||||
|
||||
const app = express();
|
||||
app.use(compression());
|
||||
app.use(function (req, res, next) {
|
||||
if (req.method == "GET") {
|
||||
if (req.path == "/") {
|
||||
req.url = "/index.html";
|
||||
}
|
||||
if (req.path.endsWith(".html")) {
|
||||
// Only html files are translated
|
||||
let pickedLanguage = req.acceptsLanguages(i18n.locales);
|
||||
if (pickedLanguage) {
|
||||
let translatedPath = req.path.substring(0, req.path.length - "html".length) + pickedLanguage + ".html";
|
||||
req.url = translatedPath;
|
||||
} // otherwise use default, untranslated file
|
||||
}
|
||||
}
|
||||
next();
|
||||
});
|
||||
app.use(express.static("site/output"));
|
||||
const server = http.createServer(app).listen(port, () => {
|
||||
console.log(`App listening on port ${port}`);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<footer>
|
||||
<div class="flex align-center justify-center gap-2 pt-4">
|
||||
<a class="font-medium" href="settings.html">Einstellungen</a>
|
||||
<a class="font-medium" href="imprint.html">Impressum</a>
|
||||
<a class="font-medium" href="data/log.txt">Logs</a>
|
||||
<a class="font-medium" href="settings.html">__Einstellungen__</a>
|
||||
<a class="font-medium" href="imprint.html">__Impressum__</a>
|
||||
<a class="font-medium" href="data/log.txt">__Logs__</a>
|
||||
<a href="https://twitter.com/badlogicgames" style="width: 24px;" aria-label="twitter.com/badlogicgames"><svg style="padding-top: 2px;" viewBox="328 355 335 276" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="
|
||||
M 630, 425
|
||||
|
@ -25,11 +25,11 @@
|
|||
<a href="https://github.com/badlogic/heissepreise" aria-label="github.com/badlogic/heissepreise"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg></a>
|
||||
</div>
|
||||
<div class="flex align-center justify-center gap-2 pt-4 pb-2">
|
||||
<span>Historische Daten von <a href="http://h.43z.one" class="font-medium">@h43z</a> & <a href="https://www.dossier.at/dossiers/supermaerkte/quellen/anatomie-eines-supermarkts-die-methodik/" class="font-medium">Dossier</a></span>
|
||||
<span>__Historische Daten von__ <a href="http://h.43z.one" class="font-medium">@h43z</a> & <a href="https://www.dossier.at/dossiers/supermaerkte/quellen/anatomie-eines-supermarkts-die-methodik/" class="font-medium">Dossier</a></span>
|
||||
</div>
|
||||
<small class="text-center mb-6">
|
||||
<p>Alle Angaben ohne Gewähr, Irrtümer vorbehalten. <br />
|
||||
Markennamen und Warenzeichen sind Eigentum der jeweiligen Inhaber.
|
||||
<p>__Alle Angaben ohne Gewähr, Irrtümer vorbehalten.__<br />
|
||||
__Markennamen und Warenzeichen sind Eigentum der jeweiligen Inhaber.__
|
||||
</p>
|
||||
</small>
|
||||
</footer>
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="Nicht-kommerzielles Open-Source-Projekt um KonsumentInnen es zu ermöglichen, die günstigste Variante eines Produktes im Handel ausfindig zu machen." />
|
||||
<title>Heisse Preise</title>
|
||||
<meta name="description" content="__page_description__" />
|
||||
<title>__Heisse Preise__</title>
|
||||
<link
|
||||
rel="icon"
|
||||
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%2210 0 100 100%22><text y=%22.90em%22 font-size=%2290%22>🔥</text></svg>"
|
||||
|
@ -15,4 +15,4 @@
|
|||
|
||||
<body class="w-full min-h-full flex flex-col">
|
||||
<div class="max-w-6xl mx-auto lg:py-4 flex flex-col flex-1 w-full">
|
||||
<h2 class="bg-primary lg:rounded-t-xl text-center text-white p-3 uppercase text-2xl font-bold">🔥 Heisse Preise 🔥</h2>
|
||||
<h2 class="bg-primary lg:rounded-t-xl text-center text-white p-3 uppercase text-2xl font-bold">🔥 __Heisse Preise__ 🔥</h2>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="flex justify-center gap-4 py-2 font-bold text-primary border border-primary/50 lg:rounded-b-xl">
|
||||
<a href="index.html">Suche</a>
|
||||
<a href="changes.html">Preisänderungen</a>
|
||||
<a href="carts.html">Warenkörbe</a>
|
||||
<a href="index.html">__Suche__</a>
|
||||
<a href="changes.html">__Preisänderungen__</a>
|
||||
<a href="carts.html">__Warenkörbe__</a>
|
||||
</div>
|
|
@ -4,13 +4,20 @@
|
|||
<cart-header x-id="cartHeader"></cart-header>
|
||||
|
||||
<div x-id="noItems" class="hidden block text-center my-3">
|
||||
Noch keine Produkte im Warenkorb.<br />
|
||||
Produkte suchen und mit '+' zum Warenkorb hinzufügen.
|
||||
__Noch keine Produkte im Warenkorb.__<br />
|
||||
__Produkte suchen und mit '+' zum Warenkorb hinzufügen.__
|
||||
</div>
|
||||
<items-filter x-id="cartFilter" stores nochartclear class="hidden" placeholder="Filtern... (min. 3 Zeichen)"></items-filter>
|
||||
<items-filter x-id="cartFilter" stores nochartclear class="hidden" placeholder="__Filtern...__ __(min. 3 Zeichen)__"></items-filter>
|
||||
<items-list x-id="linkedCartList" chart json nosort class="hidden"></items-list>
|
||||
<items-list x-id="cartList" chart json nosort remove updown class="hidden"></items-list>
|
||||
<items-filter x-id="productsFilter" class="hidden" stores misc nochartclear placeholder="Produkt hinzufügen... (min. 3 Zeichen)"></items-filter>
|
||||
<items-filter
|
||||
x-id="productsFilter"
|
||||
class="hidden"
|
||||
stores
|
||||
misc
|
||||
nochartclear
|
||||
placeholder="__Produkt hinzufügen...__ (__min. 3 Zeichen__)"
|
||||
></items-filter>
|
||||
<items-list x-id="productsList" class="hidden" add></items-list>
|
||||
%%_templates/_loader.html%%
|
||||
</div>
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
<div class="w-full relative px-4 flex-1">
|
||||
<div class="max-w-3xl mx-auto">
|
||||
<h1 class="text-2xl font-bold pb-2 pt-8 text-center">Warenkörbe</h1>
|
||||
<h1 class="text-2xl font-bold pb-2 pt-8 text-center">__Warenkörbe__</h1>
|
||||
<div class="px-4 py-2 my-4 text-sm border rounded-xl md:mt-8 md:rounded-b-none md:mb-0 bg-gray-100 flex gap-4">
|
||||
<input type="button" id="new" value="Neuer Warenkorb" class="text-primary font-medium hover:underline cursor-pointer" />
|
||||
<input type="button" id="export" value="Exportieren" class="text-primary font-medium hover:underline cursor-pointer" />
|
||||
<input type="button" id="import" value="Importieren" class="text-primary font-medium hover:underline cursor-pointer" />
|
||||
<input type="button" id="new" value="__Neuer Warenkorb__" class="text-primary font-medium hover:underline cursor-pointer" />
|
||||
<input type="button" id="export" value="__Exportieren__" class="text-primary font-medium hover:underline cursor-pointer" />
|
||||
<input type="button" id="import" value="__Importieren__" class="text-primary font-medium hover:underline cursor-pointer" />
|
||||
</div>
|
||||
<carts-list id="carts" class="carts-list w-full"></carts-list>
|
||||
%%_templates/_loader.html%%
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
%%_templates/_header.html%% %%_templates/_menu.html%%
|
||||
|
||||
<div class="w-full relative px-4 flex-1">
|
||||
<h1 class="text-2xl font-bold pb-2 pt-8 text-center">Preisänderungen</h1>
|
||||
<items-filter x-id="items-filter" pricechanges pricedirection stores misc placeholder="Filtern... (min. 3 Zeichen)"></items-filter>
|
||||
<h1 class="text-2xl font-bold pb-2 pt-8 text-center">__Preisänderungen__</h1>
|
||||
<items-filter x-id="items-filter" pricechanges pricedirection stores misc placeholder="__Filtern...__ __(min. 3 Zeichen)__"></items-filter>
|
||||
<items-list chart></items-list>
|
||||
%%_templates/_loader.html%%
|
||||
</div>
|
||||
|
|
44
site/i18n.js
Executable file → Normal file
44
site/i18n.js
Executable file → Normal file
|
@ -11,8 +11,9 @@
|
|||
*
|
||||
* If the key is missing in the current localization, it falls back to default localization
|
||||
* and then to treating the key itself as the localization.
|
||||
*
|
||||
* @type {Object.<string, Object.<string, string>>}
|
||||
*/
|
||||
|
||||
const translations = {
|
||||
cs: require("./locales/cs.json"),
|
||||
de: require("./locales/de.json"),
|
||||
|
@ -21,18 +22,25 @@ const translations = {
|
|||
/**
|
||||
* @type {string[]}
|
||||
*/
|
||||
const locales = Object.keys(locales);
|
||||
const locales = Object.keys(translations);
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
const defaultLocale = "de";
|
||||
|
||||
/** The currently selected locale. */
|
||||
/**
|
||||
* The currently selected locale.
|
||||
* @type {string}
|
||||
*/
|
||||
var currentLocale = defaultLocale;
|
||||
|
||||
/**
|
||||
* Set the globally used locale.
|
||||
* Expects a 2 character language code string, one from locales.
|
||||
* @param {string} locale
|
||||
*/
|
||||
function setLocale(locale) {
|
||||
if (Object.hasOwn(locales, locale)) {
|
||||
if (locales.indexOf(locale) != -1) {
|
||||
currentLocale = locale;
|
||||
return true;
|
||||
}
|
||||
|
@ -41,16 +49,28 @@ function setLocale(locale) {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {string} key to translate
|
||||
* @param {Object?} args arguments to substitute into the translated key
|
||||
* @returns translated string
|
||||
* Translates the key using the current global locale.
|
||||
*
|
||||
* @param {!string} key to translate
|
||||
* @param {!Object.<string, string>} [args] arguments to substitute into the translated key
|
||||
* @returns {string} translated string
|
||||
*/
|
||||
function translate(key, args) {
|
||||
let translation = locales[currentLocale][key];
|
||||
return translateWithLocale(currentLocale, key, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!string} locale name of the language to use for translation, MUST be one of the supported languages
|
||||
* @param {!string} key to translate
|
||||
* @param {!Object.<string, string>} [args] arguments to substitute into the translated key
|
||||
* @returns {string} translated string
|
||||
*/
|
||||
function translateWithLocale(locale, key, args) {
|
||||
let translation = translations[locale][key];
|
||||
if (translation === undefined) {
|
||||
console.error("Untranslated key in ", currentLocale, ": ", key);
|
||||
if (currentLocale != defaultLocale) {
|
||||
translation = locales[defaultLocale][key] || key;
|
||||
console.error("Untranslated key in ", locale, ": ", key);
|
||||
if (locale != defaultLocale) {
|
||||
translation = translations[defaultLocale][key] || key;
|
||||
} else {
|
||||
translation = key;
|
||||
}
|
||||
|
@ -67,5 +87,7 @@ function translate(key, args) {
|
|||
}
|
||||
|
||||
exports.setLocale = setLocale;
|
||||
exports.defaultLocale = defaultLocale;
|
||||
exports.locales = locales;
|
||||
exports.translate = translate;
|
||||
exports.translateWithLocale = translateWithLocale;
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
%%_templates/_header.html%% %%_templates/_menu.html%%
|
||||
|
||||
<div class="max-w-lg mx-auto my-8 px-4">
|
||||
<h1 class="text-2xl font-bold">Impressum</h1>
|
||||
<h1 class="text-2xl font-bold">__Impressum__</h1>
|
||||
<p class="mt-4">
|
||||
<strong>Angaben gemäß § 25 Mediengesetz (Österreich)</strong><br /><br />
|
||||
<b>Medieninhaber:</b> Mario Zechner<br />
|
||||
<b>Kontakt:</b> <a href="mailto:badlogicgames@gmail.com">badlogicgames@gmail.com</a><br />
|
||||
<b>Adresse:</b> Schörgelgasse 3, 8010 Graz, Österreich
|
||||
<b>__Medieninhaber__:</b> Mario Zechner<br />
|
||||
<b>__Kontakt__:</b> <a href="mailto:badlogicgames@gmail.com">badlogicgames@gmail.com</a><br />
|
||||
<b>__Adresse__:</b> Schörgelgasse 3, 8010 Graz, Österreich
|
||||
</p>
|
||||
<p class="mt-4">
|
||||
__Diese nicht-kommerzielle Seite dient KonsumentInnen dazu, Preise von Produkten im Lebensmittelhandel vergleichen zu können.__
|
||||
</p>
|
||||
<p class="mt-4">Diese nicht-kommerzielle Seite dient KonsumentInnen dazu, Preise von Produkten im Lebensmittelhandel vergleichen zu können.</p>
|
||||
</div>
|
||||
|
||||
%%_templates/_footer.html%%
|
||||
|
|
|
@ -2,11 +2,13 @@
|
|||
|
||||
<div class="w-full max-w-5-xl relative px-4 flex-1">
|
||||
<div class="flex flex-row w-full justify-center font-bold text-primary gap-4 mt-4">
|
||||
<a href="https://www.youtube.com/watch?v=2u-T85yMKGI">Video Anleitung</a>
|
||||
<a href="https://docs.google.com/document/d/1Q5OWJOICXjSzTEIHBZgJl1p3FsiWFO0lzXIuwGbXBck/edit?usp=sharing" target="_blank">Text Anleitung</a>
|
||||
<a href="./media.html">Medienberichte</a>
|
||||
<a href="https://www.youtube.com/watch?v=2u-T85yMKGI">__Video Anleitung__</a>
|
||||
<a href="https://docs.google.com/document/d/1Q5OWJOICXjSzTEIHBZgJl1p3FsiWFO0lzXIuwGbXBck/edit?usp=sharing" target="_blank"
|
||||
>__Text Anleitung__</a
|
||||
>
|
||||
<a href="./media.html">__Medienberichte__</a>
|
||||
</div>
|
||||
<h1 class="text-2xl font-bold pb-2 pt-8 text-center">Produktsuche</h1>
|
||||
<h1 class="text-2xl font-bold pb-2 pt-8 text-center">__Produktsuche__</h1>
|
||||
<items-filter x-id="items-filter" emptyquery stores misc></items-filter>
|
||||
<items-list share json chart></items-list>
|
||||
%%_templates/_loader.html%%
|
||||
|
|
36
site/locales/cs.json
Executable file → Normal file
36
site/locales/cs.json
Executable file → Normal file
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"Heisse Preise": "Zlevněno?",
|
||||
"__page_description__": "Nekomerční open source projekt umožňující spotřebitelům najít nejlevnější verzi produktu v obchodech.",
|
||||
"Einstellungen": "Nastavení",
|
||||
"Impressum": "Impressum",
|
||||
"Logs": "Logy",
|
||||
"Historische Daten von": "Historická data od",
|
||||
"Alle Angaben ohne Gewähr, Irrtümer vorbehalten.": "Veškeré informace jsou poskytovány bez záruky, chyby vyhrazeny.",
|
||||
"Markennamen und Warenzeichen sind Eigentum der jeweiligen Inhaber.": "Názvy značek a ochranné známky jsou majetkem příslušných vlastníků.",
|
||||
"Suche": "Vyhledávání",
|
||||
"Preisänderungen": "Změny cen",
|
||||
"Warenkörbe": "Nákupní vozíky",
|
||||
|
||||
"Noch keine Produkte im Warenkorb.": "Nákupní košík je prázdný.",
|
||||
"Produkte suchen und mit '+' zum Warenkorb hinzufügen.": "Vyhledejte produkty a přidejte je do nákupního košíku pomocí '+'.",
|
||||
"Filtern...": "Filtr...",
|
||||
"(min. 3 Zeichen)": "(alespoň 3 znaky)",
|
||||
"Produkt hinzufügen...": "Přidat produkt...",
|
||||
|
||||
"Neuer Warenkorb": "Nový nákupní košík",
|
||||
"Exportieren": "Export",
|
||||
"Importieren": "Import",
|
||||
|
||||
"Medieninhaber": "Majitel",
|
||||
"Kontakt": "Kontakt",
|
||||
"Adresse": "Adresa",
|
||||
"Diese nicht-kommerzielle Seite dient KonsumentInnen dazu, Preise von Produkten im Lebensmittelhandel vergleichen zu können.": "Tato nekomerční stránka umožňuje spotřebitelům porovnávat ceny produktů v obchodě s potravinami.",
|
||||
|
||||
"Video Anleitung": "Video instrukce (německy)",
|
||||
"Text Anleitung": "Textové instrukce (německy)",
|
||||
"Medienberichte": "Napsali o nás",
|
||||
"Produktsuche": "Vyhledávání produktů",
|
||||
|
||||
"Radio & Fernsehen": "Rádio & Televize",
|
||||
"Print & Online": "Tisk & Online"
|
||||
}
|
36
site/locales/de.json
Executable file → Normal file
36
site/locales/de.json
Executable file → Normal file
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"Heisse Preise": "Heisse Preise",
|
||||
"__page_description__": "Nicht-kommerzielles Open-Source-Projekt um KonsumentInnen es zu ermöglichen, die günstigste Variante eines Produktes im Handel ausfindig zu machen.",
|
||||
"Einstellungen": "Einstellungen",
|
||||
"Impressum": "Impressum",
|
||||
"Logs": "Logs",
|
||||
"Historische Daten von": "Historische Daten von",
|
||||
"Alle Angaben ohne Gewähr, Irrtümer vorbehalten.": "Alle Angaben ohne Gewähr, Irrtümer vorbehalten.",
|
||||
"Markennamen und Warenzeichen sind Eigentum der jeweiligen Inhaber.": "Markennamen und Warenzeichen sind Eigentum der jeweiligen Inhaber.",
|
||||
"Suche": "Suche",
|
||||
"Preisänderungen": "Preisänderungen",
|
||||
"Warenkörbe": "Warenkörbe",
|
||||
|
||||
"Noch keine Produkte im Warenkorb.": "Noch keine Produkte im Warenkorb.",
|
||||
"Produkte suchen und mit '+' zum Warenkorb hinzufügen.": "Produkte suchen und mit '+' zum Warenkorb hinzufügen.",
|
||||
"Filtern...": "Filtern...",
|
||||
"(min. 3 Zeichen)": "(min. 3 Zeichen)",
|
||||
"Produkt hinzufügen...": "Produkt hinzufügen...",
|
||||
|
||||
"Neuer Warenkorb": "Neuer Warenkorb",
|
||||
"Exportieren": "Exportieren",
|
||||
"Importieren": "Importieren",
|
||||
|
||||
"Medieninhaber": "Medieninhaber",
|
||||
"Kontakt": "Kontakt",
|
||||
"Adresse": "Adresse",
|
||||
"Diese nicht-kommerzielle Seite dient KonsumentInnen dazu, Preise von Produkten im Lebensmittelhandel vergleichen zu können.": "Diese nicht-kommerzielle Seite dient KonsumentInnen dazu, Preise von Produkten im Lebensmittelhandel vergleichen zu können.",
|
||||
|
||||
"Video Anleitung": "Video Anleitung",
|
||||
"Text Anleitung": "Text Anleitung",
|
||||
"Medienberichte": "Medienberichte",
|
||||
"Produktsuche": "Produktsuche",
|
||||
|
||||
"Radio & Fernsehen": "Radio & Fernsehen",
|
||||
"Print & Online": "Print & Online"
|
||||
}
|
36
site/locales/en.json
Executable file → Normal file
36
site/locales/en.json
Executable file → Normal file
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"Heisse Preise": "Hot Prices",
|
||||
"__page_description__": "Non-commercial open source project to enable consumers to find the cheapest version of a product in stores.",
|
||||
"Einstellungen": "Settings",
|
||||
"Impressum": "Imprint",
|
||||
"Logs": "Logs",
|
||||
"Historische Daten von": "Histoic data from",
|
||||
"Alle Angaben ohne Gewähr, Irrtümer vorbehalten.": "All information provided without guarantee, errors excepted.",
|
||||
"Markennamen und Warenzeichen sind Eigentum der jeweiligen Inhaber.": "Brand names and trademarks are the property of their respective owners.",
|
||||
"Suche": "Search",
|
||||
"Preisänderungen": "Price changes",
|
||||
"Warenkörbe": "Shopping carts",
|
||||
|
||||
"Noch keine Produkte im Warenkorb.": "No products in your shopping cart yet.",
|
||||
"Produkte suchen und mit '+' zum Warenkorb hinzufügen.": "Search for products and add them to the shopping cart with '+'.",
|
||||
"Filtern...": "Filter...",
|
||||
"(min. 3 Zeichen)": "(at least 3 characters)",
|
||||
"Produkt hinzufügen...": "Add product...",
|
||||
|
||||
"Neuer Warenkorb": "New shopping cart",
|
||||
"Exportieren": "Export",
|
||||
"Importieren": "Import",
|
||||
|
||||
"Medieninhaber": "Owner",
|
||||
"Kontakt": "Contact",
|
||||
"Adresse": "Address",
|
||||
"Diese nicht-kommerzielle Seite dient KonsumentInnen dazu, Preise von Produkten im Lebensmittelhandel vergleichen zu können.": "This non-commercial site allows consumers to compare prices of products in the grocery store.",
|
||||
|
||||
"Video Anleitung": "Video instructions (in German)",
|
||||
"Text Anleitung": "Text instructions (in German)",
|
||||
"Medienberichte": "Media reports",
|
||||
"Produktsuche": "Product search",
|
||||
|
||||
"Radio & Fernsehen": "Radio & Television",
|
||||
"Print & Online": "Print & Online"
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
%%_templates/_header.html%% %%_templates/_menu.html%%
|
||||
|
||||
<div class="mx-auto my-8 px-4">
|
||||
<h2 class="text-2xl font-bold">Medienberichte</h2>
|
||||
<h2 class="text-2xl font-bold">__Medienberichte__</h2>
|
||||
<div class="flex flex-col mt-4 gap-4 text-primary">
|
||||
<h3 class="text-xl font-bold text-black">Radio & Fernsehen</h3>
|
||||
<h3 class="text-xl font-bold text-black">__Radio & Fernsehen__</h3>
|
||||
<a href="https://twitter.com/badlogicgames/status/1661401581097570308"
|
||||
>24.5.203 Österreichisches Parlament - Rede von Joachim Schnabel zum Thema Lebensmittelpreise, ÖVP</a
|
||||
>
|
||||
|
@ -42,7 +42,7 @@
|
|||
>
|
||||
<video src="https://marioslab.io/uploads/ZIB_2-REWE-%C3%96sterreich-Chef_%C3%BCber_hohe_Lebensmittelpreise-0220056522.mp4" controls></video>
|
||||
|
||||
<h3 class="text-xl font-bold text-black">Print & Online</h3>
|
||||
<h3 class="text-xl font-bold text-black">__Print & Online__</h3>
|
||||
<a href="https://www.puls24.at/news/politik/preisvergleich-politik-braucht-bis-herbst-twitter-user-2-stunden/297474"
|
||||
>16.5.2023 PULS 24 - Preisvergleich: Politik braucht "bis Herbst", Twitter-User "2 Stunden"</a
|
||||
>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
%%_templates/_header.html%% %%_templates/_menu.html%%
|
||||
|
||||
<div class="max-w-lg mx-auto my-8 px-4">
|
||||
<h2 class="text-2xl font-bold text-center">Einstellungen</h2>
|
||||
<h2 class="text-2xl font-bold text-center">__Einstellungen__</h2>
|
||||
<settings-view></settings-view>
|
||||
</div>
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user