@gjsify/site 0.0.0-stage → 0.63.1-next.236
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +165 -2
- package/lib/cli.d.ts +6 -0
- package/lib/cli.js +91 -0
- package/lib/client/site.css +1286 -0
- package/lib/client/site.js +1 -0
- package/lib/index.d.ts +9 -0
- package/lib/index.js +9 -0
- package/lib/render/color.d.ts +12 -0
- package/lib/render/color.js +45 -0
- package/lib/render/html.d.ts +8 -0
- package/lib/render/html.js +20 -0
- package/lib/render/i18n.d.ts +38 -0
- package/lib/render/i18n.js +106 -0
- package/lib/render/icons.d.ts +14 -0
- package/lib/render/icons.js +42 -0
- package/lib/render/image-backend.d.ts +26 -0
- package/lib/render/image-backend.js +130 -0
- package/lib/render/images.d.ts +51 -0
- package/lib/render/images.js +97 -0
- package/lib/render/pages.d.ts +32 -0
- package/lib/render/pages.js +628 -0
- package/lib/render/qr.d.ts +8 -0
- package/lib/render/qr.js +25 -0
- package/lib/site-data/config.d.ts +23 -0
- package/lib/site-data/config.js +142 -0
- package/lib/site-data/downloads.d.ts +29 -0
- package/lib/site-data/downloads.js +193 -0
- package/lib/site-data/flathub.d.ts +24 -0
- package/lib/site-data/flathub.js +35 -0
- package/lib/site-data/github.d.ts +9 -0
- package/lib/site-data/github.js +45 -0
- package/lib/site-data/index.d.ts +12 -0
- package/lib/site-data/index.js +12 -0
- package/lib/site-data/load.d.ts +18 -0
- package/lib/site-data/load.js +164 -0
- package/lib/site-data/metainfo.d.ts +9 -0
- package/lib/site-data/metainfo.js +142 -0
- package/lib/site-data/network.d.ts +32 -0
- package/lib/site-data/network.js +67 -0
- package/lib/site-data/oars.d.ts +15 -0
- package/lib/site-data/oars.js +62 -0
- package/lib/site-data/po.d.ts +40 -0
- package/lib/site-data/po.js +148 -0
- package/lib/site-data/screenshots.d.ts +33 -0
- package/lib/site-data/screenshots.js +90 -0
- package/lib/site-data/sepa.d.ts +20 -0
- package/lib/site-data/sepa.js +45 -0
- package/lib/site-data/types.d.ts +224 -0
- package/lib/site-data/types.js +1 -0
- package/lib/site-data/xml.d.ts +11 -0
- package/lib/site-data/xml.js +51 -0
- package/lib/site.d.ts +49 -0
- package/lib/site.js +161 -0
- package/package.json +78 -5
- package/po/de.po +265 -0
- package/po/site.pot +264 -0
package/lib/site.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// The two things a consumer asks of this package: render the pages of a repository, and keep the
|
|
2
|
+
// page catalogues in sync with the strings the renderer can emit. `cli.ts` is argv parsing on top.
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { existsSync } from 'node:fs';
|
|
5
|
+
import { copyFile, mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises';
|
|
6
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import { catalogMsgids, configMsgids, rendererMsgids, syncCatalogs } from './render/i18n.js';
|
|
9
|
+
import { processImage } from './render/images.js';
|
|
10
|
+
import { renderPage, renderRoot } from './render/pages.js';
|
|
11
|
+
import { UI_SOURCE_LANGUAGE, loadCatalogs, loadSiteData, mergeCatalogs, parseSiteConfig, translator, } from './site-data/index.js';
|
|
12
|
+
/**
|
|
13
|
+
* This package's own directory: `lib/` holds the client bundle, `po/` the page catalogues.
|
|
14
|
+
*
|
|
15
|
+
* Both `src/site.ts` and the emitted `lib/site.js` sit one level below it, so the module's own URL
|
|
16
|
+
* answers this — except from a bundle, where the inlined module reports the outfile's location. A
|
|
17
|
+
* caller that knows better passes `packageDir`.
|
|
18
|
+
*/
|
|
19
|
+
const packageDir = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
20
|
+
const poDir = (dir = packageDir) => join(dir, 'po');
|
|
21
|
+
const clientDir = (dir = packageDir) => join(dir, 'lib/client');
|
|
22
|
+
/** Where the renderer's translator calls are read from when this package's own catalogue is synced. */
|
|
23
|
+
const rendererDir = (dir = packageDir) => join(dir, 'src/render');
|
|
24
|
+
/** `options.packageDir` when given, else the install this module was loaded from. */
|
|
25
|
+
const selfDir = (given) => (given === undefined ? packageDir : resolve(given));
|
|
26
|
+
export function defaultCacheDir(root) {
|
|
27
|
+
return join(root, 'node_modules/.cache/gjsify-site');
|
|
28
|
+
}
|
|
29
|
+
async function readConfig(root) {
|
|
30
|
+
const manifest = JSON.parse(await readFile(join(root, 'package.json'), 'utf8'));
|
|
31
|
+
return parseSiteConfig(manifest);
|
|
32
|
+
}
|
|
33
|
+
/** Short, stable and enough to bust a cache: the first bytes of the content's SHA-256. */
|
|
34
|
+
function contentHash(content) {
|
|
35
|
+
return createHash('sha256').update(content).digest('hex').slice(0, 8);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Stages the client bundle and the stylesheet this package ships, under content-addressed names.
|
|
39
|
+
* Both are built once, when the package is built — a site build neither needs a bundler nor an
|
|
40
|
+
* opinion about one.
|
|
41
|
+
*/
|
|
42
|
+
async function stageClientAssets(base, out, self) {
|
|
43
|
+
const assetDir = join(out, 'assets');
|
|
44
|
+
await mkdir(assetDir, { recursive: true });
|
|
45
|
+
const staged = await Promise.all(['js', 'css'].map(async (extension) => {
|
|
46
|
+
const content = await readFile(join(clientDir(self), `site.${extension}`));
|
|
47
|
+
const name = `site-${contentHash(content)}.${extension}`;
|
|
48
|
+
await writeFile(join(assetDir, name), content);
|
|
49
|
+
return `${base}assets/${name}`;
|
|
50
|
+
}));
|
|
51
|
+
return { js: staged[0], css: [staged[1]] };
|
|
52
|
+
}
|
|
53
|
+
/** Keyed by slug, which is what a `Screenshot` carries and the renderer looks up. */
|
|
54
|
+
async function processScreenshots(data, { out }, cacheDir) {
|
|
55
|
+
const options = {
|
|
56
|
+
outDir: join(out, 'img'),
|
|
57
|
+
cacheDir: join(cacheDir, 'images'),
|
|
58
|
+
urlBase: `${data.config.base}img/`,
|
|
59
|
+
};
|
|
60
|
+
const entries = await Promise.all(data.screenshots.map(async (screenshot) => [screenshot.slug, await processImage(screenshot.file, screenshot.slug, options)]));
|
|
61
|
+
return new Map(entries);
|
|
62
|
+
}
|
|
63
|
+
async function publishIcon(data, { root, out }) {
|
|
64
|
+
if (!data.app.icon)
|
|
65
|
+
return undefined;
|
|
66
|
+
const name = basename(data.app.icon);
|
|
67
|
+
await copyFile(resolve(root, data.app.icon), join(out, name));
|
|
68
|
+
return `${data.config.base}${name}`;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The page catalogues: the ones this package ships, overlaid with the consumer's own. A consumer
|
|
72
|
+
* writes its captions and download labels itself, so only it can translate them.
|
|
73
|
+
*/
|
|
74
|
+
async function loadPageCatalogs(config, root, self, warn) {
|
|
75
|
+
const base = await loadCatalogs(poDir(self), config.languages);
|
|
76
|
+
const catalogs = config.pageTranslations
|
|
77
|
+
? mergeCatalogs(base, await loadCatalogs(resolve(root, config.pageTranslations), config.languages))
|
|
78
|
+
: base;
|
|
79
|
+
if (warn) {
|
|
80
|
+
const own = configMsgids(config);
|
|
81
|
+
for (const language of config.languages) {
|
|
82
|
+
if (language === UI_SOURCE_LANGUAGE)
|
|
83
|
+
continue;
|
|
84
|
+
const missing = own.filter((msgid) => !catalogs[language]?.get(msgid));
|
|
85
|
+
if (missing.length === 0)
|
|
86
|
+
continue;
|
|
87
|
+
warn(`${language}: ${missing.length} string(s) from gjsify.site are untranslated, e.g. "${missing[0]}". ` +
|
|
88
|
+
'Run "gjsify site i18n" to write them to gjsify.site.pageTranslations.');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return catalogs;
|
|
92
|
+
}
|
|
93
|
+
/** Renders one static page per configured language, plus the language-picking root page. */
|
|
94
|
+
export async function buildSite(options) {
|
|
95
|
+
const target = { root: resolve(options.root), out: resolve(options.out) };
|
|
96
|
+
const cacheDir = options.cacheDir === false ? undefined : (options.cacheDir ?? defaultCacheDir(target.root));
|
|
97
|
+
const self = selfDir(options.packageDir);
|
|
98
|
+
const config = await readConfig(target.root);
|
|
99
|
+
await rm(target.out, { recursive: true, force: true });
|
|
100
|
+
await mkdir(target.out, { recursive: true });
|
|
101
|
+
const assets = await stageClientAssets(config.base, target.out, self);
|
|
102
|
+
const catalogs = await loadPageCatalogs(config, target.root, self, options.warn);
|
|
103
|
+
const data = await loadSiteData({
|
|
104
|
+
root: target.root,
|
|
105
|
+
screenshotCacheDir: join(cacheDir ?? defaultCacheDir(target.root), 'screenshots'),
|
|
106
|
+
offline: options.offline ?? false,
|
|
107
|
+
githubToken: options.githubToken,
|
|
108
|
+
network: cacheDir ? { cacheDir: join(cacheDir, 'http') } : {},
|
|
109
|
+
warn: options.warn,
|
|
110
|
+
});
|
|
111
|
+
const images = await processScreenshots(data, target, cacheDir ?? defaultCacheDir(target.root));
|
|
112
|
+
const iconUrl = await publishIcon(data, target);
|
|
113
|
+
for (const lang of data.config.languages) {
|
|
114
|
+
const html = renderPage({ data, lang, t: translator(catalogs[lang]), assets, images, iconUrl });
|
|
115
|
+
await mkdir(join(target.out, lang), { recursive: true });
|
|
116
|
+
await writeFile(join(target.out, lang, 'index.html'), html);
|
|
117
|
+
}
|
|
118
|
+
await writeFile(join(target.out, 'index.html'), renderRoot(data, assets, iconUrl));
|
|
119
|
+
return { languages: [...data.config.languages], screenshots: images.size };
|
|
120
|
+
}
|
|
121
|
+
/** The languages this package keeps a catalogue for: whichever `po/<lang>.po` files exist. */
|
|
122
|
+
async function packageLanguages(dir) {
|
|
123
|
+
const files = await readdir(dir);
|
|
124
|
+
return files.filter((file) => file.endsWith('.po')).map((file) => basename(file, '.po'));
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Keeps gettext catalogues in sync with the strings that can actually be emitted — this package's
|
|
128
|
+
* own `po/` when called without a root, otherwise the consumer's `pageTranslations`.
|
|
129
|
+
*/
|
|
130
|
+
export async function syncSiteI18n(options = {}) {
|
|
131
|
+
const check = options.check ?? false;
|
|
132
|
+
const self = selfDir(options.packageDir);
|
|
133
|
+
if (options.root === undefined) {
|
|
134
|
+
const renderers = rendererDir(self);
|
|
135
|
+
if (!existsSync(renderers)) {
|
|
136
|
+
throw new Error(`${renderers} does not exist: the package catalogue is synced from a source checkout`);
|
|
137
|
+
}
|
|
138
|
+
return syncCatalogs({
|
|
139
|
+
dir: poDir(self),
|
|
140
|
+
languages: await packageLanguages(poDir(self)),
|
|
141
|
+
msgids: await rendererMsgids(renderers),
|
|
142
|
+
check,
|
|
143
|
+
pot: true,
|
|
144
|
+
base: self,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
const root = resolve(options.root);
|
|
148
|
+
const config = await readConfig(root);
|
|
149
|
+
const shipped = await catalogMsgids(join(poDir(self), 'site.pot'));
|
|
150
|
+
const msgids = configMsgids(config).filter((msgid) => !shipped.has(msgid));
|
|
151
|
+
if (msgids.length === 0)
|
|
152
|
+
return { stale: [], untranslated: [] };
|
|
153
|
+
if (!config.pageTranslations) {
|
|
154
|
+
throw new Error(`gjsify.site introduces ${msgids.length} string(s) of its own, e.g. "${msgids[0]}". ` +
|
|
155
|
+
'Set gjsify.site.pageTranslations to the directory their catalogues should live in.');
|
|
156
|
+
}
|
|
157
|
+
const dir = resolve(root, config.pageTranslations);
|
|
158
|
+
if (!check)
|
|
159
|
+
await mkdir(dir, { recursive: true });
|
|
160
|
+
return syncCatalogs({ dir, languages: config.languages, msgids, check, base: root });
|
|
161
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,79 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
"name": "@gjsify/site",
|
|
3
|
+
"version": "0.63.1-next.236",
|
|
4
|
+
"description": "Static landing page generator for a GNOME-style app, rendered from its AppStream metainfo — downloads, screenshots, release notes, support links, no JavaScript required.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"module": "lib/index.js",
|
|
8
|
+
"types": "lib/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./lib/index.d.ts",
|
|
12
|
+
"default": "./lib/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./package.json": "./package.json"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"lib",
|
|
18
|
+
"po"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"clear": "gjsify clear lib tsconfig.tsbuildinfo test.gjs.mjs test.node.mjs dist",
|
|
22
|
+
"check": "gjsify tsc --noEmit",
|
|
23
|
+
"build": "gjsify tsc -p tsconfig.build.json && gjsify run build:client && gjsify copy src/styles/site.css lib/client/site.css",
|
|
24
|
+
"build:client": "gjsify build src/client/main.ts --app browser --outfile lib/client/site.js",
|
|
25
|
+
"build:test": "gjsify run build:test:gjs && gjsify run build:test:node",
|
|
26
|
+
"build:test:gjs": "gjsify build src/test.gjs.mts --app gjs --outfile test.gjs.mjs",
|
|
27
|
+
"build:test:node": "gjsify build src/test.mts --app node --outfile test.node.mjs",
|
|
28
|
+
"test": "gjsify run build:test && gjsify run test:node && gjsify run test:gjs",
|
|
29
|
+
"test:gjs": "gjsify run test.gjs.mjs",
|
|
30
|
+
"test:node": "node test.node.mjs",
|
|
31
|
+
"test:bun": "bun test.node.mjs",
|
|
32
|
+
"test:deno": "deno run -A --no-lock --node-modules-dir=none --min-dep-age=0 test.node.mjs",
|
|
33
|
+
"test:cross-runtime": "gjsify run build:test:node && gjsify run test:node && gjsify run test:bun && gjsify run test:deno",
|
|
34
|
+
"test:gjs-on-node": "gjsify build src/test.node-gi.mts --app node --outfile dist/test.node-gi.mjs && node dist/test.node-gi.mjs",
|
|
35
|
+
"i18n": "gjsify site i18n --package"
|
|
36
|
+
},
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "git+https://github.com/gjsify/gjsify.git",
|
|
40
|
+
"directory": "packages/infra/site"
|
|
41
|
+
},
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/gjsify/gjsify/issues"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://github.com/gjsify/gjsify/tree/main/packages/infra/site#readme",
|
|
46
|
+
"keywords": [
|
|
47
|
+
"appstream",
|
|
48
|
+
"metainfo",
|
|
49
|
+
"static-site",
|
|
50
|
+
"landing-page",
|
|
51
|
+
"gnome",
|
|
52
|
+
"flathub"
|
|
53
|
+
],
|
|
54
|
+
"license": "MIT",
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"@girs/gdkpixbuf-2.0": "5.4.0",
|
|
57
|
+
"@gjsify/console": "0.63.1-next.236",
|
|
58
|
+
"@gjsify/crypto": "0.63.1-next.236",
|
|
59
|
+
"@gjsify/fs": "0.63.1-next.236",
|
|
60
|
+
"@gjsify/module": "0.63.1-next.236",
|
|
61
|
+
"@gjsify/os": "0.63.1-next.236",
|
|
62
|
+
"@gjsify/path": "0.63.1-next.236",
|
|
63
|
+
"@gjsify/process": "0.63.1-next.236",
|
|
64
|
+
"@gjsify/url": "0.63.1-next.236",
|
|
65
|
+
"fast-xml-parser": "^5.11.2",
|
|
66
|
+
"qrcode-generator": "^2.0.4"
|
|
67
|
+
},
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"@gjsify/cli": "0.63.1-next.236",
|
|
70
|
+
"@gjsify/node-gi": "file:../../node-gi/node-gi",
|
|
71
|
+
"@gjsify/unit": "0.63.1-next.236",
|
|
72
|
+
"@types/node": "^25.9.2",
|
|
73
|
+
"typescript": "^6.0.3"
|
|
74
|
+
},
|
|
75
|
+
"gjsify": {
|
|
76
|
+
"tier": 2
|
|
77
|
+
},
|
|
78
|
+
"gitHead": "d4ae412daabae9cb89f4fa11ee7279a308c9e4be"
|
|
79
|
+
}
|
package/po/de.po
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
msgid ""
|
|
2
|
+
msgstr ""
|
|
3
|
+
"Project-Id-Version: gjsify-site\n"
|
|
4
|
+
"Language: de\n"
|
|
5
|
+
"MIME-Version: 1.0\n"
|
|
6
|
+
"Content-Type: text/plain; charset=UTF-8\n"
|
|
7
|
+
"Content-Transfer-Encoding: 8bit\n"
|
|
8
|
+
|
|
9
|
+
msgid "Website"
|
|
10
|
+
msgstr "Webseite"
|
|
11
|
+
|
|
12
|
+
msgid "Help"
|
|
13
|
+
msgstr "Hilfe"
|
|
14
|
+
|
|
15
|
+
msgid "FAQ"
|
|
16
|
+
msgstr "FAQ"
|
|
17
|
+
|
|
18
|
+
msgid "Source code"
|
|
19
|
+
msgstr "Quellcode"
|
|
20
|
+
|
|
21
|
+
msgid "Report an issue"
|
|
22
|
+
msgstr "Fehler melden"
|
|
23
|
+
|
|
24
|
+
msgid "Contribute"
|
|
25
|
+
msgstr "Mitmachen"
|
|
26
|
+
|
|
27
|
+
msgid "Help translate"
|
|
28
|
+
msgstr "Beim Übersetzen helfen"
|
|
29
|
+
|
|
30
|
+
msgid "Contact"
|
|
31
|
+
msgstr "Kontakt"
|
|
32
|
+
|
|
33
|
+
msgid "Donate"
|
|
34
|
+
msgstr "Spenden"
|
|
35
|
+
|
|
36
|
+
msgid "Linux"
|
|
37
|
+
msgstr "Linux"
|
|
38
|
+
|
|
39
|
+
msgid "macOS"
|
|
40
|
+
msgstr "macOS"
|
|
41
|
+
|
|
42
|
+
msgid "Windows"
|
|
43
|
+
msgstr "Windows"
|
|
44
|
+
|
|
45
|
+
msgid "Android"
|
|
46
|
+
msgstr "Android"
|
|
47
|
+
|
|
48
|
+
msgid "Web"
|
|
49
|
+
msgstr "Web"
|
|
50
|
+
|
|
51
|
+
msgid "Firefox"
|
|
52
|
+
msgstr "Firefox"
|
|
53
|
+
|
|
54
|
+
msgid "Chromium"
|
|
55
|
+
msgstr "Chromium"
|
|
56
|
+
|
|
57
|
+
msgid "Install from Flathub"
|
|
58
|
+
msgstr "Über Flathub installieren"
|
|
59
|
+
|
|
60
|
+
msgid "Build from source"
|
|
61
|
+
msgstr "Aus dem Quellcode bauen"
|
|
62
|
+
|
|
63
|
+
msgid "Try in browser"
|
|
64
|
+
msgstr "Im Browser ausprobieren"
|
|
65
|
+
|
|
66
|
+
msgid "Add to {browser}"
|
|
67
|
+
msgstr "Zu {browser} hinzufügen"
|
|
68
|
+
|
|
69
|
+
msgid "Install for {browser}"
|
|
70
|
+
msgstr "Für {browser} installieren"
|
|
71
|
+
|
|
72
|
+
msgid "Download for {os}"
|
|
73
|
+
msgstr "Für {os} herunterladen"
|
|
74
|
+
|
|
75
|
+
msgid "Screenshot {number}"
|
|
76
|
+
msgstr "Bildschirmfoto {number}"
|
|
77
|
+
|
|
78
|
+
msgid "{number} of {total}"
|
|
79
|
+
msgstr "{number} von {total}"
|
|
80
|
+
|
|
81
|
+
msgid "slide"
|
|
82
|
+
msgstr "Folie"
|
|
83
|
+
|
|
84
|
+
msgid "View full screen"
|
|
85
|
+
msgstr "Vollbild anzeigen"
|
|
86
|
+
|
|
87
|
+
msgid "Screenshots"
|
|
88
|
+
msgstr "Bildschirmfotos"
|
|
89
|
+
|
|
90
|
+
msgid "carousel"
|
|
91
|
+
msgstr "Karussell"
|
|
92
|
+
|
|
93
|
+
msgid "Previous screenshot"
|
|
94
|
+
msgstr "Vorheriges Bildschirmfoto"
|
|
95
|
+
|
|
96
|
+
msgid "Next screenshot"
|
|
97
|
+
msgstr "Nächstes Bildschirmfoto"
|
|
98
|
+
|
|
99
|
+
msgid "Close"
|
|
100
|
+
msgstr "Schließen"
|
|
101
|
+
|
|
102
|
+
msgid "Recommended"
|
|
103
|
+
msgstr "Empfohlen"
|
|
104
|
+
|
|
105
|
+
msgid "Not yet available"
|
|
106
|
+
msgstr "Noch nicht verfügbar"
|
|
107
|
+
|
|
108
|
+
msgid "All releases"
|
|
109
|
+
msgstr "Alle Versionen"
|
|
110
|
+
|
|
111
|
+
msgid "Apple Silicon is assumed. Pick the Intel build if your Mac has an Intel processor."
|
|
112
|
+
msgstr "Apple Silicon wird angenommen. Für Macs mit Intel-Prozessor die Intel-Variante wählen."
|
|
113
|
+
|
|
114
|
+
msgid "All download options"
|
|
115
|
+
msgstr "Alle Download-Optionen"
|
|
116
|
+
|
|
117
|
+
msgid "Operating system"
|
|
118
|
+
msgstr "Betriebssystem"
|
|
119
|
+
|
|
120
|
+
msgid "All releases on GitHub"
|
|
121
|
+
msgstr "Alle Versionen auf GitHub"
|
|
122
|
+
|
|
123
|
+
msgid "Download size"
|
|
124
|
+
msgstr "Downloadgröße"
|
|
125
|
+
|
|
126
|
+
msgid "License"
|
|
127
|
+
msgstr "Lizenz"
|
|
128
|
+
|
|
129
|
+
msgid "Browsers"
|
|
130
|
+
msgstr "Browser"
|
|
131
|
+
|
|
132
|
+
msgid "Platforms"
|
|
133
|
+
msgstr "Plattformen"
|
|
134
|
+
|
|
135
|
+
msgid "All ages"
|
|
136
|
+
msgstr "Alle Altersgruppen"
|
|
137
|
+
|
|
138
|
+
msgid "{age}+"
|
|
139
|
+
msgstr "ab {age}"
|
|
140
|
+
|
|
141
|
+
msgid "Age rating"
|
|
142
|
+
msgstr "Altersfreigabe"
|
|
143
|
+
|
|
144
|
+
msgid "Installs"
|
|
145
|
+
msgstr "Installationen"
|
|
146
|
+
|
|
147
|
+
msgid "Show more"
|
|
148
|
+
msgstr "Mehr anzeigen"
|
|
149
|
+
|
|
150
|
+
msgid "Show less"
|
|
151
|
+
msgstr "Weniger anzeigen"
|
|
152
|
+
|
|
153
|
+
msgid "Changes in version {version}"
|
|
154
|
+
msgstr "Änderungen in Version {version}"
|
|
155
|
+
|
|
156
|
+
msgid "Older releases"
|
|
157
|
+
msgstr "Ältere Versionen"
|
|
158
|
+
|
|
159
|
+
msgid "Links"
|
|
160
|
+
msgstr "Links"
|
|
161
|
+
|
|
162
|
+
msgid "Copied"
|
|
163
|
+
msgstr "Kopiert"
|
|
164
|
+
|
|
165
|
+
msgid "Copy"
|
|
166
|
+
msgstr "Kopieren"
|
|
167
|
+
|
|
168
|
+
msgid "Bank transfer"
|
|
169
|
+
msgstr "Banküberweisung"
|
|
170
|
+
|
|
171
|
+
msgid "Recipient"
|
|
172
|
+
msgstr "Empfänger"
|
|
173
|
+
|
|
174
|
+
msgid "Reference"
|
|
175
|
+
msgstr "Verwendungszweck"
|
|
176
|
+
|
|
177
|
+
msgid "QR code for a bank transfer"
|
|
178
|
+
msgstr "QR-Code für eine Banküberweisung"
|
|
179
|
+
|
|
180
|
+
msgid "Scan with your banking app"
|
|
181
|
+
msgstr "Mit der Banking-App scannen"
|
|
182
|
+
|
|
183
|
+
msgid "Support this project"
|
|
184
|
+
msgstr "Dieses Projekt unterstützen"
|
|
185
|
+
|
|
186
|
+
msgid "Categories"
|
|
187
|
+
msgstr "Kategorien"
|
|
188
|
+
|
|
189
|
+
msgid "Keywords"
|
|
190
|
+
msgstr "Stichwörter"
|
|
191
|
+
|
|
192
|
+
msgid "Architectures"
|
|
193
|
+
msgstr "Architekturen"
|
|
194
|
+
|
|
195
|
+
msgid "Language"
|
|
196
|
+
msgstr "Sprache"
|
|
197
|
+
|
|
198
|
+
msgid "by {developer}"
|
|
199
|
+
msgstr "von {developer}"
|
|
200
|
+
|
|
201
|
+
msgid "{app} is licensed under {license}."
|
|
202
|
+
msgstr "{app} steht unter der Lizenz {license}."
|
|
203
|
+
|
|
204
|
+
msgid "This page reproduces text licensed under {license}."
|
|
205
|
+
msgstr "Diese Seite gibt Texte unter der Lizenz {license} wieder."
|
|
206
|
+
|
|
207
|
+
msgid "Made with {project}"
|
|
208
|
+
msgstr "Gemacht mit {project}"
|
|
209
|
+
|
|
210
|
+
msgid "Flathub"
|
|
211
|
+
msgstr "Flathub"
|
|
212
|
+
|
|
213
|
+
msgid "Flatpak bundle"
|
|
214
|
+
msgstr "Flatpak-Bundle"
|
|
215
|
+
|
|
216
|
+
msgid "AppImage"
|
|
217
|
+
msgstr "AppImage"
|
|
218
|
+
|
|
219
|
+
msgid "Debian package"
|
|
220
|
+
msgstr "Debian-Paket"
|
|
221
|
+
|
|
222
|
+
msgid "RPM package"
|
|
223
|
+
msgstr "RPM-Paket"
|
|
224
|
+
|
|
225
|
+
msgid "Windows installer"
|
|
226
|
+
msgstr "Windows-Installer"
|
|
227
|
+
|
|
228
|
+
msgid "Windows portable"
|
|
229
|
+
msgstr "Windows (portabel)"
|
|
230
|
+
|
|
231
|
+
msgid "macOS app"
|
|
232
|
+
msgstr "macOS-App"
|
|
233
|
+
|
|
234
|
+
msgid "macOS disk image"
|
|
235
|
+
msgstr "macOS-Disk-Image"
|
|
236
|
+
|
|
237
|
+
msgid "Android package"
|
|
238
|
+
msgstr "Android-Paket"
|
|
239
|
+
|
|
240
|
+
msgid "Web build"
|
|
241
|
+
msgstr "Web-Version"
|
|
242
|
+
|
|
243
|
+
msgid "Firefox Add-ons"
|
|
244
|
+
msgstr "Firefox-Add-ons"
|
|
245
|
+
|
|
246
|
+
msgid "Signed Firefox add-on"
|
|
247
|
+
msgstr "Signiertes Firefox-Add-on"
|
|
248
|
+
|
|
249
|
+
msgid "Firefox extension"
|
|
250
|
+
msgstr "Firefox-Erweiterung"
|
|
251
|
+
|
|
252
|
+
msgid "Chrome Web Store"
|
|
253
|
+
msgstr "Chrome Web Store"
|
|
254
|
+
|
|
255
|
+
msgid "Chromium extension"
|
|
256
|
+
msgstr "Chromium-Erweiterung"
|
|
257
|
+
|
|
258
|
+
msgid "ZIP archive"
|
|
259
|
+
msgstr "ZIP-Archiv"
|
|
260
|
+
|
|
261
|
+
msgid "Store"
|
|
262
|
+
msgstr "Store"
|
|
263
|
+
|
|
264
|
+
msgid "Firefox add-on"
|
|
265
|
+
msgstr "Firefox-Add-on"
|