Skip to content
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
26 changes: 26 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,32 @@ updates:
schedule:
interval: "weekly"
open-pull-requests-limit: 10
# HELD BACK DELIBERATELY, not out of caution. Each of these is blocked by a
# package we do not control, and dependabot cannot see that, so it proposes
# them on every run and each one takes `npm ci` or `npm run build` from
# green to red with no code change that can fix it.
#
# typescript 7: typescript-eslint hard-throws on TS >= 7 (a `versionMajor
# >= 7` guard in its dist/index.js) and every published
# version still peers `typescript: ">=4.8.4 <6.1.0"`.
# webpack-cli 7: @nextcloud/webpack-vue-config 7.0.4, the latest, peers
# `webpack-cli: ^6.0.1`.
# @babel/core 8: the same package peers `@babel/core: ^7.22.9`, and
# preset-env 8 requires core 8, so the pair moves together
# or not at all.
#
# Lift each the moment its blocker ships support. These are compatibility
# limits, not security ones: `npm audit` reports no advisory against any of
# the versions pinned here.
ignore:
- dependency-name: "typescript"
update-types: ["version-update:semver-major"]
- dependency-name: "webpack-cli"
update-types: ["version-update:semver-major"]
- dependency-name: "@babel/core"
update-types: ["version-update:semver-major"]
- dependency-name: "@babel/preset-env"
update-types: ["version-update:semver-major"]
cooldown:
default-days: 1
include:
Expand Down
2 changes: 1 addition & 1 deletion appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Gratis en open source onder de EUPL-1.2 licentie.
which quoted the grep command in order to WARN about the problem —
made it read ")[^". The warning reproduced the bug it described.
-->
<version>1.1.3-unstable.20260830084017</version>
<version>1.1.7-unstable.20260830174749</version>
<licence>EUPL-1.2</licence>
<author mail="info@conduction.nl" homepage="https://www.conduction.nl/">Conduction</author>
<namespace>Thematiq</namespace>
Expand Down
550 changes: 233 additions & 317 deletions docs/package-lock.json

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/be.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/bg.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/bs.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/ca.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/cs.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/da.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/de.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/el.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/en.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/es.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/et.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/fi.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/fr.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/ga.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/hr.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/hu.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/is.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/it.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/lb.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/lt.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/lv.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/mk.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/mt.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/nb.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/nl.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/pl.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/pt.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/rm.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/ro.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/ru.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/sk.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/sl.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/sq.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/sr.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/sv.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/tr.js

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions l10n/uk.js

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@
"check:manifest": "node tests/validate-manifest.js",
"format": "prettier --check \"**/*.{js,ts,vue,css,scss}\"",
"format:fix": "prettier --write \"**/*.{js,ts,vue,css,scss}\"",
"prepare": "git config core.hooksPath .githooks || true"
"prepare": "git config core.hooksPath .githooks || true",
"l10n:build": "node scripts/build-l10n-js.js",
"check:l10n-js": "node scripts/build-l10n-js.js --check"
},
"dependencies": {
"@fontsource/fira-sans": "^5.0.0"
Expand Down
172 changes: 172 additions & 0 deletions scripts/build-l10n-js.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
#!/usr/bin/env node
// SPDX-License-Identifier: EUPL-1.2
// Copyright (C) 2026 Conduction B.V.
//
// build-l10n-js.js — regenerate l10n/<locale>.js from l10n/<locale>.json.
//
// WHY THIS EXISTS
//
// Nextcloud loads a locale catalogue in TWO formats and neither substitutes
// for the other:
//
// l10n/<locale>.json — read server-side by PHP `$l->t()`.
// l10n/<locale>.js — an `OC.L10N.register(<appId>, {…}, <pluralForm>)`
// call, the ONLY thing the browser ever sees. Raw
// JSON is not served from an app directory at all:
// `/custom_apps/humaniq/l10n/nl.json` is a 404.
//
// The pair was hand-maintained, which is exactly the shape of drift the
// parity guard exists to catch: a key added to the .json and forgotten in
// the .js renders in English for every browser while every server-rendered
// string is Dutch, and nothing throws. Deriving the .js removes the chance
// to forget.
//
// `npm run check:l10n` still asserts the two carry identical pairs — this
// script makes that assertion cheap to satisfy rather than replacing it.
//
// EVERY locale in l10n/ is generated, not just en/nl. larpinq ships 37 locale
// catalogues and had .js for none of them, so 35 languages' translations were
// unreachable on top of the two this script was first written for.
//
// pluralForm: taken from the catalogue when it declares one. When it does not,
// the fallback is the two-form rule `nplurals=2; plural=(n != 1);` — which is
// what every generated catalogue in this fleet already carries, including for
// languages that genuinely have more forms (cs, pl, ru). That is a known
// simplification, not a verified per-language rule: a catalogue that starts
// using plural strings in such a language needs its real rule declared in the
// JSON, which this script will then honour.
//
// Usage:
// node scripts/build-l10n-js.js (npm run l10n:build)
// node scripts/build-l10n-js.js --check exit 1 if any .js is stale
//
// Exit codes:
// 0 — every .js written (or already current, under --check)
// 1 — a catalogue is malformed, or --check found a stale .js

'use strict'

const fs = require('fs')
const path = require('path')

const REPO_ROOT = path.resolve(__dirname, '..')

/** Fallback when a catalogue declares no pluralForm — see the header note. */
const DEFAULT_PLURAL_FORM = 'nplurals=2; plural=(n != 1);'
const L10N_DIR = path.join(REPO_ROOT, 'l10n')

/**
* The app id the browser catalogue must register under. Read from
* appinfo/info.xml rather than hard-coded: this app has already been renamed
* once (hrmq -> humaniq), and a catalogue registered under the old id is
* silently ignored by `t()` — every string falls back to its English key with
* no error anywhere.
*
* @return {string} the <id> declared in appinfo/info.xml
*/
function appId() {
const xml = fs.readFileSync(path.join(REPO_ROOT, 'appinfo', 'info.xml'), 'utf8')
const match = xml.match(/<id>([^<]+)<\/id>/)
if (match === null) {
console.error('appinfo/info.xml declares no <id>')
process.exit(1)
}
return match[1].trim()
}

/**
* Render one catalogue as the `OC.L10N.register` call the browser expects.
*
* @param {string} id - the app id to register under
* @param {object} translations - key -> translation
* @param {string} pluralForm - the catalogue's gettext plural rule
* @return {string} the .js file body
*/
function renderJs(id, translations, pluralForm) {
const body = Object.keys(translations)
.map(
(key) =>
` ${JSON.stringify(key)}: ${JSON.stringify(translations[key])}`,
)
.join(',\n')
return [
'OC.L10N.register(',
` ${JSON.stringify(id)},`,
' {',
body,
' },',
` ${JSON.stringify(pluralForm)}`,
')',
'',
].join('\n')
}

function main() {
const check = process.argv.includes('--check')
const id = appId()
const stale = []

const locales = fs
.readdirSync(L10N_DIR)
// Dotfiles are never locale catalogues. `l10n/.schema-l10n-baseline.json`
// sits here so prettier ignores it, and without this guard it was read as
// a locale named `.schema-l10n-baseline` and failed for having no
// `translations` key.
.filter((f) => f.endsWith('.json') && !f.startsWith('.'))
.map((f) => f.slice(0, -5))
.sort()
if (locales.length === 0) {
console.error('l10n/ holds no <locale>.json catalogue to generate from')
process.exit(1)
}

for (const locale of locales) {
const jsonFile = path.join(L10N_DIR, `${locale}.json`)
const jsFile = path.join(L10N_DIR, `${locale}.js`)

let doc
try {
doc = JSON.parse(fs.readFileSync(jsonFile, 'utf8'))
} catch (error) {
console.error(`l10n/${locale}.json does not parse: ${error.message}`)
process.exit(1)
}
if (doc.translations === undefined) {
console.error(`l10n/${locale}.json is missing "translations"`)
process.exit(1)
}

const rendered = renderJs(
id,
doc.translations,
doc.pluralForm || DEFAULT_PLURAL_FORM,
)
const current = fs.existsSync(jsFile)
? fs.readFileSync(jsFile, 'utf8')
: null

if (current === rendered) {
console.log(
` ✓ l10n/${locale}.js up to date (${Object.keys(doc.translations).length} keys)`,
)
continue
}
if (check) {
stale.push(`l10n/${locale}.js`)
continue
}
fs.writeFileSync(jsFile, rendered)
console.log(
` ✎ l10n/${locale}.js written (${Object.keys(doc.translations).length} keys)`,
)
}

if (stale.length > 0) {
console.error('')
console.error(`Stale browser catalogue: ${stale.join(', ')}`)
console.error('Run `npm run l10n:build` and commit the result.')
process.exit(1)
}
}

main()
Loading