This document maps how the DSpace Angular frontend is composed, identifies the
Czech/LINDAT-specific parts inherited from the upstream ufal/dspace-angular fork,
and tracks what has already been adapted for repository.clarin.dk.
Run from the project root — this is required so Docker Compose picks up .env
(which sets INSTANCE, ports, and the subnet). Running from docker/ loses the env
file and causes wrong ports and subnet conflicts.
docker compose --env-file .env -p d7 \
-f docker/docker-compose-rest.yml \
-f docker/docker-compose.dev.yml \
up -dStarts dspace0 (REST/Tomcat on :8080), dspacedb0 (PostgreSQL), dspacesolr0 (Solr).
npm install --legacy-peer-deps--legacy-peer-deps is required due to a peer-dep conflict with @kolkov/ngx-gallery.
NODE_ENV=development npx ts-node --project ./tsconfig.ts-node.json scripts/serve.tsThe port is read from appConfig.ui.port (default 4000 in default-app-config.ts),
but if config.yml has no ui: section the value may not resolve correctly and
Angular will fall back to its own default of 4200. Check the startup log — it
prints the actual URL the server is listening on.
If yarn is in your PATH you can use yarn start:dev instead — it wraps the same
scripts/serve.ts in nodemon so the server process also restarts when config.yml
changes.
- Theme:
config/config.ymlcontainsthemes: [{name: custom}]which activates the CLARIN-DK theme. This is committed — do not remove it. - Config changes: Edits to
config/config.ymlare not hot-reloaded; restart the dev server to pick them up. yarn.lock: Do not commit changes toyarn.lockgenerated bynpm install. If you need to add a dependency useyarn addto keep the lockfile consistent.
The custom theme uses three SCSS files that each serve a distinct purpose. The import
order in theme.scss is what determines which file belongs where — not preference.
| File | What goes here | Why |
|---|---|---|
_theme_sass_variable_overrides.scss |
Bootstrap/DSpace SCSS compile-time variables and theme SCSS palette variables ($flexoki-*, $body-color, etc.) |
Imported before Bootstrap; variables in scope for all files imported later |
_theme_css_variable_overrides.scss |
CSS custom properties that must be accessible at runtime via var() — currently --bs-*, --ds-*, and --lt-clarin-* |
Imported after Bootstrap; values derived from SCSS vars via #{} interpolation |
_global-styles.scss |
Component overrides for upstream/base components, selectors, !important fixes |
Imported last; uses $flexoki-* and $dk-* SCSS variables directly |
$flexoki-* SCSS variables are the single source of truth for the palette — hex
values live only in _theme_sass_variable_overrides.scss. There are no --flexoki-*
CSS custom properties; the theme is static and does not need runtime colour access.
The --lt-clarin-* CSS custom properties in _theme_css_variable_overrides.scss are
derived from $flexoki-* via #{} interpolation and are CSS custom properties because
_clarin-styles.scss references them via var(). Do not add --flexoki-* custom
properties — if a new component needs a Flexoki colour, use the SCSS variable directly.
The $flexoki-cycle SCSS map in _global-styles.scss uses the SCSS variables to cycle
through eight Flexoki colours for cards, badges, and skeleton loaders.
Some Bootstrap SCSS variables feed into many derived values at compile time. $body-color
is the main example — Bootstrap derives input colours, component colours, and other values
from it. For these, the value lives in two places:
$body-colorin_theme_sass_variable_overrides.scss— controls Bootstrap's compiled output--bs-body-colorin_theme_css_variable_overrides.scss— the runtime CSS variable read by components
Both have a comment pointing to each other. When you change one, change the other.
Components we own (anything under src/themes/custom/app/) — put styles in the
component's own .scss file. Two patterns depending on whether you need base styles too:
- Replace the base SCSS entirely — point
styleUrlsonly at the custom file. Write all styles from scratch (seesearch-navbar.component.scss). No conflicts, no!important. - Extend the base SCSS — include the base file first, then the custom file:
styleUrls: ['../../../../app/foo/foo.component.scss', './foo.component.scss']. If the base defines styles inside:host {}, those compile to[_nghost][_ngcontent]giving specificity(0,3,0), and cascade order across twostyleUrlsentries is not guaranteed. Use!importantin the custom file and add a comment explaining why (seehome-page.component.scss).
Upstream and base-theme components — override from _global-styles.scss. Angular
scopes component styles with [_ngcontent-xxx] giving specificity (0,2,0). Beat it
with a selector at (0,2,1) or higher:
ds-some-component .card .target-class { ... } // (0,2,1) — beats (0,2,0)If the component uses !important internally (common in the upstream CLARIN styles),
match it with !important in _global-styles.scss and add a comment explaining why.
Components resolve in a three-tier cascade at runtime:
src/themes/custom/ → src/themes/dspace/ → src/app/ (base)
(highest priority) (fallback)
Each themed component uses a ThemedComponent<T> wrapper (e.g.
themed-footer.component.ts) that dynamically imports the component from the
highest-priority theme directory that provides it. If a theme doesn't have an
override, the next tier is tried, falling back to the base src/app/ version.
The three CSS bundles (base-theme, dspace-theme, custom-theme) load in that
order; later bundles override earlier ones via normal CSS cascade.
Note: src/themes/eager-themes.module.ts currently has the custom eager theme
module commented out. This means custom-theme components lazy-load on first access
rather than being pre-loaded, which can cause a brief flicker. Consider uncommenting
when the custom theme is stable.
There are no per-language template variants anywhere in the codebase. The entire
app uses a single set of templates. Translatable strings use the | translate pipe
(e.g. {{'navbar.repository' | translate}}); everything else is hardcoded directly
into the template HTML. This means two distinct and independent kinds of work when
adapting for a new institution or language:
- Translation file work — editing
.json5files. Affects all the generic DSpace surfaces: forms, labels, menus, search, item pages, admin panels, and theclarin-*modules. No code changes needed; the pipe does the rest. - Template/component work — editing
.htmland.tsfiles. Required for anything hardcoded: branding, institution links, images, routing logic. This is independent of translation files.
Two files are loaded per language at runtime and merged — overlay keys win over base keys:
src/assets/i18n/{lang}.json5— the base catalogue. Webpack converts it toassets/i18n/{lang}.jsonat build time.en.json5is the upstream DSpace file — do not add CLARIN-DK keys to it.da.json5was created by us (no upstream equivalent exists) and serves as the Danish fallback baseline; it should contain only generic DSpace translations, not CLARIN-DK overrides.src/themes/custom/assets/i18n/{lang}.json5— CLARIN-DK overrides only. Webpack converts this toassets/custom/i18n/{lang}.jsonat build time. All CLARIN-DK additions and changes belong here.
The merge happens at runtime inside TranslateBrowserLoader (two parallel HTTP
fetches + Object.assign) and TranslateServerLoader (two readFileSync calls +
Object.assign). In SSR mode the server merges once and stores the result in
TransferState; the browser reads it from there with no extra HTTP request.
There is no build-time merge step and no risk of accidentally committing merged base
files. git status stays clean.
A small set of pages (about, license terms) are served as pre-rendered static HTML
rather than Angular components. These live in src/static-files/, with a
language-specific subdirectory for each non-default locale:
src/static-files/ ← English defaults
src/static-files/cs/ ← Czech overrides (about.html, license-*.html)
HtmlContentService.getHtmlContentByPathAndLocale() selects the right file based
on the active language, falling back to the English default. If Danish static pages
are needed in future, a src/static-files/da/ directory following the same pattern
is the mechanism.
Each entry notes what is controlled by translation keys and what is hardcoded, so
it is clear whether a given change needs a .json5 edit, a template edit, or both.
| File | What it is | i18n'd | Hardcoded | Status |
|---|---|---|---|---|
src/themes/custom/app/header/header.component.html |
Main top nav bar | Nav description (aria labels) | CLARIN.dk brand label, DSpace logo image, placeholder for main nav links | Done |
src/themes/custom/app/shared/lang-switch/lang-switch.component.ts |
Language switcher | Flag alt text, aria labels | Flag code mapping (en→gb, da→dk) |
Done |
src/themes/custom/app/shared/auth-nav-menu/auth-nav-menu.component.ts |
Login/logout | Login and logout labels | CLARIN Discovery Service URL construction | Done |
Dead code in base component (src/app/header/header.component.ts): getLangCodeIfCzech()
and translateSlug() are inherited by the custom HeaderComponent but never called —
the custom template does not use them. They can be removed from the base file when
convenient.
| File | What it is | i18n'd | Hardcoded | Status |
|---|---|---|---|---|
src/themes/custom/app/footer/footer.component.html |
Full footer | All section headings and link labels | CLARIN ERIC and NorS hrefs; info.clarin.dk hrefs; GitHub issues link | Done |
| File | What it is | i18n'd | Hardcoded | Status |
|---|---|---|---|---|
src/themes/custom/app/home-page/home-page.component.html |
Hero section and page layout | Section headings and search labels | None — logos and LINDAT-specific content removed | Done |
Menu items for the public navigation are registered in src/app/menu.resolver.ts:
- Communities & Collections — hidden (
visible: false); single-collection setup makes it redundant. - Browse — visible; sections come from the DSpace browse configuration.
- CLARIN.dk — external link to
https://clarin.dkadded as a top-level nav item. - Statistics — removed from the home page route (
home-page-routing.module.ts); thesite_statsentry inmenu.resolver.tshandles this without auth-gating.
The DiscoJuice/AAI inline popup has been removed and replaced with a redirect
to the CLARIN Discovery Service. Login is handled
entirely by AuthNavMenuComponent.redirectToDiscovery() in
src/themes/custom/app/shared/auth-nav-menu/auth-nav-menu.component.ts.
See SHIBBOLETH.md for the full flow.
Generic CLARIN infrastructure: license management, bitstream authorization,
citation, Matomo analytics, etc. These are fully i18n-aware — all user-facing
strings use translation keys, and TypeScript code uses TranslateService for dynamic
messages (notifications, errors). Danish is now active and these modules work
automatically with da.json5.
One remaining exception: clarin-license-info.component.html contains an
isCsLocale() check that switches CSS layout classes. See
Czech/LINDAT Remnants.
The custom header template is now in place. The following functions in
src/app/header/header.component.ts are inherited but never called:
getLangCodeIfCzech()— returns'cs'or empty string, used to build LINDAT URLs.translateSlug()— maps English nav slugs to Czech equivalents.
isCsLocale()insrc/app/item-page/clarin-license-info/clarin-license-info.component.ts— togglesd-inline-flexon the divs wrapping a license label. This may be a genuine layout fix for how long translated strings wrap rather than anything Czech-specific. Check whether the layout issue reproduces in other languages before removing the check.
src/static-files/cs/— Czech versions of license and about pages. Inert while Czech is disabled in prod config. Leave in place; if Danish static pages are needed later, add asrc/static-files/da/directory following the same pattern.
The language list is defined in src/config/default-app-config.ts (all 25 languages
active by default) and overridden at runtime by docker/config.prod.yml, which
currently activates English and Danish:
languages:
- code: en
label: English
active: true
- code: da
label: Dansk
active: true
- code: cs
label: Ceština
active: falsesrc/assets/i18n/da.json5 exists as the Danish baseline (English values throughout —
no upstream Danish translation exists for DSpace 7). CLARIN-DK-specific Danish
translations (header nav, footer sections and links, home hero) live in the overlay at
src/themes/custom/assets/i18n/da.json5 and are merged in at runtime. The remaining
~6,200 DSpace interface keys fall back to English until translated.
Already configured for CLARIN-DK:
- Host/URLs point to
repository.clarin.dk - SSL on port 443
- REST namespace
/server - English and Danish active; Czech disabled
| Area | Status | Notes |
|---|---|---|
| Production URLs & ports | ✓ Done | |
| Language config (disable cs, enable da) | ✓ Done | |
| AAI / Shibboleth login | ✓ Done | Replaced DiscoJuice with CLARIN Discovery Service redirect |
Auth plugin sequence (local.cfg) |
✓ Done | |
| Header / top nav | ✓ Done | Custom template with CLARIN.dk branding |
| Footer | ✓ Done | Three-column layout; all text i18n'd |
| Home page hero | ✓ Done | Stripped LINDAT/UFAL content |
| Language switcher | ✓ Done | Flag mapping (en→gb, da→dk); custom template |
Danish translations (da.json5) |
✓ Done | Baseline file created; CLARIN-DK keys translated in overlay |
| i18n runtime merge | ✓ Done | TranslateBrowserLoader + TranslateServerLoader merge base + overlay |
| CSS colour palette | ✓ Done | $flexoki-* SCSS vars as single source of truth; no duplicated hex |
getLangCodeIfCzech() / translateSlug() |
TODO | Remove dead code from src/app/header/header.component.ts |
isCsLocale() layout check |
TODO | Investigate in clarin-license-info before removing |
| Danish static pages | TODO | Create static-files/da/ if needed |
| Eager theme module | TODO | Uncomment custom theme in eager-themes.module.ts when stable |