diff --git a/README.md b/README.md index d3d755b..2896531 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # cookiecutter-webapp-deluxe -[![Actions Status](https://github.com/UUDigitalHumanitiesLab/cookiecutter-webapp-deluxe/workflows/Tests/badge.svg)](https://github.com/UUDigitalHumanitiesLab/cookiecutter-webapp-deluxe/actions) +[![Actions Status](https://github.com/CentreForDigitalHumanities/cookiecutter-webapp-deluxe/workflows/Tests/badge.svg)](https://github.com/CentreForDigitalHumanities/cookiecutter-webapp-deluxe/actions) A boilerplate for full-fledged web applications with [Django][1] backend, [Angular][2] frontend and [Selenium][3] functional tests. @@ -47,7 +47,7 @@ These are all the external dependencies you'll need during or after project gene ```console $ cd to/parent/directory/that/contains/all/your/projects/ -$ cookiecutter gh:UUDigitalHumanitieslab/cookiecutter-webapp-deluxe --checkout develop +$ cookiecutter gh:CentreForDigitalHumanities/cookiecutter-webapp-deluxe --checkout develop # (the plan is to change the latter command into `dh init`) ``` diff --git a/cookiecutter.json b/cookiecutter.json index b032aaa..c15d6a8 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -5,7 +5,7 @@ "description": "{{cookiecutter.project_title}} will humanize all your digits!", "author": "Research Software Lab, Centre for Digital Humanities, Utrecht University", "author_url": "https://cdh.uu.nl/centre-for-digital-humanities/research-software-lab/", - "origin": "github:UUDigitalHumanitieslab/{{cookiecutter.slug}}", + "origin": "github:CentreForDigitalHumanities/{{cookiecutter.slug}}", "database_name": "{{cookiecutter.slug}}", "database_user": "{{cookiecutter.slug}}", "database_password": "{{cookiecutter.slug}}", diff --git a/{{cookiecutter.slug}}/.nvmrc b/{{cookiecutter.slug}}/.nvmrc index eea150f..5f09eed 100644 --- a/{{cookiecutter.slug}}/.nvmrc +++ b/{{cookiecutter.slug}}/.nvmrc @@ -1 +1 @@ -v14.21.2 +v18.20.3 diff --git a/{{cookiecutter.slug}}/backend/{{cookiecutter.slug}}/i18n.py b/{{cookiecutter.slug}}/backend/{{cookiecutter.slug}}/i18n.py index eca031a..b1696e0 100644 --- a/{{cookiecutter.slug}}/backend/{{cookiecutter.slug}}/i18n.py +++ b/{{cookiecutter.slug}}/backend/{{cookiecutter.slug}}/i18n.py @@ -5,18 +5,15 @@ from django.utils import translation -@api_view(['GET']) -def get(request: Request): - return Response({ - 'current': request.LANGUAGE_CODE, - 'supported': settings.LANGUAGES - }) +@api_view(["GET", "POST"]) +def i18n(request: Request): + response = Response() + if request.method == "POST": + language = request.data["language"] + translation.activate(language) + response.set_cookie(settings.LANGUAGE_COOKIE_NAME, language) + else: + language = request.LANGUAGE_CODE - -@api_view(['POST']) -def set(request: Request): - language = request.data['language'] - translation.activate(language) - response = Response(language) - response.set_cookie(settings.LANGUAGE_COOKIE_NAME, language) + response.data = {"current": language, "supported": settings.LANGUAGES} return response diff --git a/{{cookiecutter.slug}}/backend/{{cookiecutter.slug}}/urls.py b/{{cookiecutter.slug}}/backend/{{cookiecutter.slug}}/urls.py index 38b05a9..0f85bdd 100644 --- a/{{cookiecutter.slug}}/backend/{{cookiecutter.slug}}/urls.py +++ b/{{cookiecutter.slug}}/backend/{{cookiecutter.slug}}/urls.py @@ -22,7 +22,7 @@ from .index import index from .proxy_frontend import proxy_frontend -from .i18n import get, set +from .i18n import i18n from example.views import hooray as ExampleView # DELETEME, see below @@ -45,7 +45,6 @@ 'rest_framework.urls', namespace='rest_framework', )), - path('api/i18n/get/', get), - path('api/i18n/set/', set), + path('api/i18n/', i18n), spa_url, # catch-all; unknown paths to be handled by a SPA ] diff --git a/{{cookiecutter.slug}}/frontend.angular/angular.overwrite.json b/{{cookiecutter.slug}}/frontend.angular/angular.overwrite.json index e8b054b..6b86194 100644 --- a/{{cookiecutter.slug}}/frontend.angular/angular.overwrite.json +++ b/{{cookiecutter.slug}}/frontend.angular/angular.overwrite.json @@ -1,22 +1,25 @@ +{% set localizations = cookiecutter.localizations.split(',') %} +{% set project_name = cookiecutter.slug | replace('_', '-') %} { "projects": { - "{{cookiecutter.slug | replace('_', '-')}}": { + "{{project_name}}": { "i18n": { "sourceLocale": { "code": "{{cookiecutter.default_localization}}", "baseHref": "/{{cookiecutter.default_localization}}/" }, "locales": { -{% set localizations = cookiecutter.localizations.split(',') %} -{%- for loc in localizations %} -{%- set code, name = loc.split(':') %} -{%- if cookiecutter.default_localization != code -%} + {% set comma = joiner(",") %} + {%- for loc in localizations %} + {%- set code, name = loc.split(':') %} + {%- if cookiecutter.default_localization != code -%} + {{ comma() }} "{{code}}": { "translation": "locale/messages.{{code}}.xlf", "baseHref": "/{{code}}/" } -{% endif %} -{%- endfor %} + {% endif %} + {%- endfor %} } }, "architect": { @@ -26,10 +29,11 @@ "baseHref": "/" }, "configurations": { - {% set localizations = cookiecutter.localizations.split(',') %} + {% set comma = joiner(",") %} {%- for loc in localizations %} {%- set code, name = loc.split(':') %} {%- if cookiecutter.default_localization != code -%} + {{ comma() }} "{{code}}": { "localize": [ "{{code}}" @@ -42,17 +46,37 @@ }, "serve": { "configurations": { - {% set localizations = cookiecutter.localizations.split(',') %} + {% set comma = joiner(",") %} {%- for loc in localizations %} {%- set code, name = loc.split(':') %} {%- if cookiecutter.default_localization != code -%} + {{ comma() }} "{{code}}": { - "browserTarget": "{{cookiecutter.slug}}:build:{{code}}" + "browserTarget": "{{project_name}}:build:{{code}}" } {% endif %} {%- endfor %} } }, + "extract-i18n": { + "builder": "ng-extract-i18n-merge:ng-extract-i18n-merge", + "options": { + "browserTarget": "{{project_name}}:build", + "format": "xlf", + "outputPath": "locale", + "includeContext": true, + "targetFiles": [ + {% set comma = joiner(",") %} + {%- for loc in localizations %} + {%- set code, name = loc.split(':') %} + {%- if cookiecutter.default_localization != code -%} + {{ comma() }} + "messages.{{code}}.xlf" + {% endif %} + {%- endfor %} + ] + } + }, "test": { "options": { "karmaConfig": "karma.conf.js" diff --git a/{{cookiecutter.slug}}/frontend.angular/package.overwrite.json b/{{cookiecutter.slug}}/frontend.angular/package.overwrite.json index 14cbcfc..6cff5c8 100644 --- a/{{cookiecutter.slug}}/frontend.angular/package.overwrite.json +++ b/{{cookiecutter.slug}}/frontend.angular/package.overwrite.json @@ -3,7 +3,7 @@ "scripts": { "ng": null, "prebuild": "node ./build/build-pre.js", - "build": "ng build --base-href=static/ --localize", + "build": "ng build --base-href=/static/ --localize", "i18n": "ng extract-i18n --output-path locale", "preserve": "yarn prebuild", "serve": "ng serve --proxy-config ../proxy.conf.json", @@ -20,14 +20,17 @@ "watch": "ng build --watch" }, "dependencies": { - "@fortawesome/angular-fontawesome": "^0.12.1", - "@fortawesome/fontawesome-svg-core": "^6.2.1", - "@fortawesome/free-solid-svg-icons": "^6.2.1", - "@ngrx/effects": "^15.2.1", - "@ngrx/store": "^15.2.1", + "@fortawesome/angular-fontawesome": "^0.14.1", + "@fortawesome/fontawesome-svg-core": "^6.5.2", + "@fortawesome/free-solid-svg-icons": "^6.5.2", + "@ngrx/effects": "^17.2.0", + "@ngrx/store": "^17.2.0", "bulma": "^0.9.1", "colors": "^1.4.0", - "primeicons": "^6.0.1", - "primeng": "^15.1.1" - } + "primeicons": "^7.0.0", + "primeng": "^17.18.0" + }, + "devDependencies": { + "ng-extract-i18n-merge": "^2.12.0" + } } diff --git a/{{cookiecutter.slug}}/frontend.angular/src/app/app.component.ts b/{{cookiecutter.slug}}/frontend.angular/src/app/app.component.ts new file mode 100644 index 0000000..300af47 --- /dev/null +++ b/{{cookiecutter.slug}}/frontend.angular/src/app/app.component.ts @@ -0,0 +1,15 @@ +import { Component } from '@angular/core'; +import { RouterOutlet } from '@angular/router'; +import { MenuComponent } from './menu/menu.component'; +import { FooterComponent } from './footer/footer.component'; + +@Component({ + selector: '{{cookiecutter.app_prefix}}-root', + standalone: true, + imports: [RouterOutlet, MenuComponent, FooterComponent], + templateUrl: './app.component.html', + styleUrl: './app.component.scss' +}) +export class AppComponent { + title = '{{cookiecutter.project_title}}'; +} diff --git a/{{cookiecutter.slug}}/frontend.angular/src/app/app.config.ts b/{{cookiecutter.slug}}/frontend.angular/src/app/app.config.ts new file mode 100644 index 0000000..626e524 --- /dev/null +++ b/{{cookiecutter.slug}}/frontend.angular/src/app/app.config.ts @@ -0,0 +1,20 @@ +import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; +import { provideHttpClient, withXsrfConfiguration } from '@angular/common/http'; +import { provideClientHydration } from '@angular/platform-browser'; +import { provideAnimations } from '@angular/platform-browser/animations'; +import { provideRouter } from '@angular/router'; + +import { routes } from './app.routes'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideAnimations(), + provideZoneChangeDetection({ eventCoalescing: true }), + provideRouter(routes), + provideClientHydration(), + provideHttpClient(withXsrfConfiguration({ + cookieName: 'csrftoken', + headerName: 'X-CSRFToken' + })) + ] +}; diff --git a/{{cookiecutter.slug}}/frontend.angular/src/app/app.module.ts b/{{cookiecutter.slug}}/frontend.angular/src/app/app.module.ts deleted file mode 100644 index b854584..0000000 --- a/{{cookiecutter.slug}}/frontend.angular/src/app/app.module.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { BrowserModule } from '@angular/platform-browser'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { APP_BASE_HREF } from '@angular/common'; -import { HttpClientModule, HttpClientXsrfModule } from '@angular/common/http'; -import { NgModule } from '@angular/core'; - -import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; - -import { AppComponent } from './app.component'; -import { AppRoutingModule } from './app-routing.module'; - -import { FooterComponent } from './footer/footer.component'; -import { MenuComponent } from './menu/menu.component'; -import { HomeComponent } from './home/home.component'; - -@NgModule({ - declarations: [ - AppComponent, - FooterComponent, - MenuComponent, - HomeComponent - ], - imports: [ - AppRoutingModule, - BrowserModule, - BrowserAnimationsModule, - FontAwesomeModule, - HttpClientModule, - HttpClientXsrfModule.withOptions({ - cookieName: 'csrftoken', - headerName: 'X-CSRFToken' - }) - ], - providers: [ - // The language is used as the base_path for finding the right - // static-files. For example /nl/static/main.js - // However the routing is done from a base path starting from - // the root e.g. /home - // The server should then switch index.html based on a language - // cookie with a fallback to Dutch e.g. /nl/static/index.html - { provide: APP_BASE_HREF, useValue: '/' } - ], - bootstrap: [AppComponent] -}) -export class AppModule { } diff --git a/{{cookiecutter.slug}}/frontend.angular/src/app/routes.ts b/{{cookiecutter.slug}}/frontend.angular/src/app/app.routes.ts similarity index 100% rename from {{cookiecutter.slug}}/frontend.angular/src/app/routes.ts rename to {{cookiecutter.slug}}/frontend.angular/src/app/app.routes.ts diff --git a/{{cookiecutter.slug}}/frontend.angular/src/app/footer/footer.component.ts b/{{cookiecutter.slug}}/frontend.angular/src/app/footer/footer.component.ts index f7ca829..f5f737e 100644 --- a/{{cookiecutter.slug}}/frontend.angular/src/app/footer/footer.component.ts +++ b/{{cookiecutter.slug}}/frontend.angular/src/app/footer/footer.component.ts @@ -4,7 +4,8 @@ import { environment } from '../../environments/environment'; @Component({ selector: '{{cookiecutter.app_prefix}}-footer', templateUrl: './footer.component.html', - styleUrls: ['./footer.component.scss'] + styleUrls: ['./footer.component.scss'], + standalone: true }) export class FooterComponent { environment = environment; diff --git a/{{cookiecutter.slug}}/frontend.angular/src/app/home/home.component.ts b/{{cookiecutter.slug}}/frontend.angular/src/app/home/home.component.ts index b610a03..3e614a7 100644 --- a/{{cookiecutter.slug}}/frontend.angular/src/app/home/home.component.ts +++ b/{{cookiecutter.slug}}/frontend.angular/src/app/home/home.component.ts @@ -2,9 +2,10 @@ import { Component, OnInit } from '@angular/core'; import { BackendService } from './../services/backend.service'; @Component({ - selector: '{{cookiecutter.app_prefix}}-home', - templateUrl: './home.component.html', - styleUrls: ['./home.component.scss'] + selector: '{{cookiecutter.app_prefix}}-home', + templateUrl: './home.component.html', + styleUrls: ['./home.component.scss'], + standalone: true }) export class HomeComponent implements OnInit { hooray?: string; diff --git a/{{cookiecutter.slug}}/frontend.angular/src/app/menu/menu.component.ts b/{{cookiecutter.slug}}/frontend.angular/src/app/menu/menu.component.ts index 2b9fdf6..e915c32 100644 --- a/{{cookiecutter.slug}}/frontend.angular/src/app/menu/menu.component.ts +++ b/{{cookiecutter.slug}}/frontend.angular/src/app/menu/menu.component.ts @@ -1,4 +1,7 @@ import { Component, LOCALE_ID, Inject, OnInit, NgZone } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterLink } from '@angular/router'; +import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { faGlobe, faSync } from '@fortawesome/free-solid-svg-icons'; import { animations, showState } from '../animations'; import { LanguageInfo, LanguageService } from '../services/language.service'; @@ -7,7 +10,9 @@ import { LanguageInfo, LanguageService } from '../services/language.service'; animations, selector: '{{cookiecutter.app_prefix}}-menu', templateUrl: './menu.component.html', - styleUrls: ['./menu.component.scss'] + styleUrls: ['./menu.component.scss'], + standalone: true, + imports: [CommonModule, RouterLink, FontAwesomeModule] }) export class MenuComponent implements OnInit { burgerShow: showState; diff --git a/{{cookiecutter.slug}}/frontend.angular/src/app/services/language.service.ts b/{{cookiecutter.slug}}/frontend.angular/src/app/services/language.service.ts index c03a299..7ee353d 100644 --- a/{{cookiecutter.slug}}/frontend.angular/src/app/services/language.service.ts +++ b/{{cookiecutter.slug}}/frontend.angular/src/app/services/language.service.ts @@ -23,7 +23,7 @@ export class LanguageService { const response = await lastValueFrom(this.http.get<{ current: string, supported: [string, string][] - }>(this.baseApiUrl + '/i18n/get/')); + }>(this.baseApiUrl + '/i18n/')); return { current: response.current, @@ -36,7 +36,7 @@ export class LanguageService { */ async set(language: string): Promise { const response = lastValueFrom(this.http.post( - this.baseApiUrl + '/i18n/set/', { + this.baseApiUrl + '/i18n/', { language })); diff --git a/{{cookiecutter.slug}}/frontend.angular/src/environments/environment.prod.ts b/{{cookiecutter.slug}}/frontend.angular/src/environments/environment.prod.ts index edbf56d..71b41f9 100644 --- a/{{cookiecutter.slug}}/frontend.angular/src/environments/environment.prod.ts +++ b/{{cookiecutter.slug}}/frontend.angular/src/environments/environment.prod.ts @@ -2,7 +2,7 @@ import { buildTime, version, sourceUrl } from './version'; export const environment = { production: true, - // base href is static/LANG/ + // base href is /static/LANG/ assets: 'assets', buildTime, version, diff --git a/{{cookiecutter.slug}}/frontend.angular/src/environments/version.ts b/{{cookiecutter.slug}}/frontend.angular/src/environments/version.ts index 9fb87ae..3f6bb3c 100644 --- a/{{cookiecutter.slug}}/frontend.angular/src/environments/version.ts +++ b/{{cookiecutter.slug}}/frontend.angular/src/environments/version.ts @@ -1,3 +1,3 @@ export const version = '0.0.0'; export const buildTime = 'N/A'; -export const sourceUrl = 'https://github.com/UUDigitalHumanitieslab/{{cookiecutter.slug}}'; +export const sourceUrl = 'https://github.com/CentreForDigitalHumanities/{{cookiecutter.slug}}'; diff --git a/{{cookiecutter.slug}}/package.angular.json b/{{cookiecutter.slug}}/package.angular.json index 843aeac..575aae3 100644 --- a/{{cookiecutter.slug}}/package.angular.json +++ b/{{cookiecutter.slug}}/package.angular.json @@ -38,6 +38,6 @@ "start-p": "yarn static-p && yarn watch-front-p & yarn start-back-p" }, "devDependencies": { - "@angular/cli": ">=15 <16" + "@angular/cli": ">=17 <18" } }