diff --git a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/core/services/basemap.service.spec.ts b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/core/services/basemap.service.spec.ts new file mode 100644 index 00000000..10db00cb --- /dev/null +++ b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/core/services/basemap.service.spec.ts @@ -0,0 +1,98 @@ +import { signal } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; + +import { BasemapService } from './basemap.service'; +import { SettingsService } from './settings.service'; + +describe('BasemapService', () => { + let service: BasemapService; + const siteSettings = signal>({}); + + beforeEach(() => { + TestBed.resetTestingModule(); + TestBed.configureTestingModule({ + providers: [ + { + provide: SettingsService, + useValue: { siteSettings }, + }, + ], + }); + service = TestBed.inject(BasemapService); + siteSettings.set({}); + }); + + describe('key substitution', () => { + it('substitutes the configured key into the default CARTO template', () => { + siteSettings.set({ basemap_key: 'abc123' }); + expect(service.tileUrl()).toBe('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png?key=abc123'); + }); + + it('URL-encodes a key containing reserved characters', () => { + siteSettings.set({ basemap_key: 'a+b/c=d&e' }); + expect(service.tileUrl()).toContain('key=a%2Bb%2Fc%3Dd%26e'); + }); + + it('trims surrounding whitespace, which is what a paste into an admin field leaves behind', () => { + siteSettings.set({ basemap_key: ' abc123\n' }); + expect(service.tileUrl()).toContain('key=abc123'); + }); + + it('leaves no {key} placeholder behind when no key is set', () => { + // Leaflet's template helper throws on a placeholder it has no value for, so an unsubstituted + // {key} would break the map outright rather than merely watermark it. + expect(service.tileUrl()).not.toContain('{key}'); + }); + + it('leaves Leaflet its own placeholders', () => { + siteSettings.set({ basemap_key: 'abc123' }); + const url = service.tileUrl(); + for (const placeholder of ['{s}', '{z}', '{x}', '{y}', '{r}']) { + expect(url).toContain(placeholder); + } + }); + }); + + describe('missingKey', () => { + it('is true when the template wants a key and none is configured', () => { + expect(service.missingKey()).toBe(true); + }); + + it('is false once a key is set', () => { + siteSettings.set({ basemap_key: 'abc123' }); + expect(service.missingKey()).toBe(false); + }); + + it('is false for a keyless provider, which never wanted a key', () => { + siteSettings.set({ basemap_url: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png' }); + expect(service.missingKey()).toBe(false); + }); + + it('is true for a whitespace-only key, which is the same as none', () => { + siteSettings.set({ basemap_key: ' ' }); + expect(service.missingKey()).toBe(true); + }); + }); + + describe('overrides', () => { + it('uses a configured tile URL in place of the CARTO default', () => { + siteSettings.set({ basemap_key: 'k', basemap_url: 'https://tiles.example/{z}/{x}/{y}.png?token={key}' }); + expect(service.tileUrl()).toBe('https://tiles.example/{z}/{x}/{y}.png?token=k'); + }); + + it('applies the configured attribution to the layer', () => { + siteSettings.set({ basemap_attribution: '© Example' }); + expect(service.createLayer().options.attribution).toBe('© Example'); + }); + + it('defaults to CARTO and OSM attribution, which three call sites were omitting entirely', () => { + expect(service.createLayer().options.attribution).toContain('carto.com'); + expect(service.createLayer().options.attribution).toContain('openstreetmap.org'); + }); + + it('honours a maxZoom override so the overview map keeps its cap of 18', () => { + expect(service.createLayer({ maxZoom: 18 }).options.maxZoom).toBe(18); + expect(service.createLayer().options.maxZoom).toBe(19); + }); + }); +}); diff --git a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/core/services/basemap.service.ts b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/core/services/basemap.service.ts new file mode 100644 index 00000000..d5c89009 --- /dev/null +++ b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/core/services/basemap.service.ts @@ -0,0 +1,55 @@ +import { Injectable, computed, inject } from '@angular/core'; +import * as L from 'leaflet'; + +import { SettingsService } from './settings.service'; + +/** + * CARTO's basemaps now require a key. An unkeyed request still answers 200 and still returns usable + * tiles, so the only signal is an "API KEY REQUIRED" watermark drawn into the image itself: nothing + * logs, no health check notices, and the maps look broken only to whoever is looking at them. See #842. + * + * `{key}` is substituted here rather than handed to Leaflet, because Leaflet's template helper throws + * on a placeholder it has no value for, and because the encoding is ours to get right. + */ +const DEFAULT_URL = 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png?key={key}'; + +const DEFAULT_ATTRIBUTION = + '© CARTO © OSM'; + +@Injectable({ providedIn: 'root' }) +export class BasemapService { + private readonly settings = inject(SettingsService); + + private readonly attribution = computed(() => this.settings.siteSettings()['basemap_attribution'] || DEFAULT_ATTRIBUTION); + + private readonly key = computed(() => (this.settings.siteSettings()['basemap_key'] || '').trim()); + + private readonly urlTemplate = computed(() => this.settings.siteSettings()['basemap_url'] || DEFAULT_URL); + + /** + * True when the configured tile URL asks for a key and no key is set. A caller should say so out + * loud: the tiles will render, watermarked, and look like a styling bug rather than a missing + * setting. An operator who has pointed `basemap_url` at a keyless provider gets false, not a + * warning about a key their URL never wanted. + */ + readonly missingKey = computed(() => this.urlTemplate().includes('{key}') && this.key() === ''); + + /** + * The single tile layer every map on the site uses. + * + * `maxZoom` is overridable because the five call sites this replaced had already drifted apart, and + * one of them caps at 18. Three carried no attribution at all, which centralising fixes on its own. + */ + createLayer(options?: { maxZoom?: number }): L.TileLayer { + return L.tileLayer(this.tileUrl(), { + attribution: this.attribution(), + maxZoom: options?.maxZoom ?? 19, + subdomains: 'abcd', + }); + } + + /** Exposed for tests and for anything that needs the URL without a Leaflet map to attach it to. */ + tileUrl(): string { + return this.urlTemplate().replace('{key}', encodeURIComponent(this.key())); + } +} diff --git a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/modules/admin/admin-settings.component.html b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/modules/admin/admin-settings.component.html index 358ec7ab..54e6d0e3 100644 --- a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/modules/admin/admin-settings.component.html +++ b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/modules/admin/admin-settings.component.html @@ -292,6 +292,13 @@

{{ 'ADMIN.SETTINGS_TITLE' | translate }}

} + @if (group.labelKey === 'ADMIN_SETTINGS.GROUP_MAPS' && basemap.missingKey()) { +
+ info_outline + {{ 'ADMIN_SETTINGS.BASEMAP_KEY_MISSING' | translate }} +
+ } + @if (group.labelKey === 'ADMIN_SETTINGS.GROUP_TELEGRAM' && telegramConfig()) { @if (!telegramConfig()!.enabled) {
diff --git a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/modules/admin/admin-settings.component.ts b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/modules/admin/admin-settings.component.ts index bfcb7666..d29aba9c 100644 --- a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/modules/admin/admin-settings.component.ts +++ b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/modules/admin/admin-settings.component.ts @@ -26,6 +26,7 @@ import { MatTooltipModule } from '@angular/material/tooltip'; import { TranslatePipe } from '@ngx-translate/core'; import { DiscordServerConfig, OidcServerConfig, PwebSetting, SiteSetting, TelegramServerConfig } from '../../core/models'; +import { BasemapService } from '../../core/services/basemap.service'; import { I18nService } from '../../core/services/i18n.service'; import { SettingsService } from '../../core/services/settings.service'; import { ConfirmDialogComponent, ConfirmDialogData } from '../../shared/components/confirm-dialog/confirm-dialog.component'; @@ -290,6 +291,31 @@ export const SETTING_GROUPS: SettingGroup[] = [ }, ], }, + { + color: '#2e7d32', + icon: 'map', + labelKey: 'ADMIN_SETTINGS.GROUP_MAPS', + settings: [ + { + descriptionKey: 'ADMIN_SETTINGS.BASEMAP_KEY_DESC', + key: 'basemap_key', + labelKey: 'ADMIN_SETTINGS.BASEMAP_KEY_LABEL', + type: 'text', + }, + { + descriptionKey: 'ADMIN_SETTINGS.BASEMAP_URL_DESC', + key: 'basemap_url', + labelKey: 'ADMIN_SETTINGS.BASEMAP_URL_LABEL', + type: 'text', + }, + { + descriptionKey: 'ADMIN_SETTINGS.BASEMAP_ATTRIBUTION_DESC', + key: 'basemap_attribution', + labelKey: 'ADMIN_SETTINGS.BASEMAP_ATTRIBUTION_LABEL', + type: 'text', + }, + ], + }, { color: '#f44336', icon: 'admin_panel_settings', @@ -350,7 +376,6 @@ export const SETTING_GROUPS: SettingGroup[] = [ }) export class AdminSettingsComponent implements OnInit { private static readonly COLLAPSED_STORAGE_KEY = 'poracle-admin-settings-collapsed'; - private readonly allDefinedKeys = new Set([ ...SETTING_GROUPS.flatMap(g => g.settings.map(s => s.key)), 'uicons_pkmn', @@ -371,8 +396,8 @@ export class AdminSettingsComponent implements OnInit { ]); private readonly destroyRef = inject(DestroyRef); - private readonly dialog = inject(MatDialog); + private readonly dialog = inject(MatDialog); private readonly i18n = inject(I18nService); private readonly internalPrefixes = [ @@ -430,6 +455,8 @@ export class AdminSettingsComponent implements OnInit { ].some(key => this.i18n.instant(key).toLowerCase().includes(query)); }); + protected readonly basemap = inject(BasemapService); + readonly bulkSaving = signal(false); readonly collapsedGroups = signal>(AdminSettingsComponent.loadCollapsed()); diff --git a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/modules/admin/admin-settings.groups.spec.ts b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/modules/admin/admin-settings.groups.spec.ts index ba370d41..59baf06d 100644 --- a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/modules/admin/admin-settings.groups.spec.ts +++ b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/modules/admin/admin-settings.groups.spec.ts @@ -1,3 +1,6 @@ +import * as fs from 'fs'; +import * as path from 'path'; + import { PROJECTED_KEYS, SETTING_GROUPS } from './admin-settings.component'; /** @@ -51,3 +54,25 @@ describe('PROJECTED_KEYS', () => { expect(overlap).toEqual([]); }); }); + +/** + * A group or a setting whose label key is absent from en.json renders the raw key -- "ADMIN_SETTINGS. + * GROUP_MAPS" sitting where a heading should be. Nothing else catches it: locale parity compares the + * locales against each other, so a key missing from all twelve files is consistent and passes. + */ +describe('SETTING_GROUPS translation keys', () => { + const english = JSON.parse(fs.readFileSync(path.join(__dirname, '../../../assets/i18n/en.json'), 'utf8')) as { + ADMIN_SETTINGS: Record; + }; + + const resolves = (key: string) => key.startsWith('ADMIN_SETTINGS.') && key.slice('ADMIN_SETTINGS.'.length) in english.ADMIN_SETTINGS; + + it('resolves every group label', () => { + expect(SETTING_GROUPS.map(g => g.labelKey).filter(k => !resolves(k))).toEqual([]); + }); + + it('resolves every setting label and description', () => { + const keys = SETTING_GROUPS.flatMap(g => g.settings.flatMap(s => [s.labelKey, s.descriptionKey])); + expect(keys.filter(k => !resolves(k))).toEqual([]); + }); +}); diff --git a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/modules/admin/geofence-submissions/geofence-submissions.component.ts b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/modules/admin/geofence-submissions/geofence-submissions.component.ts index dbe70703..32d205c1 100644 --- a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/modules/admin/geofence-submissions/geofence-submissions.component.ts +++ b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/modules/admin/geofence-submissions/geofence-submissions.component.ts @@ -3,14 +3,14 @@ import { AfterViewInit, ChangeDetectionStrategy, Component, + computed, DestroyRef, + effect, ElementRef, + inject, NgZone, OnDestroy, OnInit, - computed, - effect, - inject, signal, } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @@ -28,6 +28,7 @@ import { firstValueFrom } from 'rxjs'; import { GeofenceData, GeofenceRegion, UserGeofence } from '../../../core/models'; import { AdminGeofenceService } from '../../../core/services/admin-geofence.service'; import { AreaService } from '../../../core/services/area.service'; +import { BasemapService } from '../../../core/services/basemap.service'; import { I18nService } from '../../../core/services/i18n.service'; import { UserGeofenceService } from '../../../core/services/user-geofence.service'; import { ConfirmDialogComponent, ConfirmDialogData } from '../../../shared/components/confirm-dialog/confirm-dialog.component'; @@ -70,6 +71,7 @@ export interface RegionGroup { export class GeofenceSubmissionsComponent implements OnInit, AfterViewInit, OnDestroy { private readonly adminGeofenceService = inject(AdminGeofenceService); private readonly areaService = inject(AreaService); + private readonly basemap = inject(BasemapService); private readonly destroyRef = inject(DestroyRef); private readonly dialog = inject(MatDialog); private readonly elementRef = inject(ElementRef); @@ -381,9 +383,7 @@ export class GeofenceSubmissionsComponent implements OnInit, AfterViewInit, OnDe zoomControl: false, }); - L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', { - maxZoom: 19, - }).addTo(map); + this.basemap.createLayer().addTo(map); const color = GEOFENCE_STATUS_COLORS[geofence.status] || '#9e9e9e'; const polygon = L.polygon( diff --git a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/shared/components/area-map/area-map.component.ts b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/shared/components/area-map/area-map.component.ts index b4caf0db..21c89636 100644 --- a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/shared/components/area-map/area-map.component.ts +++ b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/shared/components/area-map/area-map.component.ts @@ -1,30 +1,32 @@ import { AfterViewInit, Component, + computed, + effect, ElementRef, EventEmitter, inject, Input, + input, OnChanges, OnDestroy, Output, - SimpleChanges, - ViewChild, - computed, - effect, - input, output, signal, + SimpleChanges, + ViewChild, } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatTooltipModule } from '@angular/material/tooltip'; import { TranslatePipe } from '@ngx-translate/core'; import * as L from 'leaflet'; + import 'leaflet-draw'; import { INITIAL_VIEW_MAX_ZOOM, LOCATION_ONLY_ZOOM, planInitialView } from './initial-view'; import { GeofenceData } from '../../../core/models'; +import { BasemapService } from '../../../core/services/basemap.service'; import { I18nService } from '../../../core/services/i18n.service'; import { RegionOption, RegionSelectorComponent } from '../region-selector/region-selector.component'; @@ -73,6 +75,7 @@ interface RegionEntry { }) export class AreaMapComponent implements AfterViewInit, OnChanges, OnDestroy { private allBoundsRect: L.LatLngBounds | null = null; + private readonly basemap = inject(BasemapService); private customBoundsRect: L.LatLngBounds | null = null; private customGeofenceLayer: L.LayerGroup = L.layerGroup(); private drawControl: L.Control.Draw | null = null; @@ -510,11 +513,7 @@ export class AreaMapComponent implements AfterViewInit, OnChanges, OnDestroy { zoomControl: true, }).setView([37.5, -77.4], 10); - L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', { - attribution: '© CARTO © OSM', - maxZoom: 19, - subdomains: 'abcd', - }).addTo(this.map); + this.basemap.createLayer().addTo(this.map); // Once the user has touched the map, stop repositioning it. Raw DOM input events are used // rather than Leaflet's movestart/zoomstart because those fire for our own fitBounds calls too, diff --git a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/shared/components/area-overview-map/area-overview-map.component.ts b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/shared/components/area-overview-map/area-overview-map.component.ts index aab57cb4..6367a896 100644 --- a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/shared/components/area-overview-map/area-overview-map.component.ts +++ b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/shared/components/area-overview-map/area-overview-map.component.ts @@ -1,7 +1,8 @@ -import { AfterViewInit, Component, ElementRef, OnDestroy, effect, input, viewChild } from '@angular/core'; +import { AfterViewInit, Component, effect, ElementRef, inject, input, OnDestroy, viewChild } from '@angular/core'; import * as L from 'leaflet'; import { GeofenceData } from '../../../core/models'; +import { BasemapService } from '../../../core/services/basemap.service'; const AREA_COLORS = ['#43a047', '#1e88e5', '#e53935', '#fb8c00', '#8e24aa', '#00acc1', '#f4511e', '#3949ab', '#7cb342', '#d81b60']; @@ -26,6 +27,7 @@ const AREA_COLORS = ['#43a047', '#1e88e5', '#e53935', '#fb8c00', '#8e24aa', '#00 template: '
', }) export class AreaOverviewMapComponent implements AfterViewInit, OnDestroy { + private readonly basemap = inject(BasemapService); private map: L.Map | null = null; private readonly mapContainer = viewChild.required>('mapContainer'); private polygonLayer: L.LayerGroup | null = null; @@ -54,9 +56,7 @@ export class AreaOverviewMapComponent implements AfterViewInit, OnDestroy { zoomControl: false, }); - L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', { - maxZoom: 18, - }).addTo(this.map); + this.basemap.createLayer({ maxZoom: 18 }).addTo(this.map); this.polygonLayer = L.layerGroup().addTo(this.map); this.drawPolygons(); diff --git a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/shared/components/geofence-detail-dialog/geofence-detail-dialog.component.ts b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/shared/components/geofence-detail-dialog/geofence-detail-dialog.component.ts index 9a5acf3c..19402995 100644 --- a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/shared/components/geofence-detail-dialog/geofence-detail-dialog.component.ts +++ b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/shared/components/geofence-detail-dialog/geofence-detail-dialog.component.ts @@ -1,5 +1,5 @@ import { DatePipe } from '@angular/common'; -import { ChangeDetectionStrategy, Component, ElementRef, OnDestroy, ViewChild, inject } from '@angular/core'; +import { ChangeDetectionStrategy, Component, ElementRef, inject, OnDestroy, ViewChild } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatChipsModule } from '@angular/material/chips'; import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; @@ -8,6 +8,7 @@ import { TranslatePipe } from '@ngx-translate/core'; import * as L from 'leaflet'; import { GeofenceData, UserGeofence } from '../../../core/models'; +import { BasemapService } from '../../../core/services/basemap.service'; import { I18nService } from '../../../core/services/i18n.service'; import { polygonAreaSqKm } from '../../utils/geo.utils'; import { GEOFENCE_STATUS_COLORS } from '../../utils/geofence.utils'; @@ -44,6 +45,7 @@ export interface GeofenceDetailDialogData { templateUrl: './geofence-detail-dialog.component.html', }) export class GeofenceDetailDialogComponent implements OnDestroy { + private readonly basemap = inject(BasemapService); private readonly dialogRef = inject(MatDialogRef); private readonly i18n = inject(I18nService); @@ -111,11 +113,7 @@ export class GeofenceDetailDialogComponent implements OnDestroy { zoomControl: true, }).setView([0, 0], 2); - L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', { - attribution: '© CARTO © OSM', - maxZoom: 19, - subdomains: 'abcd', - }).addTo(this.map); + this.basemap.createLayer().addTo(this.map); // Draw region/area geofences from Poracle using the same color palette as area-map const refs = this.data.referenceGeofences ?? []; diff --git a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/shared/components/location-dialog/location-dialog.component.ts b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/shared/components/location-dialog/location-dialog.component.ts index a9545e9c..bbd352ad 100644 --- a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/shared/components/location-dialog/location-dialog.component.ts +++ b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/shared/components/location-dialog/location-dialog.component.ts @@ -1,4 +1,4 @@ -import { Component, inject, signal, OnInit, OnDestroy, ElementRef, viewChild, afterNextRender } from '@angular/core'; +import { afterNextRender, Component, ElementRef, inject, OnDestroy, OnInit, signal, viewChild } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatButtonModule } from '@angular/material/button'; @@ -15,6 +15,7 @@ import { Subject } from 'rxjs'; import { debounceTime, switchMap, takeUntil, filter, distinctUntilChanged } from 'rxjs/operators'; import { Location, GeocodingResult } from '../../../core/models'; +import { BasemapService } from '../../../core/services/basemap.service'; import { I18nService } from '../../../core/services/i18n.service'; import { LocationService } from '../../../core/services/location.service'; import { SettingsService } from '../../../core/services/settings.service'; @@ -45,6 +46,7 @@ export interface LocationDialogData { templateUrl: './location-dialog.component.html', }) export class LocationDialogComponent implements OnInit, OnDestroy { + private readonly basemap = inject(BasemapService); private readonly destroy$ = new Subject(); private readonly i18n = inject(I18nService); @@ -272,10 +274,7 @@ export class LocationDialogComponent implements OnInit, OnDestroy { this.map = L.map(el, { attributionControl: false, zoomControl: true }).setView([lat, lng], zoom); - L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', { - maxZoom: 19, - subdomains: 'abcd', - }).addTo(this.map); + this.basemap.createLayer().addTo(this.map); if (lat !== 0 || lng !== 0) { this.marker = L.marker([lat, lng], { icon: this.locationIcon }).addTo(this.map); diff --git a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/da.json b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/da.json index 396e79f1..566469bc 100644 --- a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/da.json +++ b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/da.json @@ -1751,7 +1751,15 @@ "AUTH_MODE_LOCAL_DESC": "Log ind direkte med Discord eller Telegram.", "AUTH_FORCE_LOCAL_ACTIVE": "Lokalt login er gennemtvunget af serverkonfigurationen.", "DISABLE_UPDATE_CHECK_LABEL": "Søg ikke efter opdateringer", - "DISABLE_UPDATE_CHECK_DESC": "Stopper siden i at spørge GitHub, om der er udgivet en nyere PoracleWeb eller Poracle. Det er den eneste forespørgsel uden for dit eget netværk, og der sendes ingen data med." + "DISABLE_UPDATE_CHECK_DESC": "Stopper siden i at spørge GitHub, om der er udgivet en nyere PoracleWeb eller Poracle. Det er den eneste forespørgsel uden for dit eget netværk, og der sendes ingen data med.", + "GROUP_MAPS": "Kort", + "BASEMAP_KEY_LABEL": "API-nøgle til baggrundskort", + "BASEMAP_KEY_DESC": "Nøgle til flisleverandøren. Uden en nøgle returnerer CARTO fliser med vandmærket ”API KEY REQUIRED”.", + "BASEMAP_URL_LABEL": "Flise-URL til baggrundskort", + "BASEMAP_URL_DESC": "URL-skabelon til fliser. Indsæt {key} der, hvor leverandøren forventer nøglen. Lad stå tom for det lyse CARTO-kort.", + "BASEMAP_ATTRIBUTION_LABEL": "Kildeangivelse for baggrundskort", + "BASEMAP_ATTRIBUTION_DESC": "Kildeangivelse vist på alle kort. Skal overholde flisleverandørens betingelser.", + "BASEMAP_KEY_MISSING": "Der er ikke angivet nogen nøgle, så alle kort på sitet vises med CARTOs vandmærke ”API KEY REQUIRED”. CARTO returnerer stadig fungerende fliser uden en nøgle, så intet andet vil melde om det." }, "GEOFENCE_DETAIL": { "NAME": "Navn", diff --git a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/de.json b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/de.json index 6b2edd96..1034b6d4 100644 --- a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/de.json +++ b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/de.json @@ -1751,7 +1751,15 @@ "AUTH_MODE_LOCAL_DESC": "Direkt mit Discord oder Telegram anmelden.", "AUTH_FORCE_LOCAL_ACTIVE": "Die lokale Anmeldung wird durch die Serverkonfiguration erzwungen.", "DISABLE_UPDATE_CHECK_LABEL": "Nicht nach Updates suchen", - "DISABLE_UPDATE_CHECK_DESC": "Verhindert, dass die Seite bei GitHub nachfragt, ob ein neueres PoracleWeb oder Poracle erschienen ist. Das ist die einzige Anfrage außerhalb deines Netzwerks; es werden keine Daten übermittelt." + "DISABLE_UPDATE_CHECK_DESC": "Verhindert, dass die Seite bei GitHub nachfragt, ob ein neueres PoracleWeb oder Poracle erschienen ist. Das ist die einzige Anfrage außerhalb deines Netzwerks; es werden keine Daten übermittelt.", + "GROUP_MAPS": "Karten", + "BASEMAP_KEY_LABEL": "Kartenhintergrund-API-Schlüssel", + "BASEMAP_KEY_DESC": "Schlüssel für den Kartenkachel-Anbieter. Ohne Schlüssel liefert CARTO Kacheln mit dem Wasserzeichen „API KEY REQUIRED“.", + "BASEMAP_URL_LABEL": "Kachel-URL des Kartenhintergrunds", + "BASEMAP_URL_DESC": "URL-Vorlage für Kacheln. {key} dort einsetzen, wo der Anbieter den Schlüssel erwartet. Leer lassen für die helle CARTO-Karte.", + "BASEMAP_ATTRIBUTION_LABEL": "Kartenhintergrund-Quellenangabe", + "BASEMAP_ATTRIBUTION_DESC": "Quellenangabe auf jeder Karte. Muss den Bedingungen des Kachel-Anbieters entsprechen.", + "BASEMAP_KEY_MISSING": "Es ist kein Schlüssel gesetzt, daher zeigt jede Karte der Website das CARTO-Wasserzeichen „API KEY REQUIRED“. CARTO liefert auch ohne Schlüssel funktionierende Kacheln, deshalb meldet dies sonst nichts." }, "GEOFENCE_DETAIL": { "NAME": "Name", diff --git a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/en.json b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/en.json index 35b7f367..e07472d3 100644 --- a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/en.json +++ b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/en.json @@ -1751,7 +1751,15 @@ "EXPAND_SECTION": "Expand section", "SUMMARY_ENABLED": "{{count}} of {{total}} enabled", "DISABLE_UPDATE_CHECK_LABEL": "Do not check for updates", - "DISABLE_UPDATE_CHECK_DESC": "Stops the site asking GitHub whether a newer PoracleWeb or Poracle has been released. This is the only request it makes outside your own network; nothing is sent with it." + "DISABLE_UPDATE_CHECK_DESC": "Stops the site asking GitHub whether a newer PoracleWeb or Poracle has been released. This is the only request it makes outside your own network; nothing is sent with it.", + "GROUP_MAPS": "Maps", + "BASEMAP_KEY_LABEL": "Basemap API Key", + "BASEMAP_KEY_DESC": "Key for the map tile provider. Without one, CARTO returns tiles watermarked “API KEY REQUIRED”.", + "BASEMAP_URL_LABEL": "Basemap Tile URL", + "BASEMAP_URL_DESC": "Tile URL template. Put {key} where the provider expects the key. Leave blank for the CARTO light basemap.", + "BASEMAP_ATTRIBUTION_LABEL": "Basemap Attribution", + "BASEMAP_ATTRIBUTION_DESC": "Attribution shown on every map. Must match your tile provider's terms.", + "BASEMAP_KEY_MISSING": "No key is set, so every map on the site renders with CARTO’s “API KEY REQUIRED” watermark. CARTO still returns working tiles without one, so nothing else will report this." }, "GEOFENCE_DETAIL": { "NAME": "Name", diff --git a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/es.json b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/es.json index 17e82b8f..d2c2e5cf 100644 --- a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/es.json +++ b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/es.json @@ -1751,7 +1751,15 @@ "AUTH_MODE_LOCAL_DESC": "Inicia sesión directamente con Discord o Telegram.", "AUTH_FORCE_LOCAL_ACTIVE": "El inicio de sesión local está forzado por la configuración del servidor.", "DISABLE_UPDATE_CHECK_LABEL": "No buscar actualizaciones", - "DISABLE_UPDATE_CHECK_DESC": "Impide que el sitio pregunte a GitHub si hay una versión más reciente de PoracleWeb o Poracle. Es la única petición que sale de tu red y no envía ningún dato." + "DISABLE_UPDATE_CHECK_DESC": "Impide que el sitio pregunte a GitHub si hay una versión más reciente de PoracleWeb o Poracle. Es la única petición que sale de tu red y no envía ningún dato.", + "GROUP_MAPS": "Mapas", + "BASEMAP_KEY_LABEL": "Clave API del mapa base", + "BASEMAP_KEY_DESC": "Clave del proveedor de teselas. Sin ella, CARTO devuelve teselas con la marca de agua “API KEY REQUIRED”.", + "BASEMAP_URL_LABEL": "URL de teselas del mapa base", + "BASEMAP_URL_DESC": "Plantilla de URL de teselas. Coloca {key} donde el proveedor espera la clave. Déjalo vacío para el mapa base claro de CARTO.", + "BASEMAP_ATTRIBUTION_LABEL": "Atribución del mapa base", + "BASEMAP_ATTRIBUTION_DESC": "Atribución mostrada en cada mapa. Debe cumplir los términos de tu proveedor de teselas.", + "BASEMAP_KEY_MISSING": "No hay ninguna clave configurada, así que todos los mapas del sitio muestran la marca de agua “API KEY REQUIRED” de CARTO. CARTO sigue devolviendo teselas funcionales sin ella, por lo que nada más lo advertirá." }, "GEOFENCE_DETAIL": { "NAME": "Nombre", diff --git a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/fr.json b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/fr.json index e1ce713c..3ea61b91 100644 --- a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/fr.json +++ b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/fr.json @@ -1751,7 +1751,15 @@ "AUTH_MODE_LOCAL_DESC": "Connexion directe avec Discord ou Telegram.", "AUTH_FORCE_LOCAL_ACTIVE": "La connexion locale est imposée par la configuration du serveur.", "DISABLE_UPDATE_CHECK_LABEL": "Ne pas rechercher de mises à jour", - "DISABLE_UPDATE_CHECK_DESC": "Empêche le site de demander à GitHub si une version plus récente de PoracleWeb ou de Poracle est parue. C’est la seule requête sortant de votre réseau, et elle n’envoie rien." + "DISABLE_UPDATE_CHECK_DESC": "Empêche le site de demander à GitHub si une version plus récente de PoracleWeb ou de Poracle est parue. C’est la seule requête sortant de votre réseau, et elle n’envoie rien.", + "GROUP_MAPS": "Cartes", + "BASEMAP_KEY_LABEL": "Clé API du fond de carte", + "BASEMAP_KEY_DESC": "Clé du fournisseur de tuiles. Sans clé, CARTO renvoie des tuiles filigranées « API KEY REQUIRED ».", + "BASEMAP_URL_LABEL": "URL des tuiles du fond de carte", + "BASEMAP_URL_DESC": "Modèle d’URL des tuiles. Placez {key} là où le fournisseur attend la clé. Laissez vide pour le fond CARTO clair.", + "BASEMAP_ATTRIBUTION_LABEL": "Attribution du fond de carte", + "BASEMAP_ATTRIBUTION_DESC": "Attribution affichée sur chaque carte. Doit respecter les conditions de votre fournisseur de tuiles.", + "BASEMAP_KEY_MISSING": "Aucune clé n’est définie, donc toutes les cartes du site affichent le filigrane « API KEY REQUIRED » de CARTO. CARTO renvoie quand même des tuiles fonctionnelles, donc rien d’autre ne le signalera." }, "GEOFENCE_DETAIL": { "NAME": "Nom", diff --git a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/it.json b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/it.json index 1c01a63e..ba3eee1f 100644 --- a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/it.json +++ b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/it.json @@ -1751,7 +1751,15 @@ "AUTH_MODE_LOCAL_DESC": "Accedi direttamente con Discord o Telegram.", "AUTH_FORCE_LOCAL_ACTIVE": "L'accesso locale è imposto dalla configurazione del server.", "DISABLE_UPDATE_CHECK_LABEL": "Non cercare aggiornamenti", - "DISABLE_UPDATE_CHECK_DESC": "Impedisce al sito di chiedere a GitHub se è uscita una versione più recente di PoracleWeb o Poracle. È l’unica richiesta che esce dalla tua rete e non invia nulla." + "DISABLE_UPDATE_CHECK_DESC": "Impedisce al sito di chiedere a GitHub se è uscita una versione più recente di PoracleWeb o Poracle. È l’unica richiesta che esce dalla tua rete e non invia nulla.", + "GROUP_MAPS": "Mappe", + "BASEMAP_KEY_LABEL": "Chiave API della mappa base", + "BASEMAP_KEY_DESC": "Chiave del fornitore di tile. Senza chiave, CARTO restituisce tile con la filigrana “API KEY REQUIRED”.", + "BASEMAP_URL_LABEL": "URL delle tile della mappa base", + "BASEMAP_URL_DESC": "Modello di URL delle tile. Inserisci {key} dove il fornitore si aspetta la chiave. Lascia vuoto per la mappa base chiara di CARTO.", + "BASEMAP_ATTRIBUTION_LABEL": "Attribuzione della mappa base", + "BASEMAP_ATTRIBUTION_DESC": "Attribuzione mostrata su ogni mappa. Deve rispettare i termini del fornitore di tile.", + "BASEMAP_KEY_MISSING": "Nessuna chiave impostata, quindi ogni mappa del sito mostra la filigrana “API KEY REQUIRED” di CARTO. CARTO restituisce comunque tile funzionanti, quindi nient’altro lo segnalerà." }, "GEOFENCE_DETAIL": { "NAME": "Nome", diff --git a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/nl.json b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/nl.json index 263fb70d..a24bb1fe 100644 --- a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/nl.json +++ b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/nl.json @@ -1751,7 +1751,15 @@ "AUTH_MODE_LOCAL_DESC": "Meld je rechtstreeks aan met Discord of Telegram.", "AUTH_FORCE_LOCAL_ACTIVE": "Lokaal aanmelden wordt afgedwongen door de serverconfiguratie.", "DISABLE_UPDATE_CHECK_LABEL": "Niet op updates controleren", - "DISABLE_UPDATE_CHECK_DESC": "Voorkomt dat de site aan GitHub vraagt of er een nieuwere PoracleWeb of Poracle is uitgebracht. Dit is het enige verzoek buiten je eigen netwerk en er wordt niets meegestuurd." + "DISABLE_UPDATE_CHECK_DESC": "Voorkomt dat de site aan GitHub vraagt of er een nieuwere PoracleWeb of Poracle is uitgebracht. Dit is het enige verzoek buiten je eigen netwerk en er wordt niets meegestuurd.", + "GROUP_MAPS": "Kaarten", + "BASEMAP_KEY_LABEL": "API-sleutel achtergrondkaart", + "BASEMAP_KEY_DESC": "Sleutel voor de tegelprovider. Zonder sleutel levert CARTO tegels met het watermerk “API KEY REQUIRED”.", + "BASEMAP_URL_LABEL": "Tegel-URL achtergrondkaart", + "BASEMAP_URL_DESC": "URL-sjabloon voor tegels. Zet {key} waar de provider de sleutel verwacht. Laat leeg voor de lichte CARTO-kaart.", + "BASEMAP_ATTRIBUTION_LABEL": "Bronvermelding achtergrondkaart", + "BASEMAP_ATTRIBUTION_DESC": "Bronvermelding op elke kaart. Moet voldoen aan de voorwaarden van je tegelprovider.", + "BASEMAP_KEY_MISSING": "Er is geen sleutel ingesteld, dus elke kaart op de site toont het CARTO-watermerk “API KEY REQUIRED”. CARTO levert ook zonder sleutel werkende tegels, dus niets anders meldt dit." }, "GEOFENCE_DETAIL": { "NAME": "Naam", diff --git a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/pl.json b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/pl.json index 867f76fe..b3437c74 100644 --- a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/pl.json +++ b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/pl.json @@ -1751,7 +1751,15 @@ "AUTH_MODE_LOCAL_DESC": "Zaloguj się bezpośrednio przez Discord lub Telegram.", "AUTH_FORCE_LOCAL_ACTIVE": "Logowanie lokalne jest wymuszone przez konfigurację serwera.", "DISABLE_UPDATE_CHECK_LABEL": "Nie sprawdzaj aktualizacji", - "DISABLE_UPDATE_CHECK_DESC": "Wyłącza pytanie GitHuba o nowsze wydanie PoracleWeb lub Poracle. To jedyne żądanie wychodzące poza twoją sieć i nie wysyła żadnych danych." + "DISABLE_UPDATE_CHECK_DESC": "Wyłącza pytanie GitHuba o nowsze wydanie PoracleWeb lub Poracle. To jedyne żądanie wychodzące poza twoją sieć i nie wysyła żadnych danych.", + "GROUP_MAPS": "Mapy", + "BASEMAP_KEY_LABEL": "Klucz API mapy podkładowej", + "BASEMAP_KEY_DESC": "Klucz dostawcy kafelków. Bez niego CARTO zwraca kafelki ze znakiem wodnym „API KEY REQUIRED”.", + "BASEMAP_URL_LABEL": "Adres URL kafelków mapy podkładowej", + "BASEMAP_URL_DESC": "Szablon adresu URL kafelków. Wstaw {key} tam, gdzie dostawca oczekuje klucza. Pozostaw puste dla jasnej mapy CARTO.", + "BASEMAP_ATTRIBUTION_LABEL": "Atrybucja mapy podkładowej", + "BASEMAP_ATTRIBUTION_DESC": "Atrybucja widoczna na każdej mapie. Musi być zgodna z warunkami dostawcy kafelków.", + "BASEMAP_KEY_MISSING": "Nie ustawiono klucza, więc każda mapa w serwisie wyświetla znak wodny CARTO „API KEY REQUIRED”. CARTO nadal zwraca działające kafelki bez klucza, więc nic innego tego nie zgłosi." }, "GEOFENCE_DETAIL": { "NAME": "Nazwa", diff --git a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/pt-BR.json b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/pt-BR.json index f4b7b1f5..a9734691 100644 --- a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/pt-BR.json +++ b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/pt-BR.json @@ -1751,7 +1751,15 @@ "AUTH_MODE_LOCAL_DESC": "Faça login diretamente com Discord ou Telegram.", "AUTH_FORCE_LOCAL_ACTIVE": "O login local é forçado pela configuração do servidor.", "DISABLE_UPDATE_CHECK_LABEL": "Não procurar atualizações", - "DISABLE_UPDATE_CHECK_DESC": "Impede o site de perguntar ao GitHub se saiu uma versão mais recente do PoracleWeb ou do Poracle. É a única requisição que sai da sua rede e não envia nada." + "DISABLE_UPDATE_CHECK_DESC": "Impede o site de perguntar ao GitHub se saiu uma versão mais recente do PoracleWeb ou do Poracle. É a única requisição que sai da sua rede e não envia nada.", + "GROUP_MAPS": "Mapas", + "BASEMAP_KEY_LABEL": "Chave de API do mapa base", + "BASEMAP_KEY_DESC": "Chave do provedor de blocos. Sem ela, o CARTO retorna blocos com a marca-d’água “API KEY REQUIRED”.", + "BASEMAP_URL_LABEL": "URL de blocos do mapa base", + "BASEMAP_URL_DESC": "Modelo de URL dos blocos. Coloque {key} onde o provedor espera a chave. Deixe em branco para o mapa base claro do CARTO.", + "BASEMAP_ATTRIBUTION_LABEL": "Atribuição do mapa base", + "BASEMAP_ATTRIBUTION_DESC": "Atribuição exibida em todos os mapas. Deve seguir os termos do seu provedor de blocos.", + "BASEMAP_KEY_MISSING": "Nenhuma chave definida, então todos os mapas do site aparecem com a marca-d’água “API KEY REQUIRED” do CARTO. O CARTO continua retornando blocos funcionais sem ela, então nada mais vai avisar sobre isso." }, "GEOFENCE_DETAIL": { "NAME": "Nome", diff --git a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/pt.json b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/pt.json index 7684514b..06cc0f15 100644 --- a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/pt.json +++ b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/pt.json @@ -1751,7 +1751,15 @@ "AUTH_MODE_LOCAL_DESC": "Inicia sessão diretamente com o Discord ou o Telegram.", "AUTH_FORCE_LOCAL_ACTIVE": "O início de sessão local é imposto pela configuração do servidor.", "DISABLE_UPDATE_CHECK_LABEL": "Não procurar atualizações", - "DISABLE_UPDATE_CHECK_DESC": "Impede o site de perguntar ao GitHub se saiu uma versão mais recente do PoracleWeb ou do Poracle. É o único pedido que sai da tua rede e não envia nada." + "DISABLE_UPDATE_CHECK_DESC": "Impede o site de perguntar ao GitHub se saiu uma versão mais recente do PoracleWeb ou do Poracle. É o único pedido que sai da tua rede e não envia nada.", + "GROUP_MAPS": "Mapas", + "BASEMAP_KEY_LABEL": "Chave de API do mapa base", + "BASEMAP_KEY_DESC": "Chave do fornecedor de mosaicos. Sem ela, o CARTO devolve mosaicos com a marca de água “API KEY REQUIRED”.", + "BASEMAP_URL_LABEL": "URL de mosaicos do mapa base", + "BASEMAP_URL_DESC": "Modelo de URL dos mosaicos. Coloque {key} onde o fornecedor espera a chave. Deixe vazio para o mapa base claro do CARTO.", + "BASEMAP_ATTRIBUTION_LABEL": "Atribuição do mapa base", + "BASEMAP_ATTRIBUTION_DESC": "Atribuição apresentada em todos os mapas. Deve cumprir os termos do seu fornecedor de mosaicos.", + "BASEMAP_KEY_MISSING": "Não está definida nenhuma chave, por isso todos os mapas do site apresentam a marca de água “API KEY REQUIRED” do CARTO. O CARTO continua a devolver mosaicos funcionais sem ela, pelo que mais nada o irá assinalar." }, "GEOFENCE_DETAIL": { "NAME": "Nome", diff --git a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/sv.json b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/sv.json index 1411d453..0b804bfd 100644 --- a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/sv.json +++ b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/assets/i18n/sv.json @@ -1751,7 +1751,15 @@ "AUTH_MODE_LOCAL_DESC": "Logga in direkt med Discord eller Telegram.", "AUTH_FORCE_LOCAL_ACTIVE": "Lokal inloggning tvingas av serverkonfigurationen.", "DISABLE_UPDATE_CHECK_LABEL": "Sök inte efter uppdateringar", - "DISABLE_UPDATE_CHECK_DESC": "Hindrar webbplatsen från att fråga GitHub om en nyare PoracleWeb eller Poracle har släppts. Det är den enda förfrågan utanför ditt eget nätverk och inget skickas med." + "DISABLE_UPDATE_CHECK_DESC": "Hindrar webbplatsen från att fråga GitHub om en nyare PoracleWeb eller Poracle har släppts. Det är den enda förfrågan utanför ditt eget nätverk och inget skickas med.", + "GROUP_MAPS": "Kartor", + "BASEMAP_KEY_LABEL": "API-nyckel för bakgrundskarta", + "BASEMAP_KEY_DESC": "Nyckel till kakelleverantören. Utan nyckel returnerar CARTO kakel med vattenstämpeln ”API KEY REQUIRED”.", + "BASEMAP_URL_LABEL": "Kakel-URL för bakgrundskarta", + "BASEMAP_URL_DESC": "URL-mall för kakel. Placera {key} där leverantören förväntar sig nyckeln. Lämna tomt för den ljusa CARTO-kartan.", + "BASEMAP_ATTRIBUTION_LABEL": "Attribution för bakgrundskarta", + "BASEMAP_ATTRIBUTION_DESC": "Attribution som visas på varje karta. Måste följa kakelleverantörens villkor.", + "BASEMAP_KEY_MISSING": "Ingen nyckel är angiven, så alla kartor på webbplatsen visas med CARTOs vattenstämpel ”API KEY REQUIRED”. CARTO returnerar fortfarande fungerande kakel utan nyckel, så inget annat kommer att rapportera det." }, "GEOFENCE_DETAIL": { "NAME": "Namn", diff --git a/CHANGELOG.md b/CHANGELOG.md index 6efe5a4d..0987e4ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- **Every map on the site was rendering with an "API KEY REQUIRED" watermark across the tiles.** CARTO now requires a key on its basemaps and watermarks requests that arrive without one -- but it still answers success and still returns usable tiles, so nothing logged, no health check noticed, and the only place it showed was on screen. The tile URL was written out in five separate components, three of which had also drifted into carrying no attribution at all, so there was nowhere to put a key without a rebuild. All five now draw their tiles from one place, and admin settings grows a *Maps* section with the key, the tile URL and the attribution line. Set the key there and the watermark goes. Leaving it blank keeps exactly the maps you have today, and an operator pointing the URL at a provider that wants no key is not nagged about one ([#842](https://github.com/PGAN-Dev/PoracleWeb.NET/issues/842)). - **The *Until* fields in the schedule editor say what they are.** Turning on a repeat adds an end time, and both its labels rendered cut off -- *Until (hou*, *Until (mir* -- because the field has to stay narrow enough for an hour and a minute to sit side by side on a phone. The qualifier has moved out of the fields and onto the pair it describes, so the row now reads *Starts at* over one hour-and-minute pair and *Until* over the other, and each field is labelled by the short word it asks for. Screen readers still hear the full wording. This is the editor quest summary schedules use as well. - **The sixth quest reward type fits.** PokéCoins arrived as a sixth tab in a strip that had already been trimmed twice to hold five, and at dialog width its label was clipped to *Pok* -- selected or not -- with the strip offering a pagination arrow rather than scrolling it into view. The words are longer still in most of the other ten languages, so the next trim would have clipped Stardust as well. The reward is now chosen from a single *Reward type* list, which fits at any width in any language and matches how the Pokemon dialog handles its own crowded first tab. Nothing about the alarms changes: the same six rewards, in the same order, saving the same rules. - **Alarm cards no longer read *Pokemon #1* on a cold load.** Open Pokemon, Raids, Nests or Max Battles as the first page of a session and every card was titled by its species number rather than its name; leaving the page and coming back fixed it, which is why it survived this long. The names arrive from Poracle a moment after the cards do, and the lists had no way of noticing: they asked for the data, painted once and never looked again. The name tables are now reactive, so the cards -- and the search and sort that read the same names -- redraw themselves the moment the names land. A Poracle that never answers leaves the number showing, exactly as it did.