diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 33177f0a6..602841460 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -6,3 +6,5 @@ name: CI
 jobs:
   tests:
     uses: city-of-helsinki/drupal-gh-actions/.github/workflows/project-tests.yml@main
+    secrets:
+      sonarcloud_token: ${{ secrets.SONAR_TOKEN }}
diff --git a/.platform/schema b/.platform/schema
index da2d3988d..3f10ffe7a 100644
--- a/.platform/schema
+++ b/.platform/schema
@@ -1 +1 @@
-14
\ No newline at end of file
+15
\ No newline at end of file
diff --git a/Makefile b/Makefile
index 27392cb1a..44ee5d483 100644
--- a/Makefile
+++ b/Makefile
@@ -1,17 +1,36 @@
 PHONY :=
 PROJECT_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
 
+# Colors
+NO_COLOR=\033[0m
+CYAN=\033[36m
+GREEN=\033[0;32m
+RED=\033[0;31m
+YELLOW=\033[0;33m
+
+ENV := local
+
 # Include project env vars (if exists)
 -include .env
 -include .env.local
 
-# Include druidfi/tools config
-include $(PROJECT_DIR)/tools/make/Makefile
+define step
+	@printf "\n⭐ ${YELLOW}${1}${NO_COLOR}\n"
+endef
+
+PHONY += help
+help: ## List all make commands
+	$(call step,Available make commands:\n)
+	@cat $(MAKEFILE_LIST) | grep -e "^[a-zA-Z_\-]*: *.*## *" | awk 'BEGIN {FS = ":.*?## "}; {printf "${CYAN}%-30s${NO_COLOR} %s\n", $$1, $$2}'
 
-# Include project specific make files (if they exist)
--include $(PROJECT_DIR)/tools/make/project/*.mk
+# Allow projects to specify makefiles.
+-include tools/make/project/*.mk
 
-# Project specific overrides for variables (if they exist)
--include $(PROJECT_DIR)/tools/make/override.mk
+include tools/make/docker.mk
+include tools/make/composer.mk
+include tools/make/drupal.mk
+include tools/make/git.mk
+include tools/make/theme.mk
+include tools/make/qa.mk
 
 .PHONY: $(PHONY)
diff --git a/composer.lock b/composer.lock
index 667cd34a7..eca950a71 100644
--- a/composer.lock
+++ b/composer.lock
@@ -64,16 +64,16 @@
         },
         {
             "name": "caxy/php-htmldiff",
-            "version": "v0.1.15",
+            "version": "v0.1.16",
             "source": {
                 "type": "git",
                 "url": "https://github.com/caxy/php-htmldiff.git",
-                "reference": "6342b02ddb86fd36093ad7e2db2efc21f01ab7cd"
+                "reference": "5c580b4f09285c078f0c5cb261573412a736a8cb"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/caxy/php-htmldiff/zipball/6342b02ddb86fd36093ad7e2db2efc21f01ab7cd",
-                "reference": "6342b02ddb86fd36093ad7e2db2efc21f01ab7cd",
+                "url": "https://api.github.com/repos/caxy/php-htmldiff/zipball/5c580b4f09285c078f0c5cb261573412a736a8cb",
+                "reference": "5c580b4f09285c078f0c5cb261573412a736a8cb",
                 "shasum": ""
             },
             "require": {
@@ -119,9 +119,9 @@
             ],
             "support": {
                 "issues": "https://github.com/caxy/php-htmldiff/issues",
-                "source": "https://github.com/caxy/php-htmldiff/tree/v0.1.15"
+                "source": "https://github.com/caxy/php-htmldiff/tree/v0.1.16"
             },
-            "time": "2023-11-05T23:49:04+00:00"
+            "time": "2025-01-22T17:03:45+00:00"
         },
         {
             "name": "chi-teck/drupal-code-generator",
@@ -1957,8 +1957,8 @@
                     "version": "1.0.2",
                     "datestamp": "1695740655",
                     "security-coverage": {
-                        "status": "covered",
-                        "message": "Covered by Drupal's security advisory policy"
+                        "status": "not-covered",
+                        "message": "Project has not opted into security advisory coverage!"
                     }
                 }
             },
@@ -1967,10 +1967,6 @@
                 "GPL-2.0-or-later"
             ],
             "authors": [
-                {
-                    "name": "dczepierga",
-                    "homepage": "https://www.drupal.org/user/911466"
-                },
                 {
                     "name": "hass",
                     "homepage": "https://www.drupal.org/user/85918"
@@ -1988,13 +1984,9 @@
                     "homepage": "https://www.drupal.org/user/1078742"
                 },
                 {
-                    "name": "Magnus",
+                    "name": "magnus",
                     "homepage": "https://www.drupal.org/user/73919"
                 },
-                {
-                    "name": "mkesicki",
-                    "homepage": "https://www.drupal.org/user/922884"
-                },
                 {
                     "name": "nod_",
                     "homepage": "https://www.drupal.org/user/598310"
@@ -2008,7 +2000,7 @@
                     "homepage": "https://www.drupal.org/user/2793801"
                 },
                 {
-                    "name": "Wim Leers",
+                    "name": "wim leers",
                     "homepage": "https://www.drupal.org/user/99777"
                 },
                 {
@@ -3081,17 +3073,17 @@
         },
         {
             "name": "drupal/editoria11y",
-            "version": "2.2.0",
+            "version": "2.2.4",
             "source": {
                 "type": "git",
                 "url": "https://git.drupalcode.org/project/editoria11y.git",
-                "reference": "2.2.0"
+                "reference": "2.2.4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://ftp.drupal.org/files/projects/editoria11y-2.2.0.zip",
-                "reference": "2.2.0",
-                "shasum": "fc06c2b27be7432f4692e37c787fa935e9d4f9f7"
+                "url": "https://ftp.drupal.org/files/projects/editoria11y-2.2.4.zip",
+                "reference": "2.2.4",
+                "shasum": "6342f0614083f90088c48f8f74a231a495bf8ebe"
             },
             "require": {
                 "drupal/core": "^9 || ^10 || ^11"
@@ -3102,8 +3094,8 @@
             "type": "drupal-module",
             "extra": {
                 "drupal": {
-                    "version": "2.2.0",
-                    "datestamp": "1736189388",
+                    "version": "2.2.4",
+                    "datestamp": "1737647999",
                     "security-coverage": {
                         "status": "covered",
                         "message": "Covered by Drupal's security advisory policy"
@@ -3520,17 +3512,17 @@
         },
         {
             "name": "drupal/entity_usage",
-            "version": "2.0.0-beta16",
+            "version": "2.0.0-beta17",
             "source": {
                 "type": "git",
                 "url": "https://git.drupalcode.org/project/entity_usage.git",
-                "reference": "8.x-2.0-beta16"
+                "reference": "8.x-2.0-beta17"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://ftp.drupal.org/files/projects/entity_usage-8.x-2.0-beta16.zip",
-                "reference": "8.x-2.0-beta16",
-                "shasum": "af6533149a0926d2d539f279e7e08073f8f08c55"
+                "url": "https://ftp.drupal.org/files/projects/entity_usage-8.x-2.0-beta17.zip",
+                "reference": "8.x-2.0-beta17",
+                "shasum": "29691274f6b68b13a8d1ba295677453c85c2ed39"
             },
             "require": {
                 "drupal/core": "^10.2 || ^11"
@@ -3550,8 +3542,8 @@
             "type": "drupal-module",
             "extra": {
                 "drupal": {
-                    "version": "8.x-2.0-beta16",
-                    "datestamp": "1733149424",
+                    "version": "8.x-2.0-beta17",
+                    "datestamp": "1736865887",
                     "security-coverage": {
                         "status": "not-covered",
                         "message": "Beta releases are not covered by Drupal security advisories."
@@ -4253,16 +4245,16 @@
         },
         {
             "name": "drupal/hdbt",
-            "version": "6.8.34",
+            "version": "6.9.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/City-of-Helsinki/drupal-hdbt.git",
-                "reference": "9216d2943483e75e9f5d9ed11eba03e836c93f98"
+                "reference": "aa5d86120d9a7c605f6339a0bce8613bc84ddd65"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/City-of-Helsinki/drupal-hdbt/zipball/9216d2943483e75e9f5d9ed11eba03e836c93f98",
-                "reference": "9216d2943483e75e9f5d9ed11eba03e836c93f98",
+                "url": "https://api.github.com/repos/City-of-Helsinki/drupal-hdbt/zipball/aa5d86120d9a7c605f6339a0bce8613bc84ddd65",
+                "reference": "aa5d86120d9a7c605f6339a0bce8613bc84ddd65",
                 "shasum": ""
             },
             "require": {
@@ -4281,10 +4273,10 @@
                 "Drupal"
             ],
             "support": {
-                "source": "https://github.com/City-of-Helsinki/drupal-hdbt/tree/6.8.34",
+                "source": "https://github.com/City-of-Helsinki/drupal-hdbt/tree/6.9.4",
                 "issues": "https://github.com/City-of-Helsinki/drupal-hdbt/issues"
             },
-            "time": "2025-01-14T10:26:39+00:00"
+            "time": "2025-01-28T14:27:33+00:00"
         },
         {
             "name": "drupal/hdbt_admin",
@@ -4472,12 +4464,12 @@
             "source": {
                 "type": "git",
                 "url": "https://github.com/City-of-Helsinki/drupal-tools.git",
-                "reference": "2eb037b210a0c311ad8e742d3c1fd97e89d7b677"
+                "reference": "bc5416375195d1307adf188098d75a19a5de0224"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/City-of-Helsinki/drupal-tools/zipball/2eb037b210a0c311ad8e742d3c1fd97e89d7b677",
-                "reference": "2eb037b210a0c311ad8e742d3c1fd97e89d7b677",
+                "url": "https://api.github.com/repos/City-of-Helsinki/drupal-tools/zipball/bc5416375195d1307adf188098d75a19a5de0224",
+                "reference": "bc5416375195d1307adf188098d75a19a5de0224",
                 "shasum": ""
             },
             "require": {
@@ -4552,7 +4544,7 @@
                 "source": "https://github.com/City-of-Helsinki/drupal-tools/tree/main",
                 "issues": "https://github.com/City-of-Helsinki/drupal-tools/issues"
             },
-            "time": "2024-12-17T10:07:01+00:00"
+            "time": "2025-01-15T12:24:04+00:00"
         },
         {
             "name": "drupal/helfi_navigation",
@@ -4590,16 +4582,16 @@
         },
         {
             "name": "drupal/helfi_platform_config",
-            "version": "4.12.3",
+            "version": "4.12.12",
             "source": {
                 "type": "git",
                 "url": "https://github.com/City-of-Helsinki/drupal-helfi-platform-config.git",
-                "reference": "fb0ab912a4fe38d695cbfeb84ee623ea644dd6a2"
+                "reference": "b60ca835a667e8a6a2c90350bf0de34f0a9ee74e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/City-of-Helsinki/drupal-helfi-platform-config/zipball/fb0ab912a4fe38d695cbfeb84ee623ea644dd6a2",
-                "reference": "fb0ab912a4fe38d695cbfeb84ee623ea644dd6a2",
+                "url": "https://api.github.com/repos/City-of-Helsinki/drupal-helfi-platform-config/zipball/b60ca835a667e8a6a2c90350bf0de34f0a9ee74e",
+                "reference": "b60ca835a667e8a6a2c90350bf0de34f0a9ee74e",
                 "shasum": ""
             },
             "require": {
@@ -4723,10 +4715,10 @@
             ],
             "description": "HELfi platform config",
             "support": {
-                "source": "https://github.com/City-of-Helsinki/drupal-helfi-platform-config/tree/4.12.3",
+                "source": "https://github.com/City-of-Helsinki/drupal-helfi-platform-config/tree/4.12.12",
                 "issues": "https://github.com/City-of-Helsinki/drupal-helfi-platform-config/issues"
             },
-            "time": "2025-01-13T06:44:52+00:00"
+            "time": "2025-01-28T14:33:21+00:00"
         },
         {
             "name": "drupal/helfi_proxy",
@@ -6937,17 +6929,17 @@
         },
         {
             "name": "drupal/redirect",
-            "version": "1.10.0",
+            "version": "1.11.0",
             "source": {
                 "type": "git",
                 "url": "https://git.drupalcode.org/project/redirect.git",
-                "reference": "8.x-1.10"
+                "reference": "8.x-1.11"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://ftp.drupal.org/files/projects/redirect-8.x-1.10.zip",
-                "reference": "8.x-1.10",
-                "shasum": "9d72d7e0717dbdea3ab3306c5d6840da5bd3024c"
+                "url": "https://ftp.drupal.org/files/projects/redirect-8.x-1.11.zip",
+                "reference": "8.x-1.11",
+                "shasum": "7df8b3524bbde07d254216039636947a689140ef"
             },
             "require": {
                 "drupal/core": "^9.2 || ^10 || ^11"
@@ -6955,8 +6947,8 @@
             "type": "drupal-module",
             "extra": {
                 "drupal": {
-                    "version": "8.x-1.10",
-                    "datestamp": "1723277641",
+                    "version": "8.x-1.11",
+                    "datestamp": "1737382886",
                     "security-coverage": {
                         "status": "covered",
                         "message": "Covered by Drupal's security advisory policy"
@@ -8045,26 +8037,26 @@
         },
         {
             "name": "drupal/translatable_menu_link_uri",
-            "version": "2.1.0",
+            "version": "2.1.1",
             "source": {
                 "type": "git",
                 "url": "https://git.drupalcode.org/project/translatable_menu_link_uri.git",
-                "reference": "2.1.0"
+                "reference": "2.1.1"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://ftp.drupal.org/files/projects/translatable_menu_link_uri-2.1.0.zip",
-                "reference": "2.1.0",
-                "shasum": "3bafa0a19761524560a2d232c945f10643e91053"
+                "url": "https://ftp.drupal.org/files/projects/translatable_menu_link_uri-2.1.1.zip",
+                "reference": "2.1.1",
+                "shasum": "862b0f0317ac37f8849d9f3725bcf44940d98dbf"
             },
             "require": {
-                "drupal/core": "^8 || ^9 || ^10"
+                "drupal/core": "^8 || ^9 || ^10 || ^11"
             },
             "type": "drupal-module",
             "extra": {
                 "drupal": {
-                    "version": "2.1.0",
-                    "datestamp": "1694361965",
+                    "version": "2.1.1",
+                    "datestamp": "1737392436",
                     "security-coverage": {
                         "status": "covered",
                         "message": "Covered by Drupal's security advisory policy"
@@ -9242,16 +9234,16 @@
         },
         {
             "name": "firebase/php-jwt",
-            "version": "v6.10.2",
+            "version": "v6.11.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/firebase/php-jwt.git",
-                "reference": "30c19ed0f3264cb660ea496895cfb6ef7ee3653b"
+                "reference": "8f718f4dfc9c5d5f0c994cdfd103921b43592712"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/firebase/php-jwt/zipball/30c19ed0f3264cb660ea496895cfb6ef7ee3653b",
-                "reference": "30c19ed0f3264cb660ea496895cfb6ef7ee3653b",
+                "url": "https://api.github.com/repos/firebase/php-jwt/zipball/8f718f4dfc9c5d5f0c994cdfd103921b43592712",
+                "reference": "8f718f4dfc9c5d5f0c994cdfd103921b43592712",
                 "shasum": ""
             },
             "require": {
@@ -9299,9 +9291,9 @@
             ],
             "support": {
                 "issues": "https://github.com/firebase/php-jwt/issues",
-                "source": "https://github.com/firebase/php-jwt/tree/v6.10.2"
+                "source": "https://github.com/firebase/php-jwt/tree/v6.11.0"
             },
-            "time": "2024-11-24T11:22:49+00:00"
+            "time": "2025-01-23T05:11:06+00:00"
         },
         {
             "name": "galbar/jsonpath",
@@ -10847,16 +10839,16 @@
         },
         {
             "name": "open-telemetry/api",
-            "version": "1.2.0",
+            "version": "1.2.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/opentelemetry-php/api.git",
-                "reference": "351a30baa79699de3de3a814c8ccc7b52ccdfb1d"
+                "reference": "74b1a03263be8c5acb578f41da054b4bac3af4a0"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/351a30baa79699de3de3a814c8ccc7b52ccdfb1d",
-                "reference": "351a30baa79699de3de3a814c8ccc7b52ccdfb1d",
+                "url": "https://api.github.com/repos/opentelemetry-php/api/zipball/74b1a03263be8c5acb578f41da054b4bac3af4a0",
+                "reference": "74b1a03263be8c5acb578f41da054b4bac3af4a0",
                 "shasum": ""
             },
             "require": {
@@ -10913,7 +10905,7 @@
                 "issues": "https://github.com/open-telemetry/opentelemetry-php/issues",
                 "source": "https://github.com/open-telemetry/opentelemetry-php"
             },
-            "time": "2025-01-08T23:50:34+00:00"
+            "time": "2025-01-20T23:35:16+00:00"
         },
         {
             "name": "open-telemetry/context",
diff --git a/conf/cmi/core.entity_form_display.helfi_news_groups.helfi_news_groups.default.yml b/conf/cmi/core.entity_form_display.helfi_news_groups.helfi_news_groups.default.yml
index c2f1cc6a8..8dd181eff 100644
--- a/conf/cmi/core.entity_form_display.helfi_news_groups.helfi_news_groups.default.yml
+++ b/conf/cmi/core.entity_form_display.helfi_news_groups.helfi_news_groups.default.yml
@@ -2,8 +2,6 @@ uuid: b95230b8-fb95-4373-b877-6117cf4d0f46
 langcode: en
 status: true
 dependencies:
-  config:
-    - external_entities.external_entity_type.helfi_news_groups
   module:
     - external_entities
 _core:
diff --git a/conf/cmi/core.entity_form_display.helfi_news_neighbourhoods.helfi_news_neighbourhoods.default.yml b/conf/cmi/core.entity_form_display.helfi_news_neighbourhoods.helfi_news_neighbourhoods.default.yml
index a656bf7c7..79cb075a0 100644
--- a/conf/cmi/core.entity_form_display.helfi_news_neighbourhoods.helfi_news_neighbourhoods.default.yml
+++ b/conf/cmi/core.entity_form_display.helfi_news_neighbourhoods.helfi_news_neighbourhoods.default.yml
@@ -2,8 +2,6 @@ uuid: d13089ef-9704-4770-90a9-fd9fedc50405
 langcode: en
 status: true
 dependencies:
-  config:
-    - external_entities.external_entity_type.helfi_news_neighbourhoods
   module:
     - external_entities
 _core:
diff --git a/conf/cmi/core.entity_form_display.helfi_news_tags.helfi_news_tags.default.yml b/conf/cmi/core.entity_form_display.helfi_news_tags.helfi_news_tags.default.yml
index c6bd11619..01f9b4bfe 100644
--- a/conf/cmi/core.entity_form_display.helfi_news_tags.helfi_news_tags.default.yml
+++ b/conf/cmi/core.entity_form_display.helfi_news_tags.helfi_news_tags.default.yml
@@ -2,8 +2,6 @@ uuid: 972acdae-8e71-4d12-a335-bc91828f4851
 langcode: en
 status: true
 dependencies:
-  config:
-    - external_entities.external_entity_type.helfi_news_tags
   module:
     - external_entities
 _core:
diff --git a/conf/cmi/core.entity_form_display.node.news_item.default.yml b/conf/cmi/core.entity_form_display.node.news_item.default.yml
index efe6959d1..3574d90a9 100644
--- a/conf/cmi/core.entity_form_display.node.news_item.default.yml
+++ b/conf/cmi/core.entity_form_display.node.news_item.default.yml
@@ -20,7 +20,7 @@ dependencies:
     - field_group
     - hdbt_admin_tools
     - helfi_annif
-    - helfi_etusivu
+    - linkit
     - media_library
     - paragraphs
     - path
diff --git a/conf/cmi/core.entity_form_display.taxonomy_term.news_neighbourhoods.default.yml b/conf/cmi/core.entity_form_display.taxonomy_term.news_neighbourhoods.default.yml
new file mode 100644
index 000000000..d4987cc5b
--- /dev/null
+++ b/conf/cmi/core.entity_form_display.taxonomy_term.news_neighbourhoods.default.yml
@@ -0,0 +1,64 @@
+uuid: dc0590fe-78b1-402d-b0c7-686c8f32d3e5
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.taxonomy_term.news_neighbourhoods.field_location
+    - taxonomy.vocabulary.news_neighbourhoods
+  module:
+    - helfi_platform_config
+    - path
+    - text
+id: taxonomy_term.news_neighbourhoods.default
+targetEntityType: taxonomy_term
+bundle: news_neighbourhoods
+mode: default
+content:
+  description:
+    type: text_textfield
+    weight: 0
+    region: content
+    settings:
+      size: 60
+      placeholder: ''
+    third_party_settings: {  }
+  field_location:
+    type: location
+    weight: 101
+    region: content
+    settings: {  }
+    third_party_settings: {  }
+  langcode:
+    type: language_select
+    weight: 2
+    region: content
+    settings:
+      include_locked: true
+    third_party_settings: {  }
+  name:
+    type: string_textfield
+    weight: -5
+    region: content
+    settings:
+      size: 60
+      placeholder: ''
+    third_party_settings: {  }
+  path:
+    type: path
+    weight: 30
+    region: content
+    settings: {  }
+    third_party_settings: {  }
+  status:
+    type: boolean_checkbox
+    weight: 100
+    region: content
+    settings:
+      display_label: true
+    third_party_settings: {  }
+  translation:
+    weight: 10
+    region: content
+    settings: {  }
+    third_party_settings: {  }
+hidden: {  }
diff --git a/conf/cmi/core.entity_view_display.helfi_news.helfi_news.default.yml b/conf/cmi/core.entity_view_display.helfi_news.helfi_news.default.yml
index ee494555e..a81e9fed2 100644
--- a/conf/cmi/core.entity_view_display.helfi_news.helfi_news.default.yml
+++ b/conf/cmi/core.entity_view_display.helfi_news.helfi_news.default.yml
@@ -2,8 +2,6 @@ uuid: 8cdbdb79-0afd-4c2a-9eac-286984c02eff
 langcode: en
 status: true
 dependencies:
-  config:
-    - external_entities.external_entity_type.helfi_news
   module:
     - external_entities
 _core:
@@ -28,6 +26,7 @@ hidden:
   neighbourhoods_uuid: true
   node_url: true
   published_at: true
+  search_api_excerpt: true
   short_title: true
   tags: true
   tags_uuid: true
diff --git a/conf/cmi/core.entity_view_display.helfi_news.helfi_news.medium_teaser.yml b/conf/cmi/core.entity_view_display.helfi_news.helfi_news.medium_teaser.yml
index f2b030351..aa23253ec 100644
--- a/conf/cmi/core.entity_view_display.helfi_news.helfi_news.medium_teaser.yml
+++ b/conf/cmi/core.entity_view_display.helfi_news.helfi_news.medium_teaser.yml
@@ -4,7 +4,6 @@ status: true
 dependencies:
   config:
     - core.entity_view_mode.helfi_news.medium_teaser
-    - external_entities.external_entity_type.helfi_news
   module:
     - external_entities
   enforced:
@@ -32,6 +31,7 @@ hidden:
   neighbourhoods_uuid: true
   node_url: true
   published_at: true
+  search_api_excerpt: true
   short_title: true
   tags: true
   tags_uuid: true
diff --git a/conf/cmi/core.entity_view_display.helfi_news_groups.helfi_news_groups.default.yml b/conf/cmi/core.entity_view_display.helfi_news_groups.helfi_news_groups.default.yml
index 307933c5f..aa728ac27 100644
--- a/conf/cmi/core.entity_view_display.helfi_news_groups.helfi_news_groups.default.yml
+++ b/conf/cmi/core.entity_view_display.helfi_news_groups.helfi_news_groups.default.yml
@@ -2,8 +2,6 @@ uuid: 045f335b-7059-4d6a-9240-43909f3c1704
 langcode: en
 status: true
 dependencies:
-  config:
-    - external_entities.external_entity_type.helfi_news_groups
   module:
     - external_entities
 _core:
diff --git a/conf/cmi/core.entity_view_display.helfi_news_neighbourhoods.helfi_news_neighbourhoods.default.yml b/conf/cmi/core.entity_view_display.helfi_news_neighbourhoods.helfi_news_neighbourhoods.default.yml
index 934490b53..c50727328 100644
--- a/conf/cmi/core.entity_view_display.helfi_news_neighbourhoods.helfi_news_neighbourhoods.default.yml
+++ b/conf/cmi/core.entity_view_display.helfi_news_neighbourhoods.helfi_news_neighbourhoods.default.yml
@@ -2,8 +2,6 @@ uuid: f8d0f0da-4aff-436c-a4b8-4df8c6ee82f8
 langcode: en
 status: true
 dependencies:
-  config:
-    - external_entities.external_entity_type.helfi_news_neighbourhoods
   module:
     - external_entities
 _core:
diff --git a/conf/cmi/core.entity_view_display.helfi_news_tags.helfi_news_tags.default.yml b/conf/cmi/core.entity_view_display.helfi_news_tags.helfi_news_tags.default.yml
index 08efe9fde..308818920 100644
--- a/conf/cmi/core.entity_view_display.helfi_news_tags.helfi_news_tags.default.yml
+++ b/conf/cmi/core.entity_view_display.helfi_news_tags.helfi_news_tags.default.yml
@@ -2,8 +2,6 @@ uuid: 7060dc64-5f95-4fa5-8046-bf0ad5e265c4
 langcode: en
 status: true
 dependencies:
-  config:
-    - external_entities.external_entity_type.helfi_news_tags
   module:
     - external_entities
 _core:
diff --git a/conf/cmi/core.entity_view_display.taxonomy_term.news_neighbourhoods.default.yml b/conf/cmi/core.entity_view_display.taxonomy_term.news_neighbourhoods.default.yml
new file mode 100644
index 000000000..ef913c501
--- /dev/null
+++ b/conf/cmi/core.entity_view_display.taxonomy_term.news_neighbourhoods.default.yml
@@ -0,0 +1,25 @@
+uuid: 96825838-b49d-4139-8bf9-24ba7d31b5ac
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.taxonomy_term.news_neighbourhoods.field_location
+    - taxonomy.vocabulary.news_neighbourhoods
+  module:
+    - text
+id: taxonomy_term.news_neighbourhoods.default
+targetEntityType: taxonomy_term
+bundle: news_neighbourhoods
+mode: default
+content:
+  description:
+    type: text_default
+    label: hidden
+    settings: {  }
+    third_party_settings: {  }
+    weight: 0
+    region: content
+hidden:
+  field_location: true
+  langcode: true
+  search_api_excerpt: true
diff --git a/conf/cmi/external_entities.external_entity_type.helfi_news_neighbourhoods.yml b/conf/cmi/external_entities.external_entity_type.helfi_news_neighbourhoods.yml
index e7f668016..b092d9948 100644
--- a/conf/cmi/external_entities.external_entity_type.helfi_news_neighbourhoods.yml
+++ b/conf/cmi/external_entities.external_entity_type.helfi_news_neighbourhoods.yml
@@ -3,7 +3,7 @@ langcode: en
 status: true
 dependencies: {  }
 _core:
-  default_config_hash: 5s8g-adBsUuIIMte5BCEwSFlQjxwFJ_Gq2Do7EEwq2E
+  default_config_hash: 09-lIa9cl9PNPVIBXCVIR8yBfofPifSwhAsD0QVh8l8
 id: helfi_news_neighbourhoods
 label: 'Helfi: News neighbourhoods'
 label_plural: 'Helfi: News neighbourhoods'
@@ -21,6 +21,8 @@ field_mapper_config:
       value: '$._source.name[0]'
     tid:
       value: '$._source.tid[0]'
+    location:
+      value: $._source.field_location
 storage_client_id: helfi_news_neighbourhoods
 storage_client_config: {  }
 persistent_cache_max_age: 86400
diff --git a/conf/cmi/field.field.media.image.field_media_image.yml b/conf/cmi/field.field.media.image.field_media_image.yml
index c9cf96f82..75a923b72 100644
--- a/conf/cmi/field.field.media.image.field_media_image.yml
+++ b/conf/cmi/field.field.media.image.field_media_image.yml
@@ -20,7 +20,7 @@ id: media.image.field_media_image
 field_name: field_media_image
 entity_type: media
 bundle: image
-label: Image
+label: 'Image (supported in other languages)'
 description: ''
 required: true
 translatable: true
diff --git a/conf/cmi/field.field.taxonomy_term.news_neighbourhoods.field_location.yml b/conf/cmi/field.field.taxonomy_term.news_neighbourhoods.field_location.yml
new file mode 100644
index 000000000..80d02f51d
--- /dev/null
+++ b/conf/cmi/field.field.taxonomy_term.news_neighbourhoods.field_location.yml
@@ -0,0 +1,23 @@
+uuid: 77736144-e2d4-4f43-9bf4-d6a5700454e9
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.taxonomy_term.field_location
+    - taxonomy.vocabulary.news_neighbourhoods
+  module:
+    - helfi_platform_config
+_core:
+  default_config_hash: zMqD_YIcxb2SEi-zEfioqQpiVagAuP6dpQnuKTmr4LU
+id: taxonomy_term.news_neighbourhoods.field_location
+field_name: field_location
+entity_type: taxonomy_term
+bundle: news_neighbourhoods
+label: Location
+description: 'Neighbourhood location. Used to search nearby content.'
+required: true
+translatable: false
+default_value: {  }
+default_value_callback: ''
+settings: {  }
+field_type: location
diff --git a/conf/cmi/field.storage.taxonomy_term.field_location.yml b/conf/cmi/field.storage.taxonomy_term.field_location.yml
new file mode 100644
index 000000000..812b4d065
--- /dev/null
+++ b/conf/cmi/field.storage.taxonomy_term.field_location.yml
@@ -0,0 +1,21 @@
+uuid: 44d5b217-5ef0-4178-8a1a-6190babb12e2
+langcode: en
+status: true
+dependencies:
+  module:
+    - helfi_platform_config
+    - taxonomy
+_core:
+  default_config_hash: J3VYlUUqe9orySkqRTkfaf3zmiABVwpRFgS2Wk-qykg
+id: taxonomy_term.field_location
+field_name: field_location
+entity_type: taxonomy_term
+type: location
+settings: {  }
+module: helfi_platform_config
+locked: false
+cardinality: 1
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/conf/cmi/language/ar/views.view.content.yml b/conf/cmi/language/ar/views.view.content.yml
index e4d1c10c7..cb89f02c1 100644
--- a/conf/cmi/language/ar/views.view.content.yml
+++ b/conf/cmi/language/ar/views.view.content.yml
@@ -54,6 +54,8 @@ display:
             group_items:
               1:
                 title: منشور
+              2:
+                title: 'غير منشور'
         langcode:
           expose:
             label: اللغة
diff --git a/conf/cmi/language/ar/views.view.media_library.yml b/conf/cmi/language/ar/views.view.media_library.yml
index 2d87d59e3..ae098c146 100644
--- a/conf/cmi/language/ar/views.view.media_library.yml
+++ b/conf/cmi/language/ar/views.view.media_library.yml
@@ -25,6 +25,8 @@ display:
             group_items:
               1:
                 title: منشور
+              2:
+                title: 'غير منشور'
         name:
           expose:
             label: الاسم
diff --git a/conf/cmi/language/de/views.view.content.yml b/conf/cmi/language/de/views.view.content.yml
index 9d4f1d334..c4cfac7c2 100644
--- a/conf/cmi/language/de/views.view.content.yml
+++ b/conf/cmi/language/de/views.view.content.yml
@@ -57,6 +57,8 @@ display:
             group_items:
               1:
                 title: Veröffentlicht
+              2:
+                title: 'Nicht veröffentlicht'
         langcode:
           expose:
             label: Sprache
diff --git a/conf/cmi/language/de/views.view.helfi_redirect.yml b/conf/cmi/language/de/views.view.helfi_redirect.yml
index 78f7d286d..082af5b58 100644
--- a/conf/cmi/language/de/views.view.helfi_redirect.yml
+++ b/conf/cmi/language/de/views.view.helfi_redirect.yml
@@ -22,10 +22,10 @@ display:
       pager:
         options:
           tags:
-            next: 'nächste Seite ›'
-            previous: '‹ vorherige Seite'
-            first: '« erste Seite'
-            last: 'letzte Seite »'
+            next: Weiter
+            previous: Vorherige
+            first: Anfang
+            last: Ende
           expose:
             items_per_page_label: 'Elemente pro Seite'
             items_per_page_options_all_label: '- Alle -'
diff --git a/conf/cmi/language/de/views.view.media_library.yml b/conf/cmi/language/de/views.view.media_library.yml
index 8dea96bd4..db069a047 100644
--- a/conf/cmi/language/de/views.view.media_library.yml
+++ b/conf/cmi/language/de/views.view.media_library.yml
@@ -46,6 +46,8 @@ display:
             group_items:
               1:
                 title: Veröffentlicht
+              2:
+                title: 'Nicht veröffentlicht'
         name:
           expose:
             label: Name
diff --git a/conf/cmi/language/es/views.view.content.yml b/conf/cmi/language/es/views.view.content.yml
index 7b2c96ab9..02507d58a 100644
--- a/conf/cmi/language/es/views.view.content.yml
+++ b/conf/cmi/language/es/views.view.content.yml
@@ -57,6 +57,8 @@ display:
             group_items:
               1:
                 title: Publicado
+              2:
+                title: 'Sin publicar'
         langcode:
           expose:
             label: Idioma
diff --git a/conf/cmi/language/es/views.view.helfi_redirect.yml b/conf/cmi/language/es/views.view.helfi_redirect.yml
index 3703eb7b4..02479f267 100644
--- a/conf/cmi/language/es/views.view.helfi_redirect.yml
+++ b/conf/cmi/language/es/views.view.helfi_redirect.yml
@@ -22,10 +22,10 @@ display:
       pager:
         options:
           tags:
-            next: 'siguiente ›'
-            previous: '‹ anterior'
-            first: '« primero'
-            last: 'último »'
+            next: Siguiente
+            previous: Anterior
+            first: Primero
+            last: Último
           expose:
             items_per_page_label: 'Elementos por página'
             items_per_page_options_all_label: '- Todo -'
diff --git a/conf/cmi/language/es/views.view.media_library.yml b/conf/cmi/language/es/views.view.media_library.yml
index 70b3ae256..24bdcfe49 100644
--- a/conf/cmi/language/es/views.view.media_library.yml
+++ b/conf/cmi/language/es/views.view.media_library.yml
@@ -46,6 +46,8 @@ display:
             group_items:
               1:
                 title: Publicado
+              2:
+                title: 'Sin publicar'
         name:
           expose:
             label: Nombre
diff --git a/conf/cmi/language/et/views.view.content.yml b/conf/cmi/language/et/views.view.content.yml
index 5e1a5c36e..229fda04b 100644
--- a/conf/cmi/language/et/views.view.content.yml
+++ b/conf/cmi/language/et/views.view.content.yml
@@ -57,6 +57,8 @@ display:
             group_items:
               1:
                 title: Avaldatud
+              2:
+                title: Avaldamata
         langcode:
           expose:
             label: Keel
diff --git a/conf/cmi/language/et/views.view.helfi_redirect.yml b/conf/cmi/language/et/views.view.helfi_redirect.yml
index 6109a9a20..fecdf5553 100644
--- a/conf/cmi/language/et/views.view.helfi_redirect.yml
+++ b/conf/cmi/language/et/views.view.helfi_redirect.yml
@@ -21,8 +21,8 @@ display:
           tags:
             next: 'järgmine ›'
             previous: '‹ eelmine'
-            first: '« esimene'
-            last: 'viimane »'
+            first: Esimene
+            last: Viimane
           expose:
             items_per_page_label: 'Kirjeid lehel'
             items_per_page_options_all_label: '- Kõik -'
diff --git a/conf/cmi/language/et/views.view.media_library.yml b/conf/cmi/language/et/views.view.media_library.yml
index 3f37847d8..dc51edac3 100644
--- a/conf/cmi/language/et/views.view.media_library.yml
+++ b/conf/cmi/language/et/views.view.media_library.yml
@@ -45,6 +45,8 @@ display:
             group_items:
               1:
                 title: Avaldatud
+              2:
+                title: Avaldamata
         name:
           expose:
             label: Nimi
diff --git a/conf/cmi/language/fa/views.view.content.yml b/conf/cmi/language/fa/views.view.content.yml
index b7760092f..0e0138f28 100644
--- a/conf/cmi/language/fa/views.view.content.yml
+++ b/conf/cmi/language/fa/views.view.content.yml
@@ -48,6 +48,8 @@ display:
             group_items:
               1:
                 title: 'منتشر شده'
+              2:
+                title: 'منتشر نشده'
         langcode:
           expose:
             label: زبان
diff --git a/conf/cmi/language/fa/views.view.helfi_redirect.yml b/conf/cmi/language/fa/views.view.helfi_redirect.yml
index b7fbc8baf..7fe203832 100644
--- a/conf/cmi/language/fa/views.view.helfi_redirect.yml
+++ b/conf/cmi/language/fa/views.view.helfi_redirect.yml
@@ -16,10 +16,10 @@ display:
       pager:
         options:
           tags:
-            next: 'بعدی ›'
-            previous: '‹ قبلی'
-            first: '« ابتدا'
-            last: 'انتها »'
+            next: بعدی
+            previous: قبلی
+            first: اولین
+            last: آخرین
           expose:
             items_per_page_label: 'آیتم‌ها در هر صفحه'
             items_per_page_options_all_label: '- همه -'
diff --git a/conf/cmi/language/fa/views.view.media_library.yml b/conf/cmi/language/fa/views.view.media_library.yml
index d99bc4557..b155c675c 100644
--- a/conf/cmi/language/fa/views.view.media_library.yml
+++ b/conf/cmi/language/fa/views.view.media_library.yml
@@ -25,6 +25,8 @@ display:
             group_items:
               1:
                 title: 'منتشر شده'
+              2:
+                title: 'منتشر نشده'
         name:
           expose:
             label: نام
diff --git a/conf/cmi/language/fi/field.field.taxonomy_term.news_neighbourhoods.field_location.yml b/conf/cmi/language/fi/field.field.taxonomy_term.news_neighbourhoods.field_location.yml
new file mode 100644
index 000000000..d109e918b
--- /dev/null
+++ b/conf/cmi/language/fi/field.field.taxonomy_term.news_neighbourhoods.field_location.yml
@@ -0,0 +1 @@
+label: Sijainti
diff --git a/conf/cmi/language/fi/views.view.archive.yml b/conf/cmi/language/fi/views.view.archive.yml
index c18dbd7db..720c74895 100644
--- a/conf/cmi/language/fi/views.view.archive.yml
+++ b/conf/cmi/language/fi/views.view.archive.yml
@@ -8,8 +8,8 @@ display:
       pager:
         options:
           tags:
-            next: 'Seuraava ›'
-            previous: '‹ Edellinen'
+            next: Seuraava
+            previous: Edellinen
           expose:
             items_per_page_label: 'Merkintöjä sivua kohti'
             items_per_page_options_all_label: '- Kaikki -'
diff --git a/conf/cmi/language/fi/views.view.content.yml b/conf/cmi/language/fi/views.view.content.yml
index fb036d95d..dabc7aafd 100644
--- a/conf/cmi/language/fi/views.view.content.yml
+++ b/conf/cmi/language/fi/views.view.content.yml
@@ -25,10 +25,10 @@ display:
       pager:
         options:
           tags:
-            next: 'Seuraava ›'
-            previous: '‹ Edellinen'
-            first: '« Ensimmäinen'
-            last: 'Viimeinen »'
+            next: Seuraava
+            previous: Edellinen
+            first: Ensimmäinen
+            last: Viimeinen
       exposed_form:
         options:
           submit_button: Suodata
@@ -54,6 +54,8 @@ display:
             group_items:
               1:
                 title: Julkaistu
+              2:
+                title: Julkaisematon
         langcode:
           expose:
             label: Kieli
diff --git a/conf/cmi/language/fi/views.view.files.yml b/conf/cmi/language/fi/views.view.files.yml
index 32d7ce0dc..95e17ad02 100644
--- a/conf/cmi/language/fi/views.view.files.yml
+++ b/conf/cmi/language/fi/views.view.files.yml
@@ -14,8 +14,8 @@ display:
       pager:
         options:
           tags:
-            previous: '‹ Edellinen'
-            next: 'Seuraava ›'
+            previous: Edellinen
+            next: Seuraava
           expose:
             items_per_page_label: 'Merkintöjä sivua kohti'
             items_per_page_options_all_label: '- Kaikki -'
@@ -66,8 +66,8 @@ display:
       pager:
         options:
           tags:
-            previous: '‹ Edellinen'
-            next: 'Seuraava ›'
+            previous: Edellinen
+            next: Seuraava
           expose:
             items_per_page_label: 'Merkintöjä sivua kohti'
             items_per_page_options_all_label: '- Kaikki -'
diff --git a/conf/cmi/language/fi/views.view.glossary.yml b/conf/cmi/language/fi/views.view.glossary.yml
index 683d043b6..240fa4932 100644
--- a/conf/cmi/language/fi/views.view.glossary.yml
+++ b/conf/cmi/language/fi/views.view.glossary.yml
@@ -13,8 +13,8 @@ display:
       pager:
         options:
           tags:
-            next: 'Seuraava ›'
-            previous: '‹ Edellinen'
+            next: Seuraava
+            previous: Edellinen
           expose:
             items_per_page_label: 'Merkintöjä sivua kohti'
             items_per_page_options_all_label: '- Kaikki -'
diff --git a/conf/cmi/language/fi/views.view.helfi_redirect.yml b/conf/cmi/language/fi/views.view.helfi_redirect.yml
index 039970d49..882c6b6d6 100644
--- a/conf/cmi/language/fi/views.view.helfi_redirect.yml
+++ b/conf/cmi/language/fi/views.view.helfi_redirect.yml
@@ -51,10 +51,10 @@ display:
             items_per_page_options_all_label: '- Kaikki -'
             offset_label: Offset
           tags:
-            previous: '‹ edellinen'
-            next: 'seuraava ›'
-            first: '« ensimmäinen'
-            last: 'viimeinen »'
+            previous: Edellinen
+            next: Seuraava
+            first: Ensimmäinen
+            last: Viimeinen
       fields:
         redirect_bulk_form:
           action_title: Valinnalla
diff --git a/conf/cmi/language/fi/views.view.locked_content.yml b/conf/cmi/language/fi/views.view.locked_content.yml
index c879a9260..a6433b542 100644
--- a/conf/cmi/language/fi/views.view.locked_content.yml
+++ b/conf/cmi/language/fi/views.view.locked_content.yml
@@ -14,10 +14,10 @@ display:
       pager:
         options:
           tags:
-            previous: '‹ Edellinen'
-            next: 'Seuraava ›'
-            first: '« Ensimmäinen'
-            last: 'Viimeinen »'
+            previous: Edellinen
+            next: Seuraava
+            first: Ensimmäinen
+            last: Viimeinen
           expose:
             items_per_page_label: 'Merkintöjä sivua kohti'
             items_per_page_options_all_label: '- Kaikki -'
diff --git a/conf/cmi/language/fi/views.view.media.yml b/conf/cmi/language/fi/views.view.media.yml
index 631b568f9..4051a51b3 100644
--- a/conf/cmi/language/fi/views.view.media.yml
+++ b/conf/cmi/language/fi/views.view.media.yml
@@ -32,10 +32,10 @@ display:
       pager:
         options:
           tags:
-            next: 'Seuraava ›'
-            previous: '‹ Edellinen'
-            first: '« Ensimmäinen'
-            last: 'Viimeinen »'
+            next: Seuraava
+            previous: Edellinen
+            first: Ensimmäinen
+            last: Viimeinen
           expose:
             items_per_page_label: 'Merkintöjä sivua kohti'
             items_per_page_options_all_label: '- Kaikki -'
diff --git a/conf/cmi/language/fi/views.view.media_library.yml b/conf/cmi/language/fi/views.view.media_library.yml
index 3ac1af279..641d4783e 100644
--- a/conf/cmi/language/fi/views.view.media_library.yml
+++ b/conf/cmi/language/fi/views.view.media_library.yml
@@ -10,8 +10,8 @@ display:
       pager:
         options:
           tags:
-            next: 'Seuraava ›'
-            previous: '‹ Edellinen'
+            next: Seuraava
+            previous: Edellinen
           expose:
             items_per_page_label: 'Merkintöjä sivua kohti'
             items_per_page_options_all_label: '- Kaikki -'
@@ -45,6 +45,8 @@ display:
             group_items:
               1:
                 title: Julkaistu
+              2:
+                title: Julkaisematon
         name:
           expose:
             label: Nimi
diff --git a/conf/cmi/language/fi/views.view.paragraphs_library.yml b/conf/cmi/language/fi/views.view.paragraphs_library.yml
index dd254064b..8e28dfc34 100644
--- a/conf/cmi/language/fi/views.view.paragraphs_library.yml
+++ b/conf/cmi/language/fi/views.view.paragraphs_library.yml
@@ -13,8 +13,8 @@ display:
       pager:
         options:
           tags:
-            previous: '‹ Edellinen'
-            next: 'Seuraava ›'
+            previous: Edellinen
+            next: Seuraava
           expose:
             items_per_page_label: 'Merkintöjä sivua kohti'
             items_per_page_options_all_label: '- Kaikki -'
diff --git a/conf/cmi/language/fi/views.view.redirect.yml b/conf/cmi/language/fi/views.view.redirect.yml
index 33a42fd66..acafc0d3e 100644
--- a/conf/cmi/language/fi/views.view.redirect.yml
+++ b/conf/cmi/language/fi/views.view.redirect.yml
@@ -41,10 +41,10 @@ display:
       pager:
         options:
           tags:
-            previous: '‹ edellinen'
-            next: 'seuraava ›'
-            first: '« ensimmäinen'
-            last: 'viimeinen »'
+            previous: Edellinen
+            next: Seuraava
+            first: Ensimmäinen
+            last: Viimeinen
           expose:
             items_per_page_label: 'Merkintöjä sivua kohti'
             items_per_page_options_all_label: '- Kaikki -'
diff --git a/conf/cmi/language/fi/views.view.scheduler_scheduled_content.yml b/conf/cmi/language/fi/views.view.scheduler_scheduled_content.yml
index 2f57f70a9..0831edddb 100644
--- a/conf/cmi/language/fi/views.view.scheduler_scheduled_content.yml
+++ b/conf/cmi/language/fi/views.view.scheduler_scheduled_content.yml
@@ -49,6 +49,13 @@ display:
         langcode:
           expose:
             label: Kieli
+      pager:
+        options:
+          tags:
+            previous: Edellinen
+            next: Seuraava
+            first: Ensimmäinen
+            last: Viimeinen
       title: 'Ajastettu sisältö'
       empty:
         area_text_custom:
@@ -60,13 +67,6 @@ display:
           exposed_sorts_label: Lajittele
           sort_asc_label: Nousevasti
           sort_desc_label: Laskevasti
-      pager:
-        options:
-          tags:
-            previous: '‹ edellinen'
-            next: 'seuraava ›'
-            first: '« ensimmäinen'
-            last: 'viimeinen »'
     display_title: Oletus
   overview:
     display_options:
diff --git a/conf/cmi/language/fi/views.view.taxonomy_term.yml b/conf/cmi/language/fi/views.view.taxonomy_term.yml
index 9b2ff8834..fab4536a6 100644
--- a/conf/cmi/language/fi/views.view.taxonomy_term.yml
+++ b/conf/cmi/language/fi/views.view.taxonomy_term.yml
@@ -7,8 +7,8 @@ display:
       pager:
         options:
           tags:
-            next: 'Seuraava ›'
-            previous: '‹ Edellinen'
+            next: Seuraava
+            previous: Edellinen
           expose:
             items_per_page_label: 'Merkintöjä sivua kohti'
             items_per_page_options_all_label: '- Kaikki -'
diff --git a/conf/cmi/language/fi/views.view.user_admin_people.yml b/conf/cmi/language/fi/views.view.user_admin_people.yml
index cbd19eed1..6d8166706 100644
--- a/conf/cmi/language/fi/views.view.user_admin_people.yml
+++ b/conf/cmi/language/fi/views.view.user_admin_people.yml
@@ -30,10 +30,10 @@ display:
       pager:
         options:
           tags:
-            next: 'Seuraava ›'
-            previous: '‹ Edellinen'
-            first: '« Ensimmäinen'
-            last: 'Viimeinen »'
+            next: Seuraava
+            previous: Edellinen
+            first: Ensimmäinen
+            last: Viimeinen
           expose:
             items_per_page_label: 'Merkintöjä sivua kohti'
             items_per_page_options_all_label: '- Kaikki -'
diff --git a/conf/cmi/language/fr/views.view.content.yml b/conf/cmi/language/fr/views.view.content.yml
index ca5615170..c6f37a86d 100644
--- a/conf/cmi/language/fr/views.view.content.yml
+++ b/conf/cmi/language/fr/views.view.content.yml
@@ -57,6 +57,8 @@ display:
             group_items:
               1:
                 title: Publié
+              2:
+                title: 'Non publié'
         langcode:
           expose:
             label: Langue
diff --git a/conf/cmi/language/fr/views.view.helfi_redirect.yml b/conf/cmi/language/fr/views.view.helfi_redirect.yml
index 5a0fe7da0..dc0cfc225 100644
--- a/conf/cmi/language/fr/views.view.helfi_redirect.yml
+++ b/conf/cmi/language/fr/views.view.helfi_redirect.yml
@@ -24,8 +24,8 @@ display:
           tags:
             next: "suivant\_›"
             previous: "‹\_précédent"
-            first: "«\_premier"
-            last: "dernier\_»"
+            first: Premier(e)
+            last: Dernier
           expose:
             items_per_page_label: 'Éléments par page'
             items_per_page_options_all_label: '- Tout -'
diff --git a/conf/cmi/language/fr/views.view.media_library.yml b/conf/cmi/language/fr/views.view.media_library.yml
index 02382c856..3a14bc2c0 100644
--- a/conf/cmi/language/fr/views.view.media_library.yml
+++ b/conf/cmi/language/fr/views.view.media_library.yml
@@ -46,6 +46,8 @@ display:
             group_items:
               1:
                 title: Publié
+              2:
+                title: 'Non publié'
         name:
           expose:
             label: Nom
diff --git a/conf/cmi/language/ru/views.view.content.yml b/conf/cmi/language/ru/views.view.content.yml
index 0cab959b4..9377f12e2 100644
--- a/conf/cmi/language/ru/views.view.content.yml
+++ b/conf/cmi/language/ru/views.view.content.yml
@@ -57,6 +57,8 @@ display:
             group_items:
               1:
                 title: Опубликовано
+              2:
+                title: 'Снято с публикации'
         langcode:
           expose:
             label: Язык
diff --git a/conf/cmi/language/ru/views.view.helfi_redirect.yml b/conf/cmi/language/ru/views.view.helfi_redirect.yml
index 33d694a09..e8339147d 100644
--- a/conf/cmi/language/ru/views.view.helfi_redirect.yml
+++ b/conf/cmi/language/ru/views.view.helfi_redirect.yml
@@ -21,10 +21,10 @@ display:
       pager:
         options:
           tags:
-            next: 'следующая ›'
-            previous: '‹ предыдущая'
-            first: '« первая'
-            last: 'последняя »'
+            next: Вперёд
+            previous: Назад
+            first: Первый
+            last: Последний
           expose:
             items_per_page_label: 'Элементов на страницу'
             items_per_page_options_all_label: '- Все -'
diff --git a/conf/cmi/language/ru/views.view.media_library.yml b/conf/cmi/language/ru/views.view.media_library.yml
index 5b2c71b65..488825b83 100644
--- a/conf/cmi/language/ru/views.view.media_library.yml
+++ b/conf/cmi/language/ru/views.view.media_library.yml
@@ -46,6 +46,8 @@ display:
             group_items:
               1:
                 title: Опубликовано
+              2:
+                title: 'Снято с публикации'
         name:
           expose:
             label: Название
diff --git a/conf/cmi/language/sv/block.block.chatleijuke.yml b/conf/cmi/language/sv/block.block.chatleijuke.yml
deleted file mode 100644
index 052d397fd..000000000
--- a/conf/cmi/language/sv/block.block.chatleijuke.yml
+++ /dev/null
@@ -1,2 +0,0 @@
-settings:
-  chat_title: 'Fråga staden!'
diff --git a/conf/cmi/language/sv/field.field.taxonomy_term.news_neighbourhoods.field_location.yml b/conf/cmi/language/sv/field.field.taxonomy_term.news_neighbourhoods.field_location.yml
new file mode 100644
index 000000000..9a338e991
--- /dev/null
+++ b/conf/cmi/language/sv/field.field.taxonomy_term.news_neighbourhoods.field_location.yml
@@ -0,0 +1 @@
+label: Plats
diff --git a/conf/cmi/language/sv/views.view.archive.yml b/conf/cmi/language/sv/views.view.archive.yml
index 6699109fa..35b91faea 100644
--- a/conf/cmi/language/sv/views.view.archive.yml
+++ b/conf/cmi/language/sv/views.view.archive.yml
@@ -8,8 +8,8 @@ display:
       pager:
         options:
           tags:
-            next: ››
-            previous: ‹‹
+            next: Nästa
+            previous: Föregående
           expose:
             items_per_page_label: 'Inlägg per sida'
             items_per_page_options_all_label: '- Alla -'
diff --git a/conf/cmi/language/sv/views.view.content.yml b/conf/cmi/language/sv/views.view.content.yml
index 61393d4d1..11a7b349e 100644
--- a/conf/cmi/language/sv/views.view.content.yml
+++ b/conf/cmi/language/sv/views.view.content.yml
@@ -13,10 +13,10 @@ display:
         name:
           label: Författare
         status:
-          label: Status
           settings:
             format_custom_false: 'Ej publicerad'
             format_custom_true: Publicerad
+          label: Status
         changed:
           label: Uppdaterad
         operations:
@@ -25,10 +25,10 @@ display:
       pager:
         options:
           tags:
-            next: 'Nästa ›'
-            previous: '‹ Föregående'
-            first: '« Första'
-            last: 'Sista »'
+            next: Nästa
+            previous: Föregående
+            first: Första
+            last: Sista
       exposed_form:
         options:
           submit_button: Filtrera
@@ -47,13 +47,15 @@ display:
           expose:
             label: Innehållstyp
         status:
-          expose:
-            label: Status
           group_info:
             label: 'Status för publicering'
             group_items:
               1:
                 title: Publicerad
+              2:
+                title: 'Ej publicerad'
+          expose:
+            label: Status
         langcode:
           expose:
             label: Språk
diff --git a/conf/cmi/language/sv/views.view.files.yml b/conf/cmi/language/sv/views.view.files.yml
index ff2d56fd8..3b285d01e 100644
--- a/conf/cmi/language/sv/views.view.files.yml
+++ b/conf/cmi/language/sv/views.view.files.yml
@@ -14,8 +14,8 @@ display:
       pager:
         options:
           tags:
-            previous: '‹ Föregående'
-            next: 'Nästa ›'
+            previous: Föregående
+            next: Nästa
           expose:
             items_per_page_label: 'Inlägg per sida'
             items_per_page_options_all_label: '- Alla -'
@@ -69,8 +69,8 @@ display:
       pager:
         options:
           tags:
-            previous: '‹ Föregående'
-            next: 'Nästa ›'
+            previous: Föregående
+            next: Nästa
           expose:
             items_per_page_label: 'Inlägg per sida'
             items_per_page_options_all_label: '- Alla -'
diff --git a/conf/cmi/language/sv/views.view.glossary.yml b/conf/cmi/language/sv/views.view.glossary.yml
index 7b112f029..a3b0a6d94 100644
--- a/conf/cmi/language/sv/views.view.glossary.yml
+++ b/conf/cmi/language/sv/views.view.glossary.yml
@@ -14,8 +14,8 @@ display:
       pager:
         options:
           tags:
-            next: ››
-            previous: ‹‹
+            next: Nästa
+            previous: Föregående
           expose:
             items_per_page_label: 'Inlägg per sida'
             items_per_page_options_all_label: '- Alla -'
diff --git a/conf/cmi/language/sv/views.view.helfi_redirect.yml b/conf/cmi/language/sv/views.view.helfi_redirect.yml
index 08b9bf6d3..687d6835b 100644
--- a/conf/cmi/language/sv/views.view.helfi_redirect.yml
+++ b/conf/cmi/language/sv/views.view.helfi_redirect.yml
@@ -1,71 +1,83 @@
-label: Omdirigera
-description: 'Lista över omdirigeringar'
 display:
   default:
-    display_title: Förvald
     display_options:
-      title: Omdirigera
-      fields:
-        redirect_bulk_form:
-          action_title: 'Med urval'
-        redirect_source__path:
-          label: Från
-          separator: ', '
-        is_custom:
-          label: Anpassad
-          separator: ', '
-        created:
-          label: Skapad
-        status:
-          label: Publicerad
-          separator: ', '
-      pager:
-        options:
-          tags:
-            next: 'next ›'
-            previous: '‹ previous'
-            first: '« first'
-            last: 'last »'
-          expose:
-            items_per_page_label: 'Inlägg per sida'
-            items_per_page_options_all_label: '- Alla -'
-            offset_label: Kompensera
-      exposed_form:
-        options:
-          submit_button: Filtrera
-          reset_button_label: Återställ
-          exposed_sorts_label: 'Sortera efter'
-          sort_asc_label: Stigande
-          sort_desc_label: Fallande
-      empty:
-        area_text_custom:
-          content: 'Det finns ingen omdirigering ännu.'
       filters:
+        status_code:
+          group_info:
+            group_items:
+              1:
+                title: '300 Flera val'
+              2:
+                title: '301 Flyttad permanent'
+              3:
+                title: '302 Hittad'
+              4:
+                title: '303 Se annan'
+              5:
+                title: '304 Ej modifierad'
+              6:
+                title: '305 Använd proxy'
+              7:
+                title: '307 Tillfällig omdirigering'
+            label: Statuskod
+          expose:
+            label: Statuskod
         redirect_source__path:
           expose:
             label: Från
         redirect_redirect__uri:
           expose:
             label: Till
-        status_code:
-          expose:
-            label: Statuskod
-          group_info:
-            label: Statuskod
-            group_items:
-              7:
-                title: '300 Multiple Choices'
         language:
           expose:
-            label: Ursprungsspråk
+            label: Originalspråk
         is_custom:
           expose:
-            label: Anpassad
+            label: Användarskapad
         status:
           expose:
             label: Publicerad
+      exposed_form:
+        options:
+          submit_button: Filtrera
+          reset_button_label: Återställ
+          exposed_sorts_label: 'Sortera efter'
+          sort_asc_label: Stigande
+          sort_desc_label: Fallande
+      pager:
+        options:
+          expose:
+            items_per_page_label: 'Inlägg per sida'
+            items_per_page_options_all_label: '- Alla -'
+            offset_label: Kompensera
+          tags:
+            previous: Föregående
+            next: Nästa
+            first: Första
+            last: Sista
+      fields:
+        redirect_bulk_form:
+          action_title: 'Med valda'
+        redirect_source__path:
+          label: Från
+          separator: ', '
+        created:
+          label: Skapad
+        is_custom:
+          label: Användarskapad
+          separator: ', '
+        status:
+          label: Publicerad
+          separator: ', '
+      empty:
+        area_text_custom:
+          content: 'Det finns inga omdirigeringar ännu.'
+      title: Redirect
+    display_title: Förvald
   page_1:
     display_title: Sida
     display_options:
       menu:
-        title: Omdirigera
+        title: Redirect
+label: Redirect
+description: 'List of redirects'
diff --git a/conf/cmi/language/sv/views.view.locked_content.yml b/conf/cmi/language/sv/views.view.locked_content.yml
index 83dfa1e44..541df435e 100644
--- a/conf/cmi/language/sv/views.view.locked_content.yml
+++ b/conf/cmi/language/sv/views.view.locked_content.yml
@@ -1,4 +1,3 @@
-label: 'Locked content'
 display:
   default:
     display_title: Förvald
@@ -13,10 +12,10 @@ display:
       pager:
         options:
           tags:
-            previous: ‹‹
-            next: ››
-            first: '« Första'
-            last: 'Sista »'
+            previous: Föregående
+            next: Nästa
+            first: Första
+            last: Sista
           expose:
             items_per_page_label: 'Inlägg per sida'
             items_per_page_options_all_label: '- Alla -'
@@ -31,7 +30,7 @@ display:
           label: Innehållstyp
           separator: ', '
         name:
-          label: 'Lock owner'
+          label: 'Lås ägare'
           separator: ', '
         operations:
           label: Funktioner
@@ -44,15 +43,16 @@ display:
             group_items:
               1:
                 title: Publicerad
-        type:
-          expose:
-            label: Innehållstyp
         title:
           expose:
             label: Titel
-      title: 'Locked content'
+        type:
+          expose:
+            label: Innehållstyp
+      title: 'Låst innehåll'
   page_1:
     display_title: Sida
     display_options:
       menu:
-        title: 'Locked content'
+        title: 'Låst innehåll'
+label: 'Låst innehåll'
diff --git a/conf/cmi/language/sv/views.view.media.yml b/conf/cmi/language/sv/views.view.media.yml
index 1ad05ea49..3fe9b899e 100644
--- a/conf/cmi/language/sv/views.view.media.yml
+++ b/conf/cmi/language/sv/views.view.media.yml
@@ -32,10 +32,10 @@ display:
       pager:
         options:
           tags:
-            next: 'Nästa ›'
-            previous: '‹ Föregående'
-            first: '« Första'
-            last: 'Sista »'
+            next: Nästa
+            previous: Föregående
+            first: Första
+            last: Sista
           expose:
             items_per_page_label: 'Inlägg per sida'
             items_per_page_options_all_label: '- Alla -'
diff --git a/conf/cmi/language/sv/views.view.media_library.yml b/conf/cmi/language/sv/views.view.media_library.yml
index 0370700e3..7f95d4390 100644
--- a/conf/cmi/language/sv/views.view.media_library.yml
+++ b/conf/cmi/language/sv/views.view.media_library.yml
@@ -8,13 +8,13 @@ display:
           action_title: Åtgärd
       pager:
         options:
+          tags:
+            next: Nästa
+            previous: Föregående
           expose:
             items_per_page_label: 'Inlägg per sida'
             items_per_page_options_all_label: '- Alla -'
             offset_label: Kompensera
-          tags:
-            next: ››
-            previous: ‹‹
       exposed_form:
         options:
           submit_button: 'Tillämpa filter'
@@ -44,6 +44,8 @@ display:
             group_items:
               1:
                 title: Publicerad
+              2:
+                title: 'Ej publicerad'
         name:
           expose:
             label: Namn
diff --git a/conf/cmi/language/sv/views.view.paragraphs_library.yml b/conf/cmi/language/sv/views.view.paragraphs_library.yml
index 43c1c0c1f..82509c655 100644
--- a/conf/cmi/language/sv/views.view.paragraphs_library.yml
+++ b/conf/cmi/language/sv/views.view.paragraphs_library.yml
@@ -12,8 +12,8 @@ display:
       pager:
         options:
           tags:
-            previous: ‹‹
-            next: ››
+            previous: Föregående
+            next: Nästa
           expose:
             items_per_page_label: 'Inlägg per sida'
             items_per_page_options_all_label: '- Alla -'
diff --git a/conf/cmi/language/sv/views.view.redirect.yml b/conf/cmi/language/sv/views.view.redirect.yml
index 74efe8175..75e1656fe 100644
--- a/conf/cmi/language/sv/views.view.redirect.yml
+++ b/conf/cmi/language/sv/views.view.redirect.yml
@@ -1,5 +1,5 @@
-label: Omdirigera
-description: 'Lista över omdirigeringar'
+label: Redirect
+description: 'List of redirects'
 display:
   default:
     display_title: Förvald
@@ -14,17 +14,17 @@ display:
       pager:
         options:
           tags:
-            previous: '‹ previous'
-            next: 'next ›'
-            first: '« first'
-            last: 'last »'
+            previous: Föregående
+            next: Nästa
+            first: Första
+            last: Sista
           expose:
             items_per_page_label: 'Inlägg per sida'
             items_per_page_options_all_label: '- Alla -'
             offset_label: Kompensera
       fields:
         redirect_bulk_form:
-          action_title: 'Med urval'
+          action_title: 'Med valda'
         redirect_source__path:
           label: Från
           separator: ', '
@@ -44,13 +44,25 @@ display:
             label: Statuskod
             group_items:
               7:
-                title: '300 Multiple Choices'
+                title: '307 Tillfällig omdirigering'
+              1:
+                title: '300 Flera val'
+              2:
+                title: '301 Flyttad permanent'
+              3:
+                title: '302 Hittad'
+              4:
+                title: '303 Se annan'
+              5:
+                title: '304 Ej modifierad'
+              6:
+                title: '305 Använd proxy'
         language:
           expose:
-            label: Ursprungsspråk
-      title: Omdirigera
+            label: Originalspråk
+      title: Redirect
       empty:
         area_text_custom:
-          content: 'Det finns ingen omdirigering ännu.'
+          content: 'Det finns inga omdirigeringar ännu.'
   page_1:
     display_title: Sida
diff --git a/conf/cmi/language/sv/views.view.scheduler_scheduled_content.yml b/conf/cmi/language/sv/views.view.scheduler_scheduled_content.yml
index f3e391d66..45a83e65d 100644
--- a/conf/cmi/language/sv/views.view.scheduler_scheduled_content.yml
+++ b/conf/cmi/language/sv/views.view.scheduler_scheduled_content.yml
@@ -14,10 +14,10 @@ display:
           label: Författare
           separator: ', '
         status:
-          label: Status
           settings:
             format_custom_true: Publicerad
             format_custom_false: 'Ej publicerad'
+          label: Status
           separator: ', '
         publish_on:
           label: 'Publicera den'
@@ -35,8 +35,6 @@ display:
           expose:
             label: Innehållstyp
         status:
-          expose:
-            label: Status
           group_info:
             label: 'Status för publicering'
             group_items:
@@ -44,6 +42,8 @@ display:
                 title: Publicerad
               2:
                 title: 'Ej publicerad'
+          expose:
+            label: Status
         langcode:
           expose:
             label: Språk
@@ -57,14 +57,14 @@ display:
       pager:
         options:
           tags:
-            previous: '‹ previous'
-            next: 'next ›'
-            first: '« first'
-            last: 'last »'
-      title: 'Scheduled Content'
+            previous: Föregående
+            next: Nästa
+            first: Första
+            last: Sista
+      title: 'Schemalagt innehåll'
       empty:
         area_text_custom:
-          content: 'No scheduled content.'
+          content: 'Inget schemalagt innehåll.'
     display_title: Förvald
   overview:
     display_options:
@@ -72,7 +72,7 @@ display:
         title: Schemalagd
       tab_options:
         title: Innehåll
-    display_title: 'Content Overview'
+    display_title: Innehållsöversikt
   user_page:
     display_options:
       menu:
@@ -83,5 +83,5 @@ display:
         uid:
           exception:
             title: Alla
-label: 'Scheduled content'
-description: 'Find and manage scheduled content.'
+label: 'Schemalagt innehåll'
+description: 'Hitta och hantera schemalagt innehåll.'
diff --git a/conf/cmi/language/sv/views.view.taxonomy_term.yml b/conf/cmi/language/sv/views.view.taxonomy_term.yml
index 10192b881..2f3e63b67 100644
--- a/conf/cmi/language/sv/views.view.taxonomy_term.yml
+++ b/conf/cmi/language/sv/views.view.taxonomy_term.yml
@@ -7,8 +7,8 @@ display:
       pager:
         options:
           tags:
-            next: ››
-            previous: ‹‹
+            next: Nästa
+            previous: Föregående
           expose:
             items_per_page_label: 'Inlägg per sida'
             items_per_page_options_all_label: '- Alla -'
diff --git a/conf/cmi/language/sv/views.view.user_admin_people.yml b/conf/cmi/language/sv/views.view.user_admin_people.yml
index 704e77adc..f442ec0fe 100644
--- a/conf/cmi/language/sv/views.view.user_admin_people.yml
+++ b/conf/cmi/language/sv/views.view.user_admin_people.yml
@@ -34,10 +34,10 @@ display:
       pager:
         options:
           tags:
-            next: 'Nästa ›'
-            previous: '‹ Föregående'
-            first: '« Första'
-            last: 'Sista »'
+            next: Nästa
+            previous: Föregående
+            first: Första
+            last: Sista
           expose:
             items_per_page_label: 'Inlägg per sida'
             items_per_page_options_all_label: '- Alla -'
diff --git a/conf/cmi/language/uk/views.view.content.yml b/conf/cmi/language/uk/views.view.content.yml
index 58260e0e7..657dd9ba0 100644
--- a/conf/cmi/language/uk/views.view.content.yml
+++ b/conf/cmi/language/uk/views.view.content.yml
@@ -57,6 +57,8 @@ display:
             group_items:
               1:
                 title: Опубліковано
+              2:
+                title: Неопубліковано
         langcode:
           expose:
             label: Мова
diff --git a/conf/cmi/language/uk/views.view.helfi_redirect.yml b/conf/cmi/language/uk/views.view.helfi_redirect.yml
index f10d7dc81..0026e0565 100644
--- a/conf/cmi/language/uk/views.view.helfi_redirect.yml
+++ b/conf/cmi/language/uk/views.view.helfi_redirect.yml
@@ -21,10 +21,10 @@ display:
       pager:
         options:
           tags:
-            next: 'наступна ›'
-            previous: '‹ попередня'
-            first: '« перша'
-            last: 'остання »'
+            next: Наступний
+            previous: Попередній
+            first: Перший
+            last: Останній
           expose:
             items_per_page_label: 'Елементів на сторінку'
             items_per_page_options_all_label: '- Усе -'
diff --git a/conf/cmi/language/uk/views.view.media_library.yml b/conf/cmi/language/uk/views.view.media_library.yml
index b4dd85a10..eaf0385a3 100644
--- a/conf/cmi/language/uk/views.view.media_library.yml
+++ b/conf/cmi/language/uk/views.view.media_library.yml
@@ -46,6 +46,8 @@ display:
             group_items:
               1:
                 title: Опубліковано
+              2:
+                title: Неопубліковано
         name:
           expose:
             label: "Назва (ім'я)"
diff --git a/conf/cmi/language/zh-hans/views.view.content.yml b/conf/cmi/language/zh-hans/views.view.content.yml
index e82ab62ed..7675a7ca8 100644
--- a/conf/cmi/language/zh-hans/views.view.content.yml
+++ b/conf/cmi/language/zh-hans/views.view.content.yml
@@ -57,6 +57,8 @@ display:
             group_items:
               1:
                 title: 已发布
+              2:
+                title: 未发布
         langcode:
           expose:
             label: 语言
diff --git a/conf/cmi/language/zh-hans/views.view.helfi_redirect.yml b/conf/cmi/language/zh-hans/views.view.helfi_redirect.yml
index 7eeb831b9..8cee953ae 100644
--- a/conf/cmi/language/zh-hans/views.view.helfi_redirect.yml
+++ b/conf/cmi/language/zh-hans/views.view.helfi_redirect.yml
@@ -23,8 +23,8 @@ display:
           tags:
             next: '下一页 ›'
             previous: '‹ 上一页'
-            first: '« 第一页'
-            last: '末页 »'
+            first: 第一
+            last: 末页
           expose:
             items_per_page_label: 每页条目数
             items_per_page_options_all_label: '- 全部 -'
diff --git a/conf/cmi/language/zh-hans/views.view.media_library.yml b/conf/cmi/language/zh-hans/views.view.media_library.yml
index 05533cccf..b89cc296f 100644
--- a/conf/cmi/language/zh-hans/views.view.media_library.yml
+++ b/conf/cmi/language/zh-hans/views.view.media_library.yml
@@ -46,6 +46,8 @@ display:
             group_items:
               1:
                 title: 已发布
+              2:
+                title: 未发布
         name:
           expose:
             label: 名称
diff --git a/conf/cmi/media.type.image.yml b/conf/cmi/media.type.image.yml
index 87921878c..6d100b13c 100644
--- a/conf/cmi/media.type.image.yml
+++ b/conf/cmi/media.type.image.yml
@@ -10,7 +10,7 @@ third_party_settings:
 _core:
   default_config_hash: LX01N5rjJ7mTGbJeTSQJuX_ZvFCUl0sCBkxSR3a18Bg
 id: image
-label: Image
+label: 'Image (supported in other languages)'
 description: 'Use local image for reusable media.'
 source: image
 queue_thumbnail_downloads: false
diff --git a/conf/cmi/search_api.index.news_terms.yml b/conf/cmi/search_api.index.news_terms.yml
index 7998b4448..0c8d15d23 100644
--- a/conf/cmi/search_api.index.news_terms.yml
+++ b/conf/cmi/search_api.index.news_terms.yml
@@ -3,6 +3,7 @@ langcode: en
 status: true
 dependencies:
   config:
+    - field.storage.taxonomy_term.field_location
     - search_api.server.default
   module:
     - helfi_etusivu
@@ -25,6 +26,14 @@ field_settings:
     label: 'Entity type'
     property_path: search_api_entity_type
     type: string
+  field_location:
+    label: Location
+    datasource_id: 'entity:taxonomy_term'
+    property_path: field_location
+    type: location
+    dependencies:
+      config:
+        - field.storage.taxonomy_term.field_location
   name:
     label: Name
     datasource_id: 'entity:taxonomy_term'
diff --git a/conf/cmi/search_api.index.suggestions.yml b/conf/cmi/search_api.index.suggestions.yml
new file mode 100644
index 000000000..dc643682d
--- /dev/null
+++ b/conf/cmi/search_api.index.suggestions.yml
@@ -0,0 +1,77 @@
+uuid: 1a06b7cd-eb89-4e5d-bb5d-2bfb922b9543
+langcode: en
+status: true
+dependencies:
+  config:
+    - search_api.server.default
+  module:
+    - helfi_annif
+    - helfi_etusivu
+    - helfi_react_search
+id: suggestions
+name: Suggestions
+description: ''
+read_only: false
+field_settings:
+  keywords:
+    label: Keywords
+    datasource_id: 'entity:suggested_topics'
+    property_path: keywords_scored
+    type: scored_item
+  parent_bundle:
+    label: 'Parent bundle'
+    datasource_id: 'entity:suggested_topics'
+    property_path: parent_bundle
+    type: string
+    dependencies:
+      module:
+        - helfi_annif
+  parent_id:
+    label: 'Parent ID'
+    datasource_id: 'entity:suggested_topics'
+    property_path: parent_id
+    type: string
+    dependencies:
+      module:
+        - helfi_annif
+  parent_instance:
+    label: 'Parent instance'
+    datasource_id: 'entity:suggested_topics'
+    property_path: parent_instance
+    type: string
+    dependencies:
+      module:
+        - helfi_annif
+  parent_type:
+    label: 'Parent type'
+    datasource_id: 'entity:suggested_topics'
+    property_path: parent_type
+    type: string
+    dependencies:
+      module:
+        - helfi_annif
+datasource_settings:
+  'entity:suggested_topics': {  }
+processor_settings:
+  add_url: {  }
+  aggregated_field: {  }
+  custom_value: {  }
+  district_image_absolute_url: {  }
+  entity_type: {  }
+  language_with_fallback: {  }
+  main_image_url: {  }
+  project_execution_schedule: {  }
+  project_image_absolute_url: {  }
+  project_plan_schedule: {  }
+  rendered_item: {  }
+  scored_reference: {  }
+  uuid_langcode: {  }
+tracker_settings:
+  default:
+    indexing_order: fifo
+options:
+  cron_limit: 50
+  delete_on_fail: true
+  index_directly: true
+  track_changes_in_references: true
+server: default
diff --git a/conf/cmi/views.view.content.yml b/conf/cmi/views.view.content.yml
index 1e5682038..2468bc9c1 100644
--- a/conf/cmi/views.view.content.yml
+++ b/conf/cmi/views.view.content.yml
@@ -318,10 +318,10 @@ display:
           pagination_heading_level: h4
           items_per_page: 50
           tags:
-            next: 'Next ›'
-            previous: '‹ Previous'
-            first: '« First'
-            last: 'Last »'
+            next: Next
+            previous: Previous
+            first: First
+            last: Last
       exposed_form:
         type: basic
         options:
@@ -377,8 +377,6 @@ display:
             multiple: false
             remember_roles:
               authenticated: authenticated
-              anonymous: '0'
-              administrator: '0'
           is_grouped: false
           group_info:
             label: ''
@@ -419,8 +417,6 @@ display:
             multiple: false
             remember_roles:
               authenticated: authenticated
-              anonymous: '0'
-              administrator: '0'
             reduce: false
           is_grouped: false
           group_info:
@@ -474,11 +470,11 @@ display:
             default_group: All
             default_group_multiple: {  }
             group_items:
-              -
+              1:
                 title: Published
                 operator: '='
                 value: '1'
-              -
+              2:
                 title: Unpublished
                 operator: '='
                 value: '0'
@@ -524,14 +520,6 @@ display:
             multiple: false
             remember_roles:
               authenticated: authenticated
-              anonymous: '0'
-              read_only: '0'
-              content_producer: '0'
-              editor: '0'
-              admin: '0'
-              menu_api: '0'
-              news_producer: '0'
-              debug_api: '0'
             reduce: true
           is_grouped: false
           group_info:
@@ -560,7 +548,7 @@ display:
       filter_groups:
         operator: AND
         groups:
-          - AND
+          1: AND
       style:
         type: table
         options:
diff --git a/conf/cmi/views.view.helfi_redirect.yml b/conf/cmi/views.view.helfi_redirect.yml
index cb34781c6..1cbff5a7f 100644
--- a/conf/cmi/views.view.helfi_redirect.yml
+++ b/conf/cmi/views.view.helfi_redirect.yml
@@ -9,7 +9,7 @@ dependencies:
     - redirect
     - user
 _core:
-  default_config_hash: T8ZZY-2rwfH9QPXho30lLjOnPsWsLzhy6YGOTix228k
+  default_config_hash: tH9tiLAhaR2LdCQkjUf5FTsU-8Adc_4pBxrNJPtBKL4
 id: helfi_redirect
 label: Redirect
 module: views
@@ -367,10 +367,10 @@ display:
           total_pages: null
           id: 0
           tags:
-            next: 'next ›'
-            previous: '‹ previous'
-            first: '« first'
-            last: 'last »'
+            next: Next
+            previous: Previous
+            first: First
+            last: Last
           expose:
             items_per_page: false
             items_per_page_label: 'Items per page'
diff --git a/conf/cmi/views.view.locked_content.yml b/conf/cmi/views.view.locked_content.yml
index b63c8b31d..da194f517 100644
--- a/conf/cmi/views.view.locked_content.yml
+++ b/conf/cmi/views.view.locked_content.yml
@@ -439,10 +439,10 @@ display:
           total_pages: null
           id: 0
           tags:
-            next: ››
-            previous: ‹‹
-            first: '« First'
-            last: 'Last »'
+            next: Next
+            previous: Previous
+            first: First
+            last: Last
           expose:
             items_per_page: false
             items_per_page_label: 'Items per page'
diff --git a/conf/cmi/views.view.media_library.yml b/conf/cmi/views.view.media_library.yml
index a112f2312..7dc4d6f0b 100644
--- a/conf/cmi/views.view.media_library.yml
+++ b/conf/cmi/views.view.media_library.yml
@@ -144,8 +144,8 @@ display:
           total_pages: null
           id: 0
           tags:
-            next: ››
-            previous: ‹‹
+            next: Next
+            previous: Previous
           expose:
             items_per_page: false
             items_per_page_label: 'Items per page'
@@ -271,11 +271,11 @@ display:
             default_group: All
             default_group_multiple: {  }
             group_items:
-              -
+              1:
                 title: Published
                 operator: '='
                 value: '1'
-              -
+              2:
                 title: Unpublished
                 operator: '='
                 value: '0'
@@ -477,13 +477,13 @@ display:
     cache_metadata:
       max-age: 0
       contexts:
+        - 'languages:language_content'
         - 'languages:language_interface'
         - url
         - url.query_args
         - 'url.query_args:sort_by'
         - user
         - user.permissions
-        - 'languages:language_content'
       tags: {  }
   page:
     id: page
@@ -1083,10 +1083,10 @@ display:
           total_pages: null
           id: 0
           tags:
-            next: ››
-            previous: ‹‹
-            first: '« First'
-            last: 'Last »'
+            next: Next
+            previous: Previous
+            first: First
+            last: Last
           expose:
             items_per_page: false
             items_per_page_label: 'Items per page'
diff --git a/conf/cmi/views.view.scheduler_scheduled_content.yml b/conf/cmi/views.view.scheduler_scheduled_content.yml
index a62e071a5..ec18cbb75 100644
--- a/conf/cmi/views.view.scheduler_scheduled_content.yml
+++ b/conf/cmi/views.view.scheduler_scheduled_content.yml
@@ -2,8 +2,6 @@ uuid: 340f2242-c480-44f6-999d-93eaf27abb79
 langcode: en
 status: true
 dependencies:
-  config:
-    - system.menu.admin
   module:
     - node
     - scheduler
@@ -535,10 +533,10 @@ display:
           total_pages: null
           id: 0
           tags:
-            next: 'next ›'
-            previous: '‹ previous'
-            first: '« first'
-            last: 'last »'
+            next: Next
+            previous: Previous
+            first: First
+            last: Last
       exposed_form:
         type: basic
         options:
@@ -723,8 +721,8 @@ display:
             remember: false
             multiple: false
             remember_roles:
-              anonymous: '0'
               authenticated: '0'
+              anonymous: '0'
               administrator: '0'
           is_grouped: true
           group_info:
diff --git a/tools/make/project/.keepme b/public/modules/custom/helfi_alt_lang_fallback/tests/.keep
similarity index 100%
rename from tools/make/project/.keepme
rename to public/modules/custom/helfi_alt_lang_fallback/tests/.keep
diff --git a/public/modules/custom/helfi_annif/README.md b/public/modules/custom/helfi_annif/README.md
index 4fc905013..c47117385 100644
--- a/public/modules/custom/helfi_annif/README.md
+++ b/public/modules/custom/helfi_annif/README.md
@@ -6,6 +6,12 @@ Keywords are generated for entities that have `field_annif_keywords` with `hook_
 
 See the API documentation at: [https://ai.finto.fi/v1/ui/](https://ai.finto.fi/v1/ui/).
 
+## Suggestions across hel.fi instances
+
+**This feature is work in progress.**
+
+Suggested topics entities are synced to suggestions search api index. In the future, other instances communicate with etusivu so that suggested topics entity is created for their content, and they can search suggestions for their content from the shared search index.
+
 ## Text converter
 
 The AI API accepts raw text only. Drupal content must be converted to UTF-8 encoded raw text. This is achieved with `TextConverterManager`.
diff --git a/public/modules/custom/helfi_annif/config/schema/helfi_annif.schema.yml b/public/modules/custom/helfi_annif/config/schema/helfi_annif.schema.yml
index aa3367c7c..0c53da8e5 100644
--- a/public/modules/custom/helfi_annif/config/schema/helfi_annif.schema.yml
+++ b/public/modules/custom/helfi_annif/config/schema/helfi_annif.schema.yml
@@ -18,3 +18,19 @@ field.value.suggested_topics_reference:
     value:
       type: label
       label: Value
+
+field.storage_settings.scored_entity_reference:
+  type: field.storage_settings.entity_reference
+  label: 'Scored reference field storage settings'
+
+field.field_settings.scored_entity_reference:
+  type: field.field_settings.entity_reference
+  label: 'Scored reference settings'
+
+field.storage_settings.suggested_topics_reference:
+  type: field.storage_settings.entity_reference
+  label: 'Suggested topics field storage settings'
+
+field.field_settings.suggested_topics_reference:
+  type: field.field_settings.entity_reference
+  label: 'Suggested topics reference settings'
diff --git a/public/modules/custom/helfi_annif/helfi_annif.install b/public/modules/custom/helfi_annif/helfi_annif.install
index bb7a041f5..bbc9b0ef5 100644
--- a/public/modules/custom/helfi_annif/helfi_annif.install
+++ b/public/modules/custom/helfi_annif/helfi_annif.install
@@ -7,6 +7,9 @@
 
 declare(strict_types=1);
 
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Field\BaseFieldDefinition;
+
 /**
  * Create entity definitions for annif-fields.
  */
@@ -96,3 +99,29 @@ function helfi_annif_update_9003(): void {
 
   // @todo remove obsolete field annif_keywords in a future update hook.
 }
+
+/**
+ * Updates entity fields for suggested_topics.
+ */
+function helfi_annif_update_9004(): void {
+  $fields['parent_id'] = BaseFieldDefinition::create('string')
+    ->setLabel(t('Parent ID'))
+    ->setDescription(t('The ID of the parent entity of which this entity is referenced.'))
+    ->setSetting('is_ascii', TRUE);
+
+  $fields['parent_type'] = BaseFieldDefinition::create('string')
+    ->setLabel(t('Parent type'))
+    ->setDescription(t('The entity parent type to which this entity is referenced.'))
+    ->setSetting('is_ascii', TRUE)
+    ->setSetting('max_length', EntityTypeInterface::ID_MAX_LENGTH);
+
+  $fields['parent_instance'] = BaseFieldDefinition::create('string')
+    ->setLabel(t('Parent instance'))
+    ->setDescription(t('The name of the instance where this entity is located at.'))
+    ->setSetting('is_ascii', TRUE);
+
+  foreach ($fields as $name => $field) {
+    \Drupal::entityDefinitionUpdateManager()
+      ->installFieldStorageDefinition($name, 'suggested_topics', 'helfi_annif', $field);
+  }
+}
diff --git a/public/modules/custom/helfi_annif/helfi_annif.services.yml b/public/modules/custom/helfi_annif/helfi_annif.services.yml
index f9a122bcc..ccf023498 100644
--- a/public/modules/custom/helfi_annif/helfi_annif.services.yml
+++ b/public/modules/custom/helfi_annif/helfi_annif.services.yml
@@ -22,3 +22,5 @@ services:
   Drupal\helfi_annif\TextConverter\RenderTextConverter:
     tags:
       - { name: helfi_annif.text_converter, priority: -1 }
+
+  Drupal\helfi_annif\EventSubscriber\SearchApiSubscriber: ~
diff --git a/public/modules/custom/helfi_annif/src/Entity/SuggestedTopics.php b/public/modules/custom/helfi_annif/src/Entity/SuggestedTopics.php
index c48bc0043..23c9ca96c 100644
--- a/public/modules/custom/helfi_annif/src/Entity/SuggestedTopics.php
+++ b/public/modules/custom/helfi_annif/src/Entity/SuggestedTopics.php
@@ -67,6 +67,28 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type): a
       ])
       ->setDisplayConfigurable('view', TRUE);
 
+    $fields['parent_id'] = BaseFieldDefinition::create('string')
+      ->setLabel(t('Parent ID'))
+      ->setDescription(t('The ID of the parent entity of which this entity is referenced.'))
+      ->setSetting('is_ascii', TRUE);
+
+    $fields['parent_type'] = BaseFieldDefinition::create('string')
+      ->setLabel(t('Parent type'))
+      ->setDescription(t('The entity parent type to which this entity is referenced.'))
+      ->setSetting('is_ascii', TRUE)
+      ->setSetting('max_length', EntityTypeInterface::ID_MAX_LENGTH);
+
+    $fields['parent_bundle'] = BaseFieldDefinition::create('string')
+      ->setLabel(t('Parent bundle'))
+      ->setDescription(t('The entity parent bundle to which this entity is referenced.'))
+      ->setSetting('is_ascii', TRUE)
+      ->setSetting('max_length', EntityTypeInterface::ID_MAX_LENGTH);
+
+    $fields['parent_instance'] = BaseFieldDefinition::create('string')
+      ->setLabel(t('Parent instance'))
+      ->setDescription(t('The name of the instance where this entity is located at.'))
+      ->setSetting('is_ascii', TRUE);
+
     return $fields;
   }
 
diff --git a/public/modules/custom/helfi_annif/src/EventSubscriber/SearchApiSubscriber.php b/public/modules/custom/helfi_annif/src/EventSubscriber/SearchApiSubscriber.php
new file mode 100644
index 000000000..55f739d69
--- /dev/null
+++ b/public/modules/custom/helfi_annif/src/EventSubscriber/SearchApiSubscriber.php
@@ -0,0 +1,33 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\helfi_annif\EventSubscriber;
+
+use Drupal\search_api\Event\MappingFieldTypesEvent;
+use Drupal\search_api\Event\SearchApiEvents;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Search api event subscriber.
+ */
+final class SearchApiSubscriber implements EventSubscriberInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents(): array {
+    return [
+      SearchApiEvents::MAPPING_FIELD_TYPES => 'mapFieldTypes',
+    ];
+  }
+
+  /**
+   * Map custom field types.
+   */
+  public function mapFieldTypes(MappingFieldTypesEvent $event): void {
+    $mapping = &$event->getFieldTypeMapping();
+    $mapping['scored_item'] = 'scored_item';
+  }
+
+}
diff --git a/public/modules/custom/helfi_annif/src/Plugin/Field/FieldType/ScoredEntityReferenceItem.php b/public/modules/custom/helfi_annif/src/Plugin/Field/FieldType/ScoredEntityReferenceItem.php
index 61e264821..0ada6492d 100644
--- a/public/modules/custom/helfi_annif/src/Plugin/Field/FieldType/ScoredEntityReferenceItem.php
+++ b/public/modules/custom/helfi_annif/src/Plugin/Field/FieldType/ScoredEntityReferenceItem.php
@@ -15,6 +15,8 @@
 /**
  * Defines the 'scored_entity_reference' field type.
  *
+ * @property mixed $score Item score.
+ *
  * @see \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem
  * @see \Drupal\Core\Field\Plugin\Field\FieldType\FloatItem
  */
diff --git a/public/modules/custom/helfi_annif/src/Plugin/Field/FieldType/SuggestedTopicsReferenceItem.php b/public/modules/custom/helfi_annif/src/Plugin/Field/FieldType/SuggestedTopicsReferenceItem.php
index cf27bfc5b..7eca830f9 100644
--- a/public/modules/custom/helfi_annif/src/Plugin/Field/FieldType/SuggestedTopicsReferenceItem.php
+++ b/public/modules/custom/helfi_annif/src/Plugin/Field/FieldType/SuggestedTopicsReferenceItem.php
@@ -9,6 +9,7 @@
 use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\helfi_annif\Entity\SuggestedTopics;
 
 /**
  * Defines the 'suggested_topics_reference' field type.
@@ -58,12 +59,16 @@ public function fieldSettingsForm(array $form, FormStateInterface $form_state):
   /**
    * {@inheritdoc}
    */
-  public function preSave() {
-    if ($this->hasNewEntity()) {
-      $this->entity->save();
-    }
+  public function postSave($update): bool {
+    $parent = $this->getEntity();
+    $entity = $this->entity;
+    assert($entity instanceof SuggestedTopics);
+
+    $entity
+      ->set('parent_id', $parent->id())
+      ->save();
 
-    parent::preSave();
+    return FALSE;
   }
 
   /**
diff --git a/public/modules/custom/helfi_annif/src/Plugin/Field/FieldWidget/SuggestedTopicsReferenceWidget.php b/public/modules/custom/helfi_annif/src/Plugin/Field/FieldWidget/SuggestedTopicsReferenceWidget.php
index 6b864d480..e803757a3 100644
--- a/public/modules/custom/helfi_annif/src/Plugin/Field/FieldWidget/SuggestedTopicsReferenceWidget.php
+++ b/public/modules/custom/helfi_annif/src/Plugin/Field/FieldWidget/SuggestedTopicsReferenceWidget.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Drupal\helfi_annif\Entity\SuggestedTopics;
+use Drupal\helfi_api_base\Environment\Project;
 
 /**
  * Defines the 'suggested_topics_reference' field widget.
@@ -37,7 +38,11 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
     $hasTargetEntity = !empty($items[$delta]->target_id) && $items[$delta]->entity;
 
     /** @var \Drupal\helfi_annif\Entity\SuggestedTopics $entity */
-    $entity = $hasTargetEntity ? $items[$delta]->entity : SuggestedTopics::create([]);
+    $entity = $hasTargetEntity ? $items[$delta]->entity : SuggestedTopics::create([
+      'parent_type' => $items->getEntity()->getEntityTypeId(),
+      'parent_bundle' => $items->getEntity()->bundle(),
+      'parent_instance' => Project::ETUSIVU,
+    ]);
 
     $element['entity'] = [
       '#type' => 'value',
diff --git a/public/modules/custom/helfi_annif/src/Plugin/search_api/data_type/ScoredItemDataType.php b/public/modules/custom/helfi_annif/src/Plugin/search_api/data_type/ScoredItemDataType.php
new file mode 100644
index 000000000..e44162970
--- /dev/null
+++ b/public/modules/custom/helfi_annif/src/Plugin/search_api/data_type/ScoredItemDataType.php
@@ -0,0 +1,20 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\helfi_annif\Plugin\search_api\data_type;
+
+use Drupal\search_api\DataType\DataTypePluginBase;
+
+/**
+ * Provides a string data type.
+ *
+ * @SearchApiDataType(
+ *   id = "scored_item",
+ *   label = @Translation("Scored item"),
+ *   description = @Translation("Item with score."),
+ *   fallback_type = "string"
+ * )
+ */
+class ScoredItemDataType extends DataTypePluginBase {
+}
diff --git a/public/modules/custom/helfi_annif/src/Plugin/search_api/processor/ScoredReferenceProcessor.php b/public/modules/custom/helfi_annif/src/Plugin/search_api/processor/ScoredReferenceProcessor.php
new file mode 100644
index 000000000..ff002b7e1
--- /dev/null
+++ b/public/modules/custom/helfi_annif/src/Plugin/search_api/processor/ScoredReferenceProcessor.php
@@ -0,0 +1,90 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\helfi_annif\Plugin\search_api\processor;
+
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\helfi_annif\Plugin\Field\FieldType\ScoredEntityReferenceItem;
+use Drupal\search_api\Datasource\DatasourceInterface;
+use Drupal\search_api\Item\ItemInterface;
+use Drupal\search_api\Processor\ProcessorPluginBase;
+use Drupal\search_api\Processor\ProcessorProperty;
+
+/**
+ * Indexes uuid with langcode.
+ *
+ * @SearchApiProcessor(
+ *   id = "scored_reference",
+ *   label = @Translation("Scored references"),
+ *   description = @Translation("Indexes scored references"),
+ *   stages = {
+ *     "add_properties" = 0
+ *   },
+ *   locked = true,
+ *   hidden = true,
+ * )
+ */
+final class ScoredReferenceProcessor extends ProcessorPluginBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPropertyDefinitions(?DataSourceInterface $datasource = NULL) : array {
+    $properties = [];
+
+    if ($datasource) {
+      $propertyDefinitions = $datasource->getPropertyDefinitions();
+      foreach ($propertyDefinitions as $id => $definition) {
+        if (
+          $definition instanceof FieldDefinitionInterface &&
+          $definition->getType() === 'scored_entity_reference'
+        ) {
+          $properties[$id . '_scored'] = new ProcessorProperty([
+            'label' => $this->t('Scored reference'),
+            'description' => $this->t('Indexes referenced item labels with a score'),
+            'type' => 'scored_item',
+            'processor_id' => $this->getPluginId(),
+          ]);
+        }
+      }
+    }
+
+    return $properties;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addFieldValues(Iteminterface $item) : void {
+    $entity = $item->getOriginalObject()?->getValue();
+
+    if (!$entity instanceof ContentEntityInterface) {
+      return;
+    }
+
+    $objectSupport = $this->index->getServerInstance()->getBackendId() === 'elasticsearch';
+
+    foreach ($item->getFields() as $field) {
+      if ($field->getOriginalType() !== 'scored_item') {
+        continue;
+      }
+
+      $property = substr($field->getPropertyPath(), 0, -strlen("_scored"));
+
+      $scoredReferenceField = $entity->get($property);
+      foreach ($scoredReferenceField as $scoredReference) {
+        assert($scoredReference instanceof ScoredEntityReferenceItem);
+
+        $value = [
+          'score' => (float) $scoredReference->score,
+          'label' => $scoredReference->entity->id(),
+        ];
+
+        $field->addValue($objectSupport ? $value : json_encode($value));
+      }
+    }
+  }
+
+}
diff --git a/public/modules/custom/helfi_annif/tests/src/Kernel/SearchApi/Processor/ScoredReferenceProcessorTest.php b/public/modules/custom/helfi_annif/tests/src/Kernel/SearchApi/Processor/ScoredReferenceProcessorTest.php
new file mode 100644
index 000000000..3af3485b4
--- /dev/null
+++ b/public/modules/custom/helfi_annif/tests/src/Kernel/SearchApi/Processor/ScoredReferenceProcessorTest.php
@@ -0,0 +1,157 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\helfi_annif\Kernel\Processor;
+
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\node\Entity\Node;
+use Drupal\node\Entity\NodeType;
+use Drupal\search_api\Item\Field;
+use Drupal\search_api\Processor\ProcessorPropertyInterface;
+use Drupal\taxonomy\Entity\Term;
+use Drupal\taxonomy\Entity\Vocabulary;
+use Drupal\Tests\search_api\Kernel\PostRequestIndexingTrait;
+use Drupal\Tests\search_api\Kernel\Processor\ProcessorTestBase;
+
+/**
+ * Tests the scored reference processor.
+ *
+ * @group helfi_annif
+ */
+class ScoredReferenceProcessorTest extends ProcessorTestBase {
+
+  use PostRequestIndexingTrait;
+
+  /**
+   * Test vocabulary.
+   */
+  private Vocabulary $vocabulary;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'helfi_annif',
+    'helfi_api_base',
+    'taxonomy',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp($processor = NULL): void {
+    parent::setUp('scored_reference');
+
+    foreach (['suggested_topics', 'taxonomy_term'] as $entityType) {
+      $this->installEntitySchema($entityType);
+    }
+
+    $this->vocabulary = Vocabulary::create([
+      'vid' => 'tags',
+    ]);
+    $this->vocabulary->save();
+
+    NodeType::create([
+      'name' => $this->randomMachineName(),
+      'type' => 'test_node_bundle',
+    ])->save();
+
+    FieldStorageConfig::create([
+      'field_name' => 'test_keywords',
+      'entity_type' => 'node',
+      'type' => 'scored_entity_reference',
+    ])->save();
+
+    FieldConfig::create([
+      'field_name' => 'test_keywords',
+      'entity_type' => 'node',
+      'bundle' => 'test_node_bundle',
+      'label' => 'Test field',
+    ])->save();
+
+    $searchApiField = new Field($this->index, 'test_keywords');
+    $searchApiField->setType('scored_item');
+    $searchApiField->setPropertyPath('test_keywords_scored');
+    $searchApiField->setLabel('Test field');
+    $searchApiField->setDatasourceId('entity:node');
+
+    $this->index->addField($searchApiField);
+    $this->index->setOption('index_directly', TRUE);
+    $this->index->save();
+  }
+
+  /**
+   * Tests that field values are added correctly.
+   */
+  public function testDatasource() : void {
+    /** @var \Drupal\search_api\Utility\PluginHelperInterface $pluginHelper */
+    $pluginHelper = $this->container->get('search_api.plugin_helper');
+
+    $term = Term::create([
+      'name' => $this->randomMachineName(),
+      'vid' => $this->vocabulary->id(),
+    ]);
+    $term->save();
+
+    Node::create([
+      'title' => 'Test',
+      'type' => 'test_node_bundle',
+      'test_keywords' => [
+        [
+          'entity' => $term,
+          'score' => 0.5,
+        ],
+      ],
+    ])->save();
+
+    $datasource = $pluginHelper->createDatasourcePlugin($this->index, 'entity:node');
+    $sut = $pluginHelper->createProcessorPlugin($this->index, 'scored_reference');
+
+    $properties = $sut->getPropertyDefinitions(NULL);
+    $this->assertEmpty($properties);
+
+    $properties = $sut->getPropertyDefinitions($datasource);
+    $this->assertNotEmpty($properties);
+    $this->assertArrayHasKey('test_keywords_scored', $properties);
+    $this->assertInstanceOf(ProcessorPropertyInterface::class, $properties['test_keywords_scored']);
+  }
+
+  /**
+   * Tests that field values are added correctly.
+   */
+  public function testAddFieldValues() : void {
+    $term = Term::create([
+      'name' => $this->randomMachineName(),
+      'vid' => $this->vocabulary->id(),
+    ]);
+    $term->save();
+
+    Node::create([
+      'title' => 'Test',
+      'type' => 'test_node_bundle',
+      'test_keywords' => [
+        [
+          'entity' => $term,
+          'score' => 0.5,
+        ],
+      ],
+    ])->save();
+
+    $this->triggerPostRequestIndexing();
+    $query = $this->index->query();
+    $results = $query->execute();
+    $values = [];
+
+    /** @var \Drupal\search_api\Item\ItemInterface $result */
+    foreach ($results as $result) {
+      $field_values = $result->getField('test_keywords')->getValues();
+      $values[] = $field_values;
+    }
+
+    $this->assertCount(1, $values);
+    $this->assertNotEmpty($values[0]);
+  }
+
+}
diff --git a/public/modules/custom/helfi_etusivu/helfi_etusivu.deploy.php b/public/modules/custom/helfi_etusivu/helfi_etusivu.deploy.php
new file mode 100644
index 000000000..898f37d96
--- /dev/null
+++ b/public/modules/custom/helfi_etusivu/helfi_etusivu.deploy.php
@@ -0,0 +1,108 @@
+<?php
+
+/**
+ * @file
+ * Contains etusivu deploy hooks.
+ */
+
+declare(strict_types=1);
+
+/**
+ * UHF-9741: Set location for neighbourhoods.
+ */
+function helfi_etusivu_deploy_0001_news_neighbourhoods() : void {
+  $neighbourhoods = [
+    'Alppiharju' => [60.187933, 24.944132],
+    'Eira' => [60.155249, 24.938121],
+    'Etu-Töölö' => [60.173279, 24.923774],
+    'Haaga' => [60.221837, 24.896392],
+    'Hakaniemi' => [60.180465, 24.951613],
+    'Hermanni' => [60.195101, 24.966697],
+    'Hernesaari' => [60.149268, 24.924342],
+    'Herttoniemi' => [60.193202, 25.036063],
+    'Honkasuo' => [60.256156, 24.845279],
+    'Itäkeskus ja Vartiokylä' => [60.214989, 25.089473],
+    'Jätkäsaari' => [60.156744, 24.913439],
+    'Kaarela' => [60.251193, 24.881449],
+    'Kaartinkaupunki' => [60.165227, 24.948996],
+    'Kaivopuisto' => [60.155933, 24.955835],
+    'Kalasatama' => [60.185294, 24.980741],
+    'Kallio' => [60.184327, 24.949712],
+    'Kamppi' => [60.167335, 24.931190],
+    'Kannelmäki' => [60.241693, 24.885368],
+    'Karhusaari' => [60.250323, 25.220108],
+    'Katajanokka' => [60.166662, 24.969216],
+    'Keskusta' => [60.169519, 24.952272],
+    'Kluuvi' => [60.172580, 24.941144],
+    'Koivusaari' => [60.163196, 24.856204],
+    'Konala' => [60.236852, 24.845177],
+    'Koskela' => [60.218253, 24.966418],
+    'Kruununhaka' => [60.172352, 24.956470],
+    'Kruunuvuorenranta' => [60.166554, 25.022126],
+    'Kulosaari' => [60.185983, 25.008457],
+    'Kumpula' => [60.209094, 24.964821],
+    'Kuninkaantammi' => [60.261009, 24.890437],
+    'Käpylä' => [60.214154, 24.950797],
+    'Laajasalo' => [60.171870, 25.043385],
+    'Laakso' => [60.192795, 24.916465],
+    'Lauttasaari' => [60.158291, 24.874188],
+    'Länsisatama' => [60.159699, 24.924461],
+    'Malmi' => [60.250980, 25.010506],
+    'Malminkartano' => [60.247517, 24.862129],
+    'Meilahti' => [60.191904, 24.898236],
+    'Mellunkylä' => [60.233563, 25.102157],
+    'Munkkiniemi' => [60.198247, 24.875977],
+    'Mustikkamaa-Korkeasaari' => [60.180563, 24.990058],
+    'Myllypuro' => [60.223721, 25.067943],
+    'Oulunkylä' => [60.229094, 24.963609],
+    'Pakila' => [60.244177, 24.948121],
+    'Pasila' => [60.203116, 24.926870],
+    'Pitäjänmäki' => [60.222972, 24.862067],
+    'Pukinmäki' => [60.245141, 24.988965],
+    'Punavuori' => [60.161450, 24.937520],
+    'Ruoholahti' => [60.163818, 24.908564],
+    'Ruskeasuo' => [60.202629, 24.905525],
+    'Salmenkallio' => [60.263065, 25.192160],
+    'Santahamina' => [60.147226, 25.051203],
+    'Suomenlinna' => [60.145609, 24.986295],
+    'Suurmetsä' => [60.265570, 25.079241],
+    'Suutarila' => [60.280899, 25.010958],
+    'Sörnäinen' => [60.186503, 24.968059],
+    'Taka-Töölö' => [60.184170, 24.923262],
+    'Talosaari' => [60.242051, 25.197464],
+    'Tammisalo' => [60.191309, 25.063848],
+    'Tapaninkylä' => [60.262263, 25.011138],
+    'Toukola' => [60.206072, 24.972955],
+    'Tuomarinkylä' => [60.256383, 24.966787],
+    'Ulkosaaret' => [60.089292, 24.926002],
+    'Ullanlinna' => [60.158457, 24.948937],
+    'Ultuna' => [60.278678, 25.195271],
+    'Vallila' => [60.194432, 24.956953],
+    'Vanhakaupunki' => [60.216221, 24.981143],
+    'Vartiokylä' => [60.217183, 25.095837],
+    'Vartiosaari' => [60.184930, 25.078028],
+    'Viikki' => [60.224815, 25.020019],
+    'Villinki' => [60.158338, 25.114887],
+    'Vuosaari' => [60.209362, 25.147469],
+    'Östersundom' => [60.251257, 25.182209],
+  ];
+
+  $storage = \Drupal::entityTypeManager()
+    ->getStorage('taxonomy_term');
+
+  foreach ($neighbourhoods as $name => $location) {
+    $terms = $storage->loadByProperties(['name' => $name, 'vid' => 'news_neighbourhoods']);
+    if (!$terms) {
+      continue;
+    }
+
+    [$latitude, $longitude] = $location;
+    $term = reset($terms);
+    $term->set('field_location', [
+      'latitude' => $latitude,
+      'longitude' => $longitude,
+    ]);
+    $term->save();
+  }
+
+}
diff --git a/public/modules/custom/helfi_etusivu/helfi_etusivu.info.yml b/public/modules/custom/helfi_etusivu/helfi_etusivu.info.yml
index 65bd3c07f..12dc63827 100644
--- a/public/modules/custom/helfi_etusivu/helfi_etusivu.info.yml
+++ b/public/modules/custom/helfi_etusivu/helfi_etusivu.info.yml
@@ -12,5 +12,6 @@ dependencies:
   - helfi_annif:helfi_annif
   - simple_sitemap:simple_sitemap
   - helfi_react_search:helfi_react_search
+  - helfi_platform_config:helfi_paragraphs_news_list
 'interface translation project': helfi_etusivu
 'interface translation server pattern': modules/custom/helfi_etusivu/translations/%language.po
diff --git a/public/modules/custom/helfi_etusivu/helfi_etusivu.module b/public/modules/custom/helfi_etusivu/helfi_etusivu.module
index 133173a43..59e7fe9e9 100644
--- a/public/modules/custom/helfi_etusivu/helfi_etusivu.module
+++ b/public/modules/custom/helfi_etusivu/helfi_etusivu.module
@@ -330,6 +330,8 @@ function helfi_etusivu_theme() : array {
         'coordinates' => NULL,
         'title' => NULL,
         'service_groups' => NULL,
+        'nearby_neighbourhoods' => NULL,
+        'news_archive_url' => NULL,
       ],
       'template' => 'helsinki-near-you-results-page',
     ],
@@ -347,3 +349,20 @@ function helfi_etusivu_theme() : array {
     ],
   ];
 }
+
+/**
+ * Implements hook_metatags_alter().
+ *
+ * UHF-11151 For updating news items, set the published time
+ * same as the changed time for better google search results.
+ */
+function helfi_etusivu_metatags_alter(array &$metatags, array &$context): void {
+  $entity = $context['entity'];
+  if (!$entity instanceof NewsItem) {
+    return;
+  }
+
+  if (!$entity->get('field_news_item_updating_news')->isEmpty()) {
+    $metatags['article_published_time'] = '[updating-news:changed:html_datetime]';
+  }
+}
diff --git a/public/modules/custom/helfi_etusivu/helfi_etusivu.tokens.inc b/public/modules/custom/helfi_etusivu/helfi_etusivu.tokens.inc
new file mode 100644
index 000000000..a0b896abe
--- /dev/null
+++ b/public/modules/custom/helfi_etusivu/helfi_etusivu.tokens.inc
@@ -0,0 +1,70 @@
+<?php
+
+/**
+ * @file
+ * Contains token data for helfi_etusivu.
+ */
+
+declare(strict_types=1);
+
+use Drupal\helfi_etusivu\Entity\Node\NewsItem;
+
+/**
+ * Implements hook_token_info().
+ */
+function helfi_etusivu_token_info(): array {
+  $info = [];
+
+  $info['types']['node']['updating-news:changed:html_datetime'] = [
+    'name' => 'updating-news',
+    'description' => 'Get the latest changed time of updating news',
+    'needs-data' => 'node',
+  ];
+
+  $info['tokens']['node']['updating-news:changed:html_datetime'] = [
+    'name' => 'Updating news latest update',
+  ];
+
+  return $info;
+}
+
+/**
+ * Implements hook_tokens().
+ */
+function helfi_etusivu_tokens(
+  $type,
+  $tokens,
+  array $data,
+): array {
+  if ($type !== 'updating-news' || empty($data['node'])) {
+    return [];
+  }
+
+  /** @var \Drupal\Core\Datetime\DateFormatterInterface $dateFormatterService */
+  $dateFormatterService = \Drupal::service('date.formatter');
+
+  $replacements = [];
+  $entity = $data['node'];
+
+  foreach ($tokens as $original) {
+    if (
+      !$entity instanceof NewsItem ||
+      $original !== '[updating-news:changed:html_datetime]'
+    ) {
+      continue;
+    }
+
+    $updates = $entity->getNewsUpdates();
+    $latest = end($updates);
+    // @phpstan-ignore-next-line
+    $timestamp = $latest->get('field_news_update_date')
+      ->date
+      ->getTimestamp();
+
+    $datetime = $dateFormatterService->format($timestamp, 'html_datetime', 'c');
+
+    $replacements[$original] = $datetime;
+  }
+
+  return $replacements;
+}
diff --git a/public/modules/custom/helfi_etusivu/src/Controller/HelsinkiNearYouResultsController.php b/public/modules/custom/helfi_etusivu/src/Controller/HelsinkiNearYouResultsController.php
index d31870d0b..432a832f7 100644
--- a/public/modules/custom/helfi_etusivu/src/Controller/HelsinkiNearYouResultsController.php
+++ b/public/modules/custom/helfi_etusivu/src/Controller/HelsinkiNearYouResultsController.php
@@ -7,9 +7,11 @@
 use Drupal\Component\Utility\Xss;
 use Drupal\Core\Controller\ControllerBase;
 use Drupal\Core\Url;
+use Drupal\external_entities\Entity\Query\External\Query;
 use Drupal\helfi_etusivu\Enum\InternalSearchLink;
 use Drupal\helfi_etusivu\Enum\ServiceMapLink;
 use Drupal\helfi_etusivu\Servicemap;
+use Drupal\helfi_paragraphs_news_list\Entity\ExternalEntity\Term;
 use Drupal\helfi_react_search\LinkedEvents;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\RedirectResponse;
@@ -38,7 +40,6 @@ public function __construct(
    * Returns a renderable array.
    *
    * @param \Symfony\Component\HttpFoundation\Request $request
-   *
    *   The request.
    *
    * @return array
@@ -53,7 +54,7 @@ public function content(Request $request) : array|RedirectResponse {
       return $this->redirect('helfi_etusivu.helsinki_near_you');
     }
     $address = Xss::filter($address);
-    $addressData = $this->getCoordinates(urldecode($address));
+    $addressData = $this->servicemap->getAddressData(urldecode($address));
 
     if (!$addressData) {
       $this->messenger()->addError(
@@ -68,6 +69,12 @@ public function content(Request $request) : array|RedirectResponse {
 
     $addressName = $this->resolveTranslation($addressData['address_translations']);
 
+    $neighborhoods = $this->getNearbyNewsNeighbourhoods($addressData['coordinates']);
+    $newsQuery = [
+      'neighbourhoods' => array_values(array_map(static fn (Term $term) => $term->getTid(), $neighborhoods)),
+    ];
+    $newsArchiveUrl = $this->getInternalSearchLink(InternalSearchLink::NEWS_ARCHIVE, $newsQuery);
+
     return [
       '#attached' => [
         'drupalSettings' => [
@@ -85,11 +92,17 @@ public function content(Request $request) : array|RedirectResponse {
             'seeAllButtonOverride' => $this->t('See all events', [], ['context' => 'Helsinki near you']),
             'useExperimentalGhosts' => TRUE,
           ],
+          'helfi_news_archive' => [
+            'elastic_proxy_url' => $this->config('elastic_proxy.settings')->get('elastic_proxy_url'),
+            'default_query' => http_build_query($newsQuery),
+            'hide_form' => TRUE,
+            'max_results' => 3,
+          ],
         ],
-        'library' => ['hdbt/event-list'],
       ],
       '#back_link_label' => $this->t('Edit address', [], ['context' => 'Helsinki near you']),
       '#back_link_url' => $return_url,
+      '#news_archive_url' => $newsArchiveUrl,
       '#cache' => [
         'contexts' => ['url.query_args:q'],
       ],
@@ -100,83 +113,82 @@ public function content(Request $request) : array|RedirectResponse {
         ['@address' => $addressName],
         ['context' => 'Helsinki near you']
       ),
-      '#service_groups' => [
-        [
-          'title' => $this->t('Health is key', [], ['context' => 'Helsinki near you']),
-          'service_links' => [
-            [
-              'link_label' => $this->t('Your own health station', [], ['context' => 'Helsinki near you']),
-              'link_url' => $this->getInternalSearchLink(
-                InternalSearchLink::HEALTH_STATIONS,
-                $addressName,
-              ),
-            ],
-            [
-              'link_label' => $this->t('Closest maternity and child health clinic', [], ['context' => 'Helsinki near you']),
-              'link_url' => $this->getInternalSearchLink(
-                InternalSearchLink::CHILD_HEALTH_STATIONS,
-                $addressName,
-              ),
-            ],
+      '#nearby_neighbourhoods' => $neighborhoods,
+      '#service_groups' => $this->buildServiceGroups($addressName),
+    ];
+  }
+
+  /**
+   * Builds service groups render array.
+   *
+   * @param string $addressName
+   *   Current address.
+   *
+   * @return array
+   *   Render array.
+   */
+  public function buildServiceGroups(string $addressName) : array {
+    $addressQuery = ['address' => $addressName];
+    $viewsAddressQuery = ['address_search' => $addressName];
+
+    return [
+      [
+        'title' => $this->t('Health is key', [], ['context' => 'Helsinki near you']),
+        'service_links' => [
+          [
+            'link_label' => $this->t('Your own health station', [], ['context' => 'Helsinki near you']),
+            'link_url' => $this->getInternalSearchLink(InternalSearchLink::HEALTH_STATIONS, $addressQuery),
+          ],
+          [
+            'link_label' => $this->t('Closest maternity and child health clinic', [], ['context' => 'Helsinki near you']),
+            'link_url' => $this->getInternalSearchLink(InternalSearchLink::CHILD_HEALTH_STATIONS, $addressQuery),
           ],
         ],
-        [
-          'title' => $this->t('Grow and learn', [], ['context' => 'Helsinki near you']),
-          'service_links' => [
-            [
-              'link_label' => $this->t('Schools close to you', [], ['context' => 'Helsinki near you']),
-              'link_url' => $this->getInternalSearchLink(
-                InternalSearchLink::SCHOOLS,
-                $addressName,
-              ),
-            ],
-            [
-              'link_label' => $this->t('Closest playgrounds and family houses', [], ['context' => 'Helsinki near you']),
-              'link_url' => $this->getInternalSearchLink(
-                InternalSearchLink::PLAYGROUNDS_FAMILY_HOUSES,
-                $addressName,
-              ),
-            ],
-            [
-              'link_label' => $this->t('Closest daycare centres', [], ['context' => 'Helsinki near you']),
-              'link_url' => $this->getInternalSearchLink(
-                InternalSearchLink::DAYCARES,
-                $addressName,
-              ),
-            ],
+      ],
+      [
+        'title' => $this->t('Grow and learn', [], ['context' => 'Helsinki near you']),
+        'service_links' => [
+          [
+            'link_label' => $this->t('Schools close to you', [], ['context' => 'Helsinki near you']),
+            'link_url' => $this->getInternalSearchLink(InternalSearchLink::SCHOOLS, $addressQuery),
+          ],
+          [
+            'link_label' => $this->t('Closest playgrounds and family houses', [], ['context' => 'Helsinki near you']),
+            'link_url' => $this->getInternalSearchLink(InternalSearchLink::PLAYGROUNDS_FAMILY_HOUSES, $viewsAddressQuery, 'views-exposed-form-playground-search-block'),
+          ],
+          [
+            'link_label' => $this->t('Closest daycare centres', [], ['context' => 'Helsinki near you']),
+            'link_url' => $this->getInternalSearchLink(InternalSearchLink::DAYCARES, $viewsAddressQuery, 'views-exposed-form-daycare-search-block'),
           ],
         ],
-        [
-          'title' => $this->t('Move around the city', [], ['context' => 'Helsinki near you']),
-          'service_links' => [
-            [
-              'link_label' => $this->t('Roadway ploughing schedule', [], ['context' => 'Helsinki near you']),
-              'link_url' => $this->getInternalSearchLink(
-                InternalSearchLink::PLOWING_SCHEDULES,
-                $addressName,
-              ),
-            ],
-            [
-              'link_label' => $this->t('Roadworks and events on map', [], ['context' => 'Helsinki near you']),
-              'link_url' => $this->servicemap->getLink(ServiceMapLink::ROADWORK_EVENTS, $addressName),
-            ],
-            [
-              'link_label' => $this->t('City bike stations on map', [], ['context' => 'Helsinki near you']),
-              'link_url' => $this->servicemap->getLink(ServiceMapLink::CITYBIKE_STATIONS_STANDS, $addressName),
-            ],
+      ],
+      [
+        'title' => $this->t('Move around the city', [], ['context' => 'Helsinki near you']),
+        'service_links' => [
+          [
+            'link_label' => $this->t('Roadway ploughing schedule', [], ['context' => 'Helsinki near you']),
+            'link_url' => $this->getInternalSearchLink(InternalSearchLink::PLOWING_SCHEDULES, $addressQuery),
+          ],
+          [
+            'link_label' => $this->t('Roadworks and events on map', [], ['context' => 'Helsinki near you']),
+            'link_url' => $this->servicemap->getLink(ServiceMapLink::ROADWORK_EVENTS, $addressName),
+          ],
+          [
+            'link_label' => $this->t('City bike stations on map', [], ['context' => 'Helsinki near you']),
+            'link_url' => $this->servicemap->getLink(ServiceMapLink::CITYBIKE_STATIONS_STANDS, $addressName),
           ],
         ],
-        [
-          'title' => $this->t('The city is developing', [], ['context' => 'Helsinki near you']),
-          'service_links' => [
-            [
-              'link_label' => $this->t('Street and park development on map', [], ['context' => 'Helsinki near you']),
-              'link_url' => $this->servicemap->getLink(ServiceMapLink::STREET_PARK_PROJECTS, $addressName),
-            ],
-            [
-              'link_label' => $this->t('Plans in process on map', [], ['context' => 'Helsinki near you']),
-              'link_url' => $this->servicemap->getLink(ServiceMapLink::PLANS_IN_PROCESS, $addressName),
-            ],
+      ],
+      [
+        'title' => $this->t('The city is developing', [], ['context' => 'Helsinki near you']),
+        'service_links' => [
+          [
+            'link_label' => $this->t('Street and park development on map', [], ['context' => 'Helsinki near you']),
+            'link_url' => $this->servicemap->getLink(ServiceMapLink::STREET_PARK_PROJECTS, $addressName),
+          ],
+          [
+            'link_label' => $this->t('Plans in process on map', [], ['context' => 'Helsinki near you']),
+            'link_url' => $this->servicemap->getLink(ServiceMapLink::PLANS_IN_PROCESS, $addressName),
           ],
         ],
       ],
@@ -210,31 +222,6 @@ public function addressSuggestions(Request $request) : JsonResponse {
     return new JsonResponse($suggestions);
   }
 
-  /**
-   * Get coordinates from servicemap API.
-   *
-   * @param string $address
-   *   The address.
-   *
-   * @return array
-   *   The coordinates.
-   */
-  protected function getCoordinates(string $address) : ?array {
-    $results = $this->servicemap->query($address);
-
-    if (
-      isset($results['0']->name) &&
-      isset($results['0']->location->coordinates)
-    ) {
-      return [
-        'address_translations' => $results['0']->name,
-        'coordinates' => $results['0']->location->coordinates,
-      ];
-    }
-
-    return NULL;
-  }
-
   /**
    * Resolves the translation string for given translation object.
    *
@@ -253,28 +240,70 @@ protected function resolveTranslation(\stdClass $translations) : string {
   }
 
   /**
-   * Generate link to internal search with address param set.
+   * Generate link to internal search with query params.
    *
    * @param \Drupal\helfi_etusivu\Enum\InternalSearchLink $link
    *   Internal search link option.
-   * @param string $address
-   *   Address param for the link.
+   * @param array $query
+   *   Query params for the link.
+   * @param string|null $anchor
+   *   Anchor to add to the link.
    *
    * @return string
    *   The resulting link.
    */
   protected function getInternalSearchLink(
     InternalSearchLink $link,
-    string $address,
+    array $query,
+    ?string $anchor = NULL,
   ) : string {
     $langcode = $this->languageManager()->getCurrentLanguage()->getId();
-    $query = ['address' => urlencode($address)];
     $url = Url::fromUri(
       $link->getLinkTranslations()[$langcode],
       ['query' => $query],
     );
 
-    return $url->toString();
+    return $url->toString() . ($anchor ? "#$anchor" : '');
+  }
+
+  /**
+   * Get nearby news neighbourhoods.
+   *
+   * @param array $coordinates
+   *   Coordinates tuple.
+   *
+   * @return \Drupal\Core\Entity\EntityInterface[]
+   *   Helfi: news Neighbourhoods entities.
+   */
+  protected function getNearbyNewsNeighbourhoods(array $coordinates): array {
+    $storage = $this->entityTypeManager()
+      ->getStorage('helfi_news_neighbourhoods');
+    $query = $storage
+      ->getQuery();
+
+    assert($query instanceof Query);
+    $query->setParameter('location', [
+      $coordinates,
+      [
+        'unit' => 'km',
+        'order' => 'asc',
+        // 'arc' is more accurate, but within
+        // a city it should not matter.
+        'distance_type' => 'plane',
+        // What to do in case a field has several geo points.
+        'mode' => 'min',
+        // Unmapped field cause the search to fail.
+        'ignore_unmapped' => FALSE,
+      ],
+    ], 'GEO_DISTANCE_SORT');
+
+    $ids = $query
+      ->range(length: 3)
+      ->condition('search_api_language', $this->languageManager()->getCurrentLanguage()->getId())
+      ->accessCheck(FALSE)
+      ->execute();
+
+    return $storage->loadMultiple($ids);
   }
 
 }
diff --git a/public/modules/custom/helfi_etusivu/src/Entity/Node/NewsItem.php b/public/modules/custom/helfi_etusivu/src/Entity/Node/NewsItem.php
index 542bfba84..7f9286488 100644
--- a/public/modules/custom/helfi_etusivu/src/Entity/Node/NewsItem.php
+++ b/public/modules/custom/helfi_etusivu/src/Entity/Node/NewsItem.php
@@ -48,7 +48,7 @@ public function preSave(EntityStorageInterface $storage) : void {
    * @return \Drupal\Core\Entity\EntityInterface[]
    *   News updates entities.
    */
-  private function getNewsUpdates() : array {
+  public function getNewsUpdates() : array {
     $field = $this->get('field_news_item_updating_news');
     assert($field instanceof EntityReferenceFieldItemListInterface);
     return $field->referencedEntities();
diff --git a/public/modules/custom/helfi_etusivu/src/Enum/InternalSearchLink.php b/public/modules/custom/helfi_etusivu/src/Enum/InternalSearchLink.php
index 4165ebe84..52702f41f 100644
--- a/public/modules/custom/helfi_etusivu/src/Enum/InternalSearchLink.php
+++ b/public/modules/custom/helfi_etusivu/src/Enum/InternalSearchLink.php
@@ -17,6 +17,7 @@ enum InternalSearchLink {
   case PLAYGROUNDS_FAMILY_HOUSES;
   case DAYCARES;
   case PLOWING_SCHEDULES;
+  case NEWS_ARCHIVE;
 
   /**
    * Return array of link translations.
@@ -56,6 +57,11 @@ public function getLinkTranslations() : array {
         'sv' => 'https://www.hel.fi/sv/stadsmiljo-och-trafik/underhall/gatuunderhall/vinterunderhall-av-gator',
         'en' => 'https://www.hel.fi/en/urban-environment-and-traffic/general-maintenance/street-maintenance/winter-street-maintenance',
       ],
+      InternalSearchLink::NEWS_ARCHIVE => [
+        'fi' => 'https://www.hel.fi/fi/uutiset/etsi-uutisia',
+        'sv' => 'https://www.hel.fi/sv/nyheter/sok-efter-nyheter',
+        'en' => 'https://www.hel.fi/en/news/search-for-news',
+      ]
     };
   }
 
diff --git a/public/modules/custom/helfi_etusivu/src/Servicemap.php b/public/modules/custom/helfi_etusivu/src/Servicemap.php
index 80168e44e..5527daee4 100644
--- a/public/modules/custom/helfi_etusivu/src/Servicemap.php
+++ b/public/modules/custom/helfi_etusivu/src/Servicemap.php
@@ -73,13 +73,38 @@ public function __construct(
   ) {
   }
 
+  /**
+   * Get coordinates from servicemap API.
+   *
+   * @param string $address
+   *   The address.
+   *
+   * @return ?array
+   *   The coordinates.
+   */
+  public function getAddressData(string $address) : ?array {
+    $results = $this->query($address);
+
+    if (
+      isset($results['0']->name) &&
+      isset($results['0']->location->coordinates)
+    ) {
+      return [
+        'address_translations' => $results['0']->name,
+        'coordinates' => $results['0']->location->coordinates,
+      ];
+    }
+
+    return NULL;
+  }
+
   /**
    * Queries location data based on address.
    *
    * @param string $address
    *   Address to query against.
    * @param int $page_size
-   *   Maximum number or results.
+   *   Maximum number of results.
    *
    * @return array
    *   Array of results.
diff --git a/public/modules/custom/helfi_etusivu_config/config/rewrite/core.entity_form_display.node.news_item.default.yml b/public/modules/custom/helfi_etusivu_config/config/rewrite/core.entity_form_display.node.news_item.default.yml
index fdb9dba33..6f700feba 100644
--- a/public/modules/custom/helfi_etusivu_config/config/rewrite/core.entity_form_display.node.news_item.default.yml
+++ b/public/modules/custom/helfi_etusivu_config/config/rewrite/core.entity_form_display.node.news_item.default.yml
@@ -1,5 +1,10 @@
 config_rewrite:
-  replace: ['hidden', 'dependencies']
+  replace: [
+    'hidden',
+    'dependencies',
+    'third_party_settings.field_group.group_updating_news.children',
+    'third_party_settings.field_group.group_automatically_recommended.children',
+  ]
 dependencies:
   config:
     - field.field.node.news_item.field_content
diff --git a/public/modules/custom/helfi_etusivu_config/config/rewrite/field.field.media.image.field_media_image.yml b/public/modules/custom/helfi_etusivu_config/config/rewrite/field.field.media.image.field_media_image.yml
new file mode 100644
index 000000000..98a48f6aa
--- /dev/null
+++ b/public/modules/custom/helfi_etusivu_config/config/rewrite/field.field.media.image.field_media_image.yml
@@ -0,0 +1 @@
+label: 'Image (supported in other languages)'
diff --git a/public/modules/custom/helfi_etusivu_config/config/rewrite/language/fi/field.field.media.image.field_media_image.yml b/public/modules/custom/helfi_etusivu_config/config/rewrite/language/fi/field.field.media.image.field_media_image.yml
new file mode 100644
index 000000000..38b204637
--- /dev/null
+++ b/public/modules/custom/helfi_etusivu_config/config/rewrite/language/fi/field.field.media.image.field_media_image.yml
@@ -0,0 +1 @@
+label: 'Kuva (tuettu muilla kielillä)'
diff --git a/public/modules/custom/helfi_etusivu_config/config/rewrite/language/fi/media.type.image.yml b/public/modules/custom/helfi_etusivu_config/config/rewrite/language/fi/media.type.image.yml
new file mode 100644
index 000000000..38b204637
--- /dev/null
+++ b/public/modules/custom/helfi_etusivu_config/config/rewrite/language/fi/media.type.image.yml
@@ -0,0 +1 @@
+label: 'Kuva (tuettu muilla kielillä)'
diff --git a/public/modules/custom/helfi_etusivu_config/config/rewrite/language/sv/field.field.media.image.field_media_image.yml b/public/modules/custom/helfi_etusivu_config/config/rewrite/language/sv/field.field.media.image.field_media_image.yml
new file mode 100644
index 000000000..cef092dc9
--- /dev/null
+++ b/public/modules/custom/helfi_etusivu_config/config/rewrite/language/sv/field.field.media.image.field_media_image.yml
@@ -0,0 +1 @@
+label: 'Bild (stöds på andra språk)'
diff --git a/public/modules/custom/helfi_etusivu_config/config/rewrite/language/sv/media.type.image.yml b/public/modules/custom/helfi_etusivu_config/config/rewrite/language/sv/media.type.image.yml
new file mode 100644
index 000000000..cef092dc9
--- /dev/null
+++ b/public/modules/custom/helfi_etusivu_config/config/rewrite/language/sv/media.type.image.yml
@@ -0,0 +1 @@
+label: 'Bild (stöds på andra språk)'
diff --git a/public/modules/custom/helfi_etusivu_config/config/rewrite/media.type.image.yml b/public/modules/custom/helfi_etusivu_config/config/rewrite/media.type.image.yml
new file mode 100644
index 000000000..98a48f6aa
--- /dev/null
+++ b/public/modules/custom/helfi_etusivu_config/config/rewrite/media.type.image.yml
@@ -0,0 +1 @@
+label: 'Image (supported in other languages)'
diff --git a/public/modules/custom/helfi_etusivu_config/config/rewrite/views.view.content.yml b/public/modules/custom/helfi_etusivu_config/config/rewrite/views.view.content.yml
new file mode 100644
index 000000000..dd50ec680
--- /dev/null
+++ b/public/modules/custom/helfi_etusivu_config/config/rewrite/views.view.content.yml
@@ -0,0 +1,20 @@
+display:
+  default:
+    display_options:
+      filters:
+        langcode:
+          value:
+            fi: fi
+            sv: sv
+            en: en
+            de: de
+            fr: fr
+            ru: ru
+            uk: uk
+            ar: ar
+            et: et
+            fa: fa
+            es: es
+            so: so
+            se: se
+            zh-hans: zh-hans
diff --git a/public/modules/custom/helfi_etusivu_config/helfi_etusivu_config.module b/public/modules/custom/helfi_etusivu_config/helfi_etusivu_config.module
index c45473dc6..1672459b6 100644
--- a/public/modules/custom/helfi_etusivu_config/helfi_etusivu_config.module
+++ b/public/modules/custom/helfi_etusivu_config/helfi_etusivu_config.module
@@ -14,6 +14,8 @@ use Drupal\config_rewrite\ConfigRewriterInterface;
  */
 function helfi_etusivu_config_rewrite_config_update(string $module, ConfigRewriterInterface $configRewriter): void {
   $modules = [
+    'helfi_base_content',
+    'helfi_media',
     'helfi_node_announcement',
     'helfi_node_survey',
     'helfi_node_news_item',
diff --git a/public/modules/custom/helfi_etusivu_config/src/.keep b/public/modules/custom/helfi_etusivu_config/src/.keep
new file mode 100644
index 000000000..e69de29bb
diff --git a/public/modules/custom/helfi_etusivu_config/tests/.keep b/public/modules/custom/helfi_etusivu_config/tests/.keep
new file mode 100644
index 000000000..e69de29bb
diff --git a/public/modules/custom/helfi_node_news_article/tests/.keep b/public/modules/custom/helfi_node_news_article/tests/.keep
new file mode 100644
index 000000000..e69de29bb
diff --git a/public/themes/custom/hdbt_subtheme/.nvmrc b/public/themes/custom/hdbt_subtheme/.nvmrc
index d5a159609..2bd5a0a98 100644
--- a/public/themes/custom/hdbt_subtheme/.nvmrc
+++ b/public/themes/custom/hdbt_subtheme/.nvmrc
@@ -1 +1 @@
-20.10.0
+22
diff --git a/public/themes/custom/hdbt_subtheme/templates/module/helfi-etusivu/helsinki-near-you-results-page.html.twig b/public/themes/custom/hdbt_subtheme/templates/module/helfi-etusivu/helsinki-near-you-results-page.html.twig
index 6db6ffb17..8ccd97a17 100644
--- a/public/themes/custom/hdbt_subtheme/templates/module/helfi-etusivu/helsinki-near-you-results-page.html.twig
+++ b/public/themes/custom/hdbt_subtheme/templates/module/helfi-etusivu/helsinki-near-you-results-page.html.twig
@@ -48,7 +48,7 @@
         component_classes: [
         'component--react-search',
         'component--event-list',
-        'component--coordinates-based-event-list',
+        'component--coordinates-based-list',
         ],
         component_title: 'Events near you'|trans({}, {'context': 'Helsinki near you'}),
         component_description: 'Browse events near you, sorted by their start time'|trans({}, {'context': 'Helsinki near you'}),
@@ -57,6 +57,7 @@
     %}
       {% block component_content %}
         {# Hook React app to this div #}
+        {{ attach_library('hdbt/event-list') }}
         <div
           id="helfi-events-search"
           data-paragraph-id="helfi-coordinates-based-event-list"
@@ -70,5 +71,44 @@
         </noscript>
       {% endblock %}
     {% endembed %}
+    {% if nearby_neighbourhoods is not empty %}
+      {% set neighbourhood_names = nearby_neighbourhoods | map(n => n.title.value) | join(', ', ' and ' | t({}, {'context': 'News close to you block'})) %}
+      {% embed "@hdbt/misc/component.twig" with
+        {
+          component_classes: [
+          'component--react-search',
+          'component--news-archive',
+          'component--coordinates-based-list',
+        ],
+          component_title: 'News close to you'|t({}, {'context': 'News close to you block'}),
+          component_description: 'Check out the news from the neighborhoods of %neighbourhoods%'|t({'%neighbourhoods%': neighbourhood_names}, {'context': 'News close to you block'}),
+          component_content_class: 'news-archive',
+        }
+      %}
+        {% block component_content %}
+          {# Hook React app to this div #}
+          {{ attach_library('hdbt/news-archive') }}
+          <div
+            id="helfi-etusivu-news-search"
+            data-paragraph-id="helfi-coordinates-based-news-list"
+          >
+          </div>
+          {# Indicate JS not enabled #}
+          <noscript>
+            <div class="news-archive__javascript-disabled">
+              {{ 'You must enable JavaScript in your browser for news list to work'|t }}
+            </div>
+          </noscript>
+          <div class="news-archive__link-wrapper">
+            {% set news_archive_title = 'View all news'|t({}, {'context': 'News close to you block'}) %}
+            {% include '@hdbt/navigation/link-button.html.twig' with {
+              type: 'primary',
+              label: news_archive_title,
+              url: news_archive_url,
+            } %}
+          </div>
+        {% endblock component_content %}
+      {% endembed %}
+    {% endif %}
   </div>
 </article>
diff --git a/public/themes/custom/hdbt_subtheme/translations/fi.po b/public/themes/custom/hdbt_subtheme/translations/fi.po
index 0143fc1ea..ac8269f04 100644
--- a/public/themes/custom/hdbt_subtheme/translations/fi.po
+++ b/public/themes/custom/hdbt_subtheme/translations/fi.po
@@ -25,6 +25,22 @@ msgctxt "Services close to you block"
 msgid "Services close to you"
 msgstr "Palvelut lähelläsi"
 
+msgctxt "News close to you block"
+msgid "News close to you"
+msgstr "Uutisia läheltäsi"
+
+msgctxt "News close to you block"
+msgid " and "
+msgstr " ja "
+
+msgctxt "News close to you block"
+msgid "Check out the news from the neighborhoods of %neighbourhoods%"
+msgstr "Katso uutisia kaupunginosista %neighbourhoods%"
+
+msgctxt "News close to you block"
+msgid "View all news"
+msgstr "Katso kaikki uutiset"
+
 msgctxt "Services close to you block"
 msgid "Use the links to go to results based on your address."
 msgstr "Siirry linkeistä oman osoitteesi mukaisiin hakutuloksiin."
diff --git a/public/themes/custom/hdbt_subtheme/translations/sv.po b/public/themes/custom/hdbt_subtheme/translations/sv.po
index 7aec2f489..ad6a41a3d 100644
--- a/public/themes/custom/hdbt_subtheme/translations/sv.po
+++ b/public/themes/custom/hdbt_subtheme/translations/sv.po
@@ -25,6 +25,22 @@ msgctxt "Services close to you block"
 msgid "Services close to you"
 msgstr "Tjänster nära dig"
 
+msgctxt "News close to you block"
+msgid "News close to you"
+msgstr "Nyheter nära dig"
+
+msgctxt "News close to you block"
+msgid " and "
+msgstr " och "
+
+msgctxt "News close to you block"
+msgid "Check out the news from the neighborhoods of %neighbourhoods%"
+msgstr "Kolla in nyheterna från stadsdelarna %neighbourhoods%"
+
+msgctxt "News close to you block"
+msgid "View all news"
+msgstr "Se alla nyheter"
+
 msgctxt "Services close to you block"
 msgid "Use the links to go to results based on your address."
 msgstr "Använd länkarna för att visa resultat baserat på din adress."
diff --git a/sonar-project.properties b/sonar-project.properties
new file mode 100644
index 000000000..b91c2436f
--- /dev/null
+++ b/sonar-project.properties
@@ -0,0 +1,6 @@
+sonar.projectKey=City-of-Helsinki_drupal-helfi-etusivu
+sonar.organization=city-of-helsinki
+sonar.php.coverage.reportPaths=coverage.xml
+sonar.sources=public/modules/custom
+sonar.inclusions=public/modules/custom/*/src/**/*
+sonar.test.inclusions=public/modules/custom/*/tests/src/**/*
diff --git a/tests/dtt/src/ExistingSite/NewsContentTypeTest.php b/tests/dtt/src/ExistingSite/NewsContentTypeTest.php
index 7e4e098c3..c44a1e290 100644
--- a/tests/dtt/src/ExistingSite/NewsContentTypeTest.php
+++ b/tests/dtt/src/ExistingSite/NewsContentTypeTest.php
@@ -123,7 +123,7 @@ public function testNewsOgImage() : void {
   }
 
   /**
-   * Tests that adding news update sets published_at field.
+   * Tests that adding news update sets published_at field and the article:published_time metatag updates.
    */
   public function testNewsUpdate() : void {
     $node = $this->createNode([
@@ -152,6 +152,13 @@ public function testNewsUpdate() : void {
 
     // Adding news update should have updated published_at field.
     $this->assertEquals($node->get('published_at')->value, $updateTime->getTimestamp());
+
+    $dateFormatter = $this->container->get('date.formatter');
+    $newTimestamp = $dateFormatter->format($update->get('field_news_update_date')->date->getTimestamp(), 'html_datetime', 'c');
+
+    // Article:published_time-metatag is set to the updated news creation time.
+    $this->drupalGet($node->toUrl());
+    $this->assertSession()->elementAttributeContains('css', 'meta[property="article:published_time"]', 'content', $newTimestamp);
   }
 
   /**
diff --git a/tools/commit-msg b/tools/commit-msg
old mode 100755
new mode 100644
diff --git a/tools/make/Makefile b/tools/make/Makefile
deleted file mode 100644
index fa000889d..000000000
--- a/tools/make/Makefile
+++ /dev/null
@@ -1,46 +0,0 @@
-.DEFAULT_GOAL := help
-
-BUILD_TARGETS :=
-COMPOSER_JSON_PATH ?= .
-DRUIDFI_TOOLS_MAKE_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
-ENV := dev
-PACKAGE_JSON_PATH ?= .
-RUN_ON := host
-UNAME_S := $(shell uname -s)
-# Shorten with https://git.io/ : https://raw.githubusercontent.com/druidfi/tools/main/update.sh
-UPDATE_SCRIPT_URL := https://git.io/JP10q
-WEBROOT ?= public
-
-include $(DRUIDFI_TOOLS_MAKE_DIR)utils.mk
-
-# Include druidfi/tools make files
-include $(DRUIDFI_TOOLS_MAKE_DIR)include.mk
-
-PHONY += debug
-debug: ## Show debug information
-	@printf "\n$(YELLOW)Debug starts:$(NO_COLOR)\n\n"
-	$(call dbg,DOCKER,$(call has,docker))
-	$(call dbg,DOCKER_PROJECT_ROOT,$(DOCKER_PROJECT_ROOT))
-	$(call dbg,DOCKER_COMPOSE_YML_EXISTS,$(DOCKER_COMPOSE_YML_EXISTS))
-	$(call dbg,CLI_SERVICE,$(CLI_SERVICE))
-	$(call dbg,CLI_USER,$(if $(CLI_USER),$(CLI_USER),default user from CLI image))
-	$(call dbg,CLI_SHELL,${CLI_SHELL})
-	$(call dbg,ENV,${ENV})
-	$(call dbg,PHP on host,$(call has,php))
-	$(call dbg,PHP_BIN,$(shell command -v php || echo no))
-	$(call dbg,RUN_ON,${RUN_ON})
-	$(call dbg,Composer on host,$(call has,composer))
-	$(call dbg,COMPOSER_BIN,$(shell command -v composer || echo no))
-	$(call dbg,COMPOSER_JSON_EXISTS,${COMPOSER_JSON_EXISTS})
-	$(call dbg,IS_DRUPAL,${IS_DRUPAL})
-	$(call dbg,IS_SYMFONY,${IS_SYMFONY})
-	$(call dbg,LAGOON,${LAGOON})
-	$(call dbg,SYSTEM,${SYSTEM})
-	$(call dbg,WEBROOT,${WEBROOT})
-	$(call dbg,UNAME_S,${UNAME_S})
-ifeq ($(RUN_ON),docker)
-	@printf "\n${YELLOW}Assumption: We should run on Docker and not on host${NO_COLOR}\n"
-else
-	@printf "\n${YELLOW}Assumption: We should run on host and not on Docker${NO_COLOR}\n"
-endif
-	@printf "\n${GREEN}End of debug.${NO_COLOR}\n"
diff --git a/tools/make/ansible.mk b/tools/make/ansible.mk
deleted file mode 100644
index 132f84c1e..000000000
--- a/tools/make/ansible.mk
+++ /dev/null
@@ -1,35 +0,0 @@
-ANSIBLE_ROLES_PATH ?= ansible/roles
-ANSIBLE_CHECK_ROLE ?= geerlingguy.docker
-ANSIBLE_PLAYBOOK ?= ansible-playbook
-ANSIBLE_PROVISION ?= ansible/provision.yml
-ANSIBLE_REQUIREMENTS ?= ansible/requirements.yml
-ANSIBLE_FLAGS ?=
-
-PHONY += provision
-provision: $(ANSIBLE_ROLES_PATH)/$(ANSIBLE_CHECK_ROLE) ## Make provisioning
-	$(call step,Ansible: Make dry run on provisioning...\n)
-	@$(ANSIBLE_PLAYBOOK) $(ANSIBLE_PROVISION) $(ANSIBLE_FLAGS)
-
-PHONY += provision-%
-provision-%: $(ANSIBLE_ROLES_PATH)/$(ANSIBLE_CHECK_ROLE) ## Make provisioning by tag
-	$(call step,Ansible: Make provisioning by tag "$*"...\n)
-	@$(ANSIBLE_PLAYBOOK) $(ANSIBLE_PROVISION) --tags="$*" $(ANSIBLE_FLAGS)
-
-PHONY += provision-dry-run
-provision-dry-run: $(ANSIBLE_ROLES_PATH)/$(ANSIBLE_CHECK_ROLE) ## Make dry run on provisioning
-	$(call step,Ansible: Make dry run on provisioning...\n)
-	@$(ANSIBLE_PLAYBOOK) $(ANSIBLE_PROVISION) $(ANSIBLE_FLAGS) --check
-
-PHONY += ansible-install-roles
-ansible-install-roles: ## Install Ansible roles
-	$(call step,Ansible: Install Ansible roles...\n)
-	@ansible-galaxy install -r $(ANSIBLE_REQUIREMENTS) -p $(ANSIBLE_ROLES_PATH)
-
-PHONY += ansible-update-roles
-ansible-update-roles: ## Update Ansible roles
-	$(call step,Ansible: Update Ansible roles...\n)
-	@ansible-galaxy remove --roles-path=$(ANSIBLE_ROLES_PATH) $(shell find $(ANSIBLE_ROLES_PATH) -mindepth 1 -maxdepth 1 -type d -exec basename {} \;) || true
-	@ansible-galaxy install --force-with-deps --role-file=$(ANSIBLE_REQUIREMENTS) --roles-path=$(ANSIBLE_ROLES_PATH)
-
-$(ANSIBLE_ROLES_PATH)/$(ANSIBLE_CHECK_ROLE):
-	@$(MAKE) ansible-install-roles
diff --git a/tools/make/common.mk b/tools/make/common.mk
deleted file mode 100644
index 0ede44575..000000000
--- a/tools/make/common.mk
+++ /dev/null
@@ -1,75 +0,0 @@
-ARTIFACT_INCLUDE_EXISTS := $(shell test -f conf/artifact/include && echo yes || echo no)
-ARTIFACT_EXCLUDE_EXISTS := $(shell test -f conf/artifact/exclude && echo yes || echo no)
-ARTIFACT_CMD := tar -hczf artifact.tar.gz
-DUMP_SQL_FILENAME ?= dump.sql
-DUMP_SQL_EXISTS := $(shell test -f $(DUMP_SQL_FILENAME) && echo yes || echo no)
-SSH_OPTS ?= -o LogLevel=ERROR -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no
-CLEAN_EXCLUDE := .idea $(DUMP_SQL_FILENAME) .env.local
-
-ifeq ($(ARTIFACT_EXCLUDE_EXISTS),yes)
-	ARTIFACT_CMD := $(ARTIFACT_CMD) --exclude-from=conf/artifact/exclude
-endif
-
-ifeq ($(ARTIFACT_INCLUDE_EXISTS),yes)
-	ARTIFACT_CMD := $(ARTIFACT_CMD) --files-from=conf/artifact/include
-else
-	ARTIFACT_CMD := $(ARTIFACT_CMD) *
-endif
-
-PHONY += artifact
-# This command can always be run on host
-artifact: RUN_ON := host
-artifact: ## Make tar.gz package from the current build
-	$(call step,Create artifact...\n)
-	@$(ARTIFACT_CMD)
-
-PHONY += build
-build: ## Build codebase(s)
-	$(call group_step,Build ($(ENV)):${NO_COLOR} $(BUILD_TARGETS))
-	@$(MAKE) $(BUILD_TARGETS) ENV=$(ENV)
-
-PHONY += build-dev
-build-dev: build
-
-PHONY += build-testing
-build-testing:
-	@$(MAKE) build ENV=testing
-
-PHONY += build-production
-build-production:
-	@$(MAKE) build ENV=production
-
-PHONY += clean
-clean: ## Cleanup
-	$(call step,Cleanup loaded files...\n)
-	@rm -rf vendor
-	@git clean -fdx $(foreach item,$(CLEAN_EXCLUDE),-e $(item))
-
-PHONY += self-update
-self-update: ## Self-update makefiles from druidfi/tools
-	$(call step,Update makefiles from druidfi/tools\n)
-	@bash -c "$$(curl -fsSL $(UPDATE_SCRIPT_URL))"
-
-PHONY += shell-%
-shell-%: OPTS = $(INSTANCE_$*_OPTS)
-shell-%: USER = $(INSTANCE_$*_USER)
-shell-%: HOST = $(INSTANCE_$*_HOST)
-shell-%: EXTRA = $(INSTANCE_$*_EXTRA)
-shell-%: ## Login to remote instance
-	ssh $(OPTS) $(USER)@$(HOST) $(EXTRA)
-
-PHONY += sync
-sync: ## Sync data from other environments
-	$(call group_step,Sync:$(NO_COLOR) $(SYNC_TARGETS))
-	@$(MAKE) $(SYNC_TARGETS) ENV=$(ENV)
-
-PHONY += gh-download-dump
-gh-download-dump: GH_FLAGS += $(if $(GH_ARTIFACT),-n $(GH_ARTIFACT),-n latest-dump)
-gh-download-dump: GH_FLAGS += $(if $(GH_REPO),-R $(GH_REPO),)
-gh-download-dump: ## Download database dump from repository artifacts
-	$(call step,Download database dump from repository artifacts\n)
-ifeq ($(DUMP_SQL_EXISTS),no)
-	$(call run,gh run download $(strip $(GH_FLAGS)),Downloaded $(DUMP_SQL_FILENAME),Failed)
-else
-	@echo "There is already $(DUMP_SQL_FILENAME)"
-endif
diff --git a/tools/make/composer.mk b/tools/make/composer.mk
index 4dd219543..1e61b280f 100644
--- a/tools/make/composer.mk
+++ b/tools/make/composer.mk
@@ -1,11 +1,3 @@
-BUILD_TARGETS += composer-install
-COMPOSER_PROD_FLAGS := --no-dev --optimize-autoloader --prefer-dist
-
-PHONY += composer-info
-composer-info: ## Composer info
-	$(call step,Do Composer info...\n)
-	$(call composer,info)
-
 PHONY += composer-update
 composer-update: ## Update Composer packages
 	$(call step,Do Composer update...\n)
@@ -14,19 +6,13 @@ composer-update: ## Update Composer packages
 PHONY += composer-install
 composer-install: ## Install Composer packages
 	$(call step,Do Composer install...\n)
-	$(call composer,install$(if $(filter production,$(ENV)), $(COMPOSER_PROD_FLAGS),))
+	$(call composer,install)
 
 PHONY += composer-outdated
 composer-outdated: ## Show outdated Composer packages
 	$(call step,Show outdated Composer packages...\n)
 	$(call composer,outdated --direct)
 
-ifeq ($(RUN_ON),docker)
-define composer
-	$(call docker_compose_exec,composer --ansi$(if $(filter $(COMPOSER_JSON_PATH),.),, --working-dir=$(COMPOSER_JSON_PATH)) $(1))
-endef
-else
 define composer
-	@composer --ansi$(if $(filter $(COMPOSER_JSON_PATH),.),, --working-dir=$(COMPOSER_JSON_PATH)) $(1)
+	$(call docker_compose_exec,composer $(1))
 endef
-endif
diff --git a/tools/make/docker.mk b/tools/make/docker.mk
index 4367e2df3..5fd105955 100644
--- a/tools/make/docker.mk
+++ b/tools/make/docker.mk
@@ -1,26 +1,3 @@
-CLI_SERVICE := app
-CLI_SHELL := sh
-# Note: specification says this file would be compose.yaml
-DOCKER_COMPOSE_YML_PATH ?= compose.yaml
-DOCKER_COMPOSE_YML_EXISTS := $(shell test -f $(DOCKER_COMPOSE_YML_PATH) && echo yes || echo no)
-DOCKER_ENV := $(shell test -f /.dockerenv && echo yes || echo no)
-DOCKER_PROJECT_ROOT ?= /app
-DOCKER_WARNING_INSIDE := You are inside the Docker container!
-
-ifeq ($(DOCKER_ENV),yes)
-	RUN_ON := host
-endif
-
-# If compose.yaml exists
-ifeq ($(DOCKER_ENV)-$(DOCKER_COMPOSE_YML_EXISTS),no-yes)
-	RUN_ON := docker
-endif
-
-PHONY += config
-config: ## Show docker-compose config
-	$(call step,Show Docker Compose config...\n)
-	$(call docker_compose,config)
-
 PHONY += pull
 pull: ## Pull docker images
 	$(call step,Pull the latest docker images...\n)
@@ -48,43 +25,13 @@ up: ## Launch the environment
 
 PHONY += shell
 shell: ## Login to CLI container
-ifeq ($(RUN_ON),docker)
-	$(call docker_compose,exec $(CLI_SERVICE) $(CLI_SHELL))
-else
-	$(call warn,$(DOCKER_WARNING_INSIDE))
-endif
+	$(call docker_compose,exec app bash)
 
-PHONY += ssh-check
-ssh-check: ## Check SSH keys on CLI container
-	$(call docker_compose_exec,ssh-add -L)
-
-ifeq ($(RUN_ON),docker)
-define docker
-	@docker $(1) > /dev/null 2>&1 && $(if $(2),echo "$(2)",)
-endef
-else
-define docker
-	$(call sub_step,$(DOCKER_WARNING_INSIDE))
-endef
-endif
-
-ifeq ($(RUN_ON),docker)
-define docker_compose_exec
-	$(call docker_compose,exec$(if $(CLI_USER), -u $(CLI_USER),) $(CLI_SERVICE) $(CLI_SHELL) -c "$(1)")
-	$(if $(2),@echo "$(2)",)
-endef
-else
 define docker_compose_exec
-	@$(1) && echo $(2)
+	$(call docker_compose,exec app bash -c "$(1)")
 endef
-endif
 
-ifeq ($(RUN_ON),docker)
 define docker_compose
-	@docker compose$(if $(filter $(DOCKER_COMPOSE_YML_PATH),$(DOCKER_COMPOSE_YML_PATH)),, -f $(DOCKER_COMPOSE_YML_PATH)) $(1)
+	@docker compose $(1)
 endef
-else
-define docker_compose
-	$(call sub_step,$(DOCKER_WARNING_INSIDE))
-endef
-endif
+
diff --git a/tools/make/drupal.mk b/tools/make/drupal.mk
index 059237a40..1c4e59b27 100644
--- a/tools/make/drupal.mk
+++ b/tools/make/drupal.mk
@@ -1,59 +1,16 @@
-BUILD_TARGETS += drupal-create-folders
+DUMP_SQL_EXISTS := $(shell test -f dump.sql && echo yes || echo no)
 DRUPAL_CONF_EXISTS := $(shell test -f conf/cmi/core.extension.yml && echo yes || echo no)
-DRUPAL_FRESH_TARGETS := up build sync post-install
-DRUPAL_NEW_TARGETS := up build drush-si drush-uli
-DRUPAL_POST_INSTALL_TARGETS := drush-deploy
-CLEAN_EXCLUDE += $(WEBROOT)/sites/default/files
-DRUPAL_DISABLE_MODULES ?= no
-DRUPAL_ENABLE_MODULES ?= no
-DRUPAL_PROFILE ?= minimal
-DRUPAL_SITE_EMAIL ?= maintenance@druid.fi
-DRUPAL_SYNC_FILES ?= yes
-DRUPAL_SYNC_SOURCE ?= main
-DRUSH_RSYNC_MODE ?= Pakzu
-DRUSH_RSYNC_OPTS ?=  -- --omit-dir-times --no-perms --no-group --no-owner --chmod=ugo=rwX
-DRUSH_RSYNC_EXCLUDE ?= css:ctools:js:php:tmp:tmp_php
-SYNC_TARGETS += drush-sync
-SYNC_FROM_REMOTE ?= no
-CS_EXTS := inc,php,module,install,profile,theme
-CS_STANDARD_PATHS := vendor/drupal/coder/coder_sniffer,vendor/slevomat/coding-standard
-CS_STANDARDS := Drupal,DrupalPractice
-LINT_PATHS_JS += ./$(WEBROOT)/modules/custom/*/js
-LINT_PATHS_JS += ./$(WEBROOT)/themes/custom/*/js
-LINT_PATHS_PHP += drush
-LINT_PATHS_PHP += $(WEBROOT)/modules/custom
-LINT_PATHS_PHP += $(WEBROOT)/themes/custom
-LINT_PHP_TARGETS += lint-drupal
-FIX_TARGETS += fix-drupal
-DRUPAL_CREATE_FOLDERS := $(WEBROOT)/sites/default/files/private
-DRUPAL_CREATE_FOLDERS += $(WEBROOT)/sites/default/files/translations
-
-ifeq ($(LAGOON),yes)
-	SYNC_FROM_REMOTE := yes
-endif
 
-ifeq ($(GH_DUMP_ARTIFACT),yes)
-	DRUPAL_FRESH_TARGETS := gh-download-dump $(DRUPAL_FRESH_TARGETS)
-endif
+DRUPAL_CREATE_FOLDERS := /app/public/sites/default/files/private
+DRUPAL_CREATE_FOLDERS += /app/public/sites/default/files/translations
 
-ifneq ($(DRUPAL_DISABLE_MODULES),no)
-	SYNC_TARGETS += drush-disable-modules
-endif
-
-ifneq ($(DRUPAL_ENABLE_MODULES),no)
-	DRUPAL_POST_INSTALL_TARGETS += drush-enable-modules
-endif
+DRUPAL_PROFILE ?= minimal
 
 PHONY += drupal-create-folders
 drupal-create-folders:
 	$(call step,Create folders for Drupal...\n)
 	$(call docker_compose_exec,mkdir -v -p $(DRUPAL_CREATE_FOLDERS))
 
-PHONY += drupal-update
-drupal-update: ## Update Drupal core with Composer
-	$(call step,Update Drupal core with Composer...\n)
-	$(call composer,update -W "drupal/core-*")
-
 PHONY += drush-cex
 drush-cex: ## Export configuration
 	$(call step,Export configuration...\n)
@@ -78,7 +35,7 @@ drush-uli: DRUPAL_UID ?=
 drush-uli: DRUPAL_DESTINATION ?= admin/reports/status
 drush-uli: ## Get login link
 	$(call step,Login to your site with:\n)
-	$(call drush,uli$(if $(DRUPAL_UID), --uid=$(DRUPAL_UID),) $(DRUPAL_DESTINATION))
+	$(call drush,uli $(DRUPAL_DESTINATION))
 
 PHONY += drush-uli-%
 drush-uli-%: ## Get login link for provided uid
@@ -93,7 +50,13 @@ else
 endif
 drush-si: ## Site install
 	$(call step,Do Drush site:install...\n)
-	$(call drush,si ${DRUSH_SI} --site-mail=$(DRUPAL_SITE_EMAIL))
+	$(call drush,si ${DRUSH_SI})
+
+PHONY += drush-helfi-enable-modules
+drush-helfi-enable-modules: ## Enable modules and base configurations.
+	$(call step,Install base configurations...)
+	$(call drush,cr)
+	$(call drush,en -y helfi_platform_config helfi_platform_config_base)
 
 PHONY += drush-deploy
 drush-deploy: ## Run Drush deploy
@@ -113,69 +76,47 @@ drush-reset-local: ## Reset local configuration (cim, cr, updb, cr)
 	$(call drush,updb -y --no-cache-clear)
 	$(call drush,cr)
 
+PHONY += drush-unblock
+drush-unblock: ## Get login link
+	$(call step,Unblocking helfi-admin...\n)
+	$(call drush,user:unblock --uid=1)
+
+PHONY += drush-locale-update
+drush-locale-update: drupal-create-folders ## Update translations.
+	$(call step,Update translations...)
+	$(call drush,state:set locale.translation_last_checked 0)
+	$(call drush,locale:check)
+	$(call drush,locale:update)
+	$(call drush,cr)
+
+DRUPAL_POST_INSTALL_TARGETS := drush-sanitize-database drush-deploy drush-locale-update drush-unblock drush-uli
+
+DRUPAL_FRESH_TARGETS := up drupal-create-folders composer-install drush-import-dump $(DRUPAL_POST_INSTALL_TARGETS)
 PHONY += fresh
-fresh: ## Build fresh development environment and sync
-	@$(MAKE) $(DRUPAL_FRESH_TARGETS)
+fresh: $(DRUPAL_FRESH_TARGETS) ## Build fresh development environment and sync
 
+DRUPAL_NEW_TARGETS := up drupal-create-folders composer-install drush-si drush-helfi-enable-modules drush-cr drush-unblock drush-uli
 PHONY += new
-new: ## Create a new empty Drupal installation from configuration
-	@$(MAKE) $(DRUPAL_NEW_TARGETS)
-
-PHONY += post-install
-post-install: ## Run post-install Drush actions
-	@$(MAKE) $(DRUPAL_POST_INSTALL_TARGETS) drush-uli
-
-PHONY += drush-disable-modules
-drush-disable-modules: ## Disable Drupal modules
-	$(call step,Disable Drupal modules...\n)
-ifneq ($(DRUPAL_DISABLE_MODULES),no)
-	$(call drush,pmu -y $(subst ",,$(DRUPAL_DISABLE_MODULES)))
-else
-	$(call sub_step,No modules to disable)
-endif
-
-PHONY += drush-enable-modules
-drush-enable-modules: ## Enable Drupal modules
-	$(call step,Enable Drupal modules...\n)
-ifneq ($(DRUPAL_ENABLE_MODULES),no)
-	$(call drush,en -y $(subst ",,$(DRUPAL_ENABLE_MODULES)))
-else
-	$(call sub_step,No modules to enable)
-endif
+new: $(DRUPAL_NEW_TARGETS) ## Create a new empty Drupal installation from configuration
 
-PHONY += drush-sync
-drush-sync: drush-sync-db drush-sync-files ## Sync database and files
+dump.sql:
+	@touch /tmp/kube-config
+	@docker run --pull=always --env-file .env -it --rm -v /tmp/kube-config:/root/.kube/config -v $(shell pwd):/app --name helfi-oc ghcr.io/city-of-helsinki/drupal-oc-cli:latest sh -c "db-sync"
+	$(call docker_compose_exec,gunzip dump.sql.gz)
 
-PHONY += drush-sync-db
-drush-sync-db: ## Sync database
+PHONY += drush-import-dump
+drush-import-dump: dump.sql
 	$(call drush,sql-drop --quiet -y)
-ifeq ($(DUMP_SQL_EXISTS),yes)
 	$(call step,Import local SQL dump...)
-	$(call drush,sql-query --file=${DOCKER_PROJECT_ROOT}/$(DUMP_SQL_FILENAME) && echo 'SQL dump imported')
-else
-ifeq ($(SYNC_FROM_REMOTE),yes)
-	$(call step,Sync database from @$(DRUPAL_SYNC_SOURCE)...)
-	$(call drush,sql-sync -y --structure-tables-key=common @$(DRUPAL_SYNC_SOURCE) @self)
-endif
-endif
+	$(call drush,sql-query --file=/app/dump.sql && echo 'SQL dump imported')
 
-PHONY += drush-sync-files
-drush-sync-files: ## Sync files
-ifeq ($(DRUPAL_SYNC_FILES),yes)
-ifeq ($(SYNC_FROM_REMOTE),yes)
-	$(call step,Sync files from @$(DRUPAL_SYNC_SOURCE)...)
-	$(call drush,-y rsync --exclude-paths=$(DRUSH_RSYNC_EXCLUDE) --mode=$(DRUSH_RSYNC_MODE) @$(DRUPAL_SYNC_SOURCE):%files @self:%files $(DRUSH_RSYNC_OPTS))
-endif
-endif
+PHONY += drush-sanitize-database
+drush-sanitize-database:
+	$(call drush,sql-query \"UPDATE file_managed SET uri = REPLACE(uri, 'azure://', 'public://');\",Fixed Azure URIs)
 
 PHONY += drush-create-dump
-drush-create-dump: FLAGS := --structure-tables-key=common --extra-dump=--no-tablespaces
 drush-create-dump: ## Create database dump to dump.sql
-	$(call drush,sql-dump $(FLAGS) --result-file=${DOCKER_PROJECT_ROOT}/$(DUMP_SQL_FILENAME))
-
-PHONY += drush-download-dump
-drush-download-dump: ## Download database dump to dump.sql
-	$(call drush,@$(DRUPAL_SYNC_SOURCE) sql-dump --structure-tables-key=common > ${DOCKER_PROJECT_ROOT}/$(DUMP_SQL_FILENAME))
+	$(call drush,sql-dump --structure-tables-key=common --extra-dump=--no-tablespaces --result-file=/app/dump.sql)
 
 PHONY += open-db-gui
 open-db-gui: ## Open database with GUI tool
@@ -185,30 +126,6 @@ open-db-gui: ## Open database with GUI tool
 	$(eval DB_PASS ?= drupal)
 	@open mysql://$(DB_USER):$(DB_PASS)@$(shell docker compose port $(DB_SERVICE) 3306 | grep -v ::)/$(DB_NAME)
 
-PHONY += fix-drupal
-fix-drupal: PATHS := $(subst $(space),,$(LINT_PATHS_PHP))
-fix-drupal: ## Fix Drupal code style
-	$(call step,Fix Drupal code style with phpcbf...\n)
-	$(call cs,phpcbf,$(PATHS))
-
-PHONY += lint-drupal
-lint-drupal: PATHS := $(subst $(space),,$(LINT_PATHS_PHP))
-lint-drupal: ## Lint Drupal code style
-	$(call step,Lint Drupal code style with phpcs...\n)
-	$(call cs,phpcs,$(PATHS))
-
-PHONY += mmfix
-mmfix: MODULE := MISSING_MODULE
-mmfix:
-	$(call step,Remove missing module '$(MODULE)'\n)
-	$(call drush,sql-query \"DELETE FROM key_value WHERE collection='system.schema' AND name='$(MODULE)';\",Module was removed)
-
-ifeq ($(RUN_ON),docker)
 define drush
 	$(call docker_compose_exec,drush $(1),$(2))
 endef
-else
-define drush
-	@drush $(1)
-endef
-endif
diff --git a/tools/make/project/git.mk b/tools/make/git.mk
similarity index 54%
rename from tools/make/project/git.mk
rename to tools/make/git.mk
index c598f4d7b..aa22a43e7 100644
--- a/tools/make/project/git.mk
+++ b/tools/make/git.mk
@@ -1,3 +1,3 @@
 PHONY += copy-commit-message-script
 copy-commit-message-script:
-	@$(foreach name,$(shell find . -type d -name ".git" -exec dirname {} \; 2> /dev/null ),cp tools/commit-msg $(name)/.git/hooks;)
+	@$(foreach name,$(shell find . -type d -name ".git" -exec dirname {} \; 2> /dev/null ),cp tools/commit-msg $(name)/.git/hooks && chmod +x $(name)/.git/hooks/commit-msg;)
diff --git a/tools/make/include.mk b/tools/make/include.mk
deleted file mode 100644
index de30c41e6..000000000
--- a/tools/make/include.mk
+++ /dev/null
@@ -1,60 +0,0 @@
-include $(DRUIDFI_TOOLS_MAKE_DIR)common.mk
-
-ifeq ($(call has,docker),yes)
-include $(DRUIDFI_TOOLS_MAKE_DIR)docker.mk
-endif
-
-include $(DRUIDFI_TOOLS_MAKE_DIR)qa.mk
-
-#
-# Apps
-#
-
-IS_DRUPAL ?= $(shell test -f $(WEBROOT)/sites/default/settings.php && echo yes || echo no)
-IS_SYMFONY ?= $(shell test -f config/bundles.php && echo yes || echo no)
-
-ifeq ($(IS_DRUPAL),yes)
-include $(DRUIDFI_TOOLS_MAKE_DIR)drupal.mk
-endif
-
-ifeq ($(IS_SYMFONY),yes)
-include $(DRUIDFI_TOOLS_MAKE_DIR)symfony.mk
-endif
-
-#
-# Other tools
-#
-
-HAS_ANSIBLE ?= $(shell test -d ansible && echo yes || echo no)
-
-ifeq ($(HAS_ANSIBLE),yes)
-include $(DRUIDFI_TOOLS_MAKE_DIR)ansible.mk
-endif
-
-#
-# Hosting systems
-#
-
-LAGOON := $(shell test -f .lagoon.yml && echo yes || echo no)
-
-ifeq ($(LAGOON),yes)
-	SYSTEM := LAGOON
-else
-	SYSTEM := WHOKNOWS
-endif
-
-ifeq ($(SYSTEM),LAGOON)
-include $(DRUIDFI_TOOLS_MAKE_DIR)lagoon.mk
-endif
-
-COMPOSER_JSON_EXISTS := $(shell test -f $(COMPOSER_JSON_PATH)/composer.json && echo yes || echo no)
-
-ifeq ($(COMPOSER_JSON_EXISTS),yes)
-include $(DRUIDFI_TOOLS_MAKE_DIR)composer.mk
-endif
-
-PACKAGE_JSON_EXISTS := $(shell test -f $(PACKAGE_JSON_PATH)/package.json && echo yes || echo no)
-
-ifeq ($(PACKAGE_JSON_EXISTS),yes)
-include $(DRUIDFI_TOOLS_MAKE_DIR)javascript.mk
-endif
diff --git a/tools/make/javascript.mk b/tools/make/javascript.mk
deleted file mode 100644
index 9b8c2fcfa..000000000
--- a/tools/make/javascript.mk
+++ /dev/null
@@ -1,44 +0,0 @@
-BUILD_TARGETS += js-install
-JS_PACKAGE_MANAGER ?= yarn
-JS_PACKAGE_MANAGER_CWD_FLAG_NPM ?= --prefix
-JS_PACKAGE_MANAGER_CWD_FLAG_YARN ?= --cwd
-INSTALLED_NODE_VERSION := $(shell command -v node > /dev/null && node --version | cut -c2-3 || echo no)
-NVM_SH := $(HOME)/.nvm/nvm.sh
-NVM := $(shell test -f "$(NVM_SH)" && echo yes || echo no)
-NODE_BIN := $(shell command -v node || echo no)
-NPM_BIN := $(shell command -v npm || echo no)
-YARN_BIN := $(shell command -v yarn || echo no)
-NODE_VERSION ?= 16
-
-PHONY += js-install
-js-install: ## Install JS packages
-ifeq ($(JS_PACKAGE_MANAGER),yarn)
-	$(call node_run,install --frozen-lockfile)
-else
-	$(call node_run,install --no-audit --no-fund --engine-strict true)
-endif
-
-PHONY += js-outdated
-js-outdated: ## Show outdated JS packages
-	$(call step,Show outdated JS packages with $(JS_PACKAGE_MANAGER)...)
-	$(call node_run,outdated)
-
-ifeq ($(NVM),no)
-define node_run
-	$(call error,$(NVM_REQUIRED))
-endef
-else
-define node_run
-	$(call step,Run '$(JS_PACKAGE_MANAGER) $(1)' with Node $(NODE_VERSION)...\n)
-	@. $(NVM_SH) && (nvm which $(NODE_VERSION) > /dev/null 2>&1 || nvm install $(NODE_VERSION)) && \
-		nvm exec $(NODE_VERSION) $(JS_PACKAGE_MANAGER) $(if $(filter $(JS_PACKAGE_MANAGER),yarn),$(JS_PACKAGE_MANAGER_CWD_FLAG_YARN),$(JS_PACKAGE_MANAGER_CWD_FLAG_NPM)) $(PACKAGE_JSON_PATH) $(1)
-endef
-endif
-
-define NVM_REQUIRED
-
-
-🚫 NVM is required to run $(JS_PACKAGE_MANAGER) commands and control Node versions!
-
-
-endef
diff --git a/tools/make/kubectl.mk b/tools/make/kubectl.mk
deleted file mode 100644
index 60f5003e8..000000000
--- a/tools/make/kubectl.mk
+++ /dev/null
@@ -1,56 +0,0 @@
-KUBECTL_BIN := $(shell command -v kubectl || echo no)
-KUBECTL_NAMESPACE ?= foobar-namespace
-KUBECTL_SHELL ?= sh
-KUBECTL_EXEC_FLAGS ?= -n $(KUBECTL_NAMESPACE) -c $(KUBECTL_CONTAINER)
-KUBECTL_POD_SELECTOR ?= appName=foobar-app
-KUBECTL_WORKDIR ?= /app
-
-PHONY += kubectl-sync-db
-kubectl-sync-db: ## Sync database from Kubernetes
-	$(call drush,sql-drop --quiet -y)
-ifeq ($(DUMP_SQL_EXISTS),no)
-	$(eval POD := $(call kubectl_get_pod))
-	$(call step,Get database dump from $(POD)...\n)
-	$(KUBECTL_BIN) exec $(KUBECTL_EXEC_FLAGS) $(POD) -- drush sql-dump --structure-tables-key=common --extra-dump=--no-tablespaces --result-file=/tmp/$(DUMP_SQL_FILENAME) --gzip
-	$(KUBECTL_BIN) cp $(KUBECTL_EXEC_FLAGS) --retries=-1 $(POD):/tmp/$(DUMP_SQL_FILENAME).gz ./$(DUMP_SQL_FILENAME).gz
-	$(KUBECTL_BIN) exec $(KUBECTL_EXEC_FLAGS) $(POD) -- rm -f /tmp/$(DUMP_SQL_FILENAME).gz
-	@gzip -d $(DUMP_SQL_FILENAME).gz
-endif
-	$(call step,Import local SQL dump...\n)
-	$(call drush,sql-query --file=${DOCKER_PROJECT_ROOT}/$(DUMP_SQL_FILENAME))
-
-PHONY += kubectl-sync-files-tar
-kubectl-sync-files-tar: ## Sync files from Kubernetes using tar
-	$(call step,Copy files from remote...\n)
-	$(eval POD := $(call kubectl_get_pod))
-	$(KUBECTL_BIN) exec $(KUBECTL_EXEC_FLAGS) $(POD) -- tar cf - $(SYNC_FILES_EXCLUDE) $(SYNC_FILES_PATH) | tar xfv - -C .
-
-PHONY += kubectl-rsync-files
-kubectl-rsync-files: FLAGS := -aurP --blocking-io
-kubectl-rsync-files: REMOTE_PATH := $(KUBECTL_WORKDIR)/$(SYNC_FILES_PATH)/
-kubectl-rsync-files: LOCAL_PATH := ./$(SYNC_FILES_PATH)/
-kubectl-rsync-files: ## Sync files from Kubernetes using rsync
-	$(call step,Sync files from remote...\n)
-	$(eval POD := $(call kubectl_get_pod))
-	rsync $(FLAGS) $(SYNC_FILES_EXCLUDE) --rsync-path=$(REMOTE_PATH) -e '$(KUBECTL_BIN) exec -i $(KUBECTL_EXEC_FLAGS) $(POD) -- env ' rsync: $(LOCAL_PATH)
-
-PHONY += kubectl-shell
-kubectl-shell: ## Open shell to Pod in Kubernetes
-	$(eval POD := $(call kubectl_get_pod))
-	$(KUBECTL_BIN) exec $(KUBECTL_EXEC_FLAGS) -ti $(POD) -- $(KUBECTL_SHELL)
-
-define kubectl_exec
-	$(KUBECTL_BIN) exec $(KUBECTL_EXEC_FLAGS) $(1) -- $(KUBECTL_SHELL) -c '$(2)'
-endef
-
-define kubectl_exec_to_file
-	$(KUBECTL_BIN) exec $(KUBECTL_EXEC_FLAGS) $(1) -- $(KUBECTL_SHELL) -c '$(2)' > $(3)
-endef
-
-define kubectl_cp
-	$(KUBECTL_BIN) cp $(KUBECTL_EXEC_FLAGS) $(1) $(2)
-endef
-
-define kubectl_get_pod
-	$(shell $(KUBECTL_BIN) get pods -n $(KUBECTL_NAMESPACE) --selector=$(KUBECTL_POD_SELECTOR) --template '{{range .items}}{{ if not .metadata.deletionTimestamp }}{{.metadata.name}}{{"\n"}}{{end}}{{end}}')
-endef
diff --git a/tools/make/lagoon.mk b/tools/make/lagoon.mk
deleted file mode 100644
index e6baebad4..000000000
--- a/tools/make/lagoon.mk
+++ /dev/null
@@ -1,38 +0,0 @@
-CLI_SERVICE := cli
-CLI_SHELL := bash
-DB_SERVICE := mariadb
-
-INSTANCE_prod_USER ?= project-name-branch
-INSTANCE_prod_HOST ?= ssh.lagoon.amazeeio.cloud
-INSTANCE_prod_OPTS ?= $(SSH_OPTS) -p 32222 -t
-INSTANCE_test_USER ?= project-name-branch
-INSTANCE_test_HOST ?= $(INSTANCE_prod_HOST)
-INSTANCE_test_OPTS ?= $(INSTANCE_prod_OPTS)
-
-ifeq ($(MAKECMDGOALS),set-lagoon-secrets)
-include .env.local.lagoon
-endif
-
-PHONY += lagoon-env
-lagoon-env: ## Print Lagoon env variables
-	$(call docker_compose_exec,printenv | grep LAGOON_)
-
-PHONY += deploy-lagoon-%
-deploy-lagoon-%: ## Deploy lagoon branch
-	$(call step,Deploy Lagoon branch $*...\n)
-	@lagoon -p $(LAGOON_PROJECT) deploy branch -b $*
-
-PHONY += set-lagoon-secrets-%
-set-lagoon-secrets-%: ## Set Lagoon secrets
-		$(call step,Set Lagoon secrets on $*...\n)
-		@$(foreach secret,$(LAGOON_SECRETS),$(call set_lagoon_secret,$(secret),$*))
-
-PHONY += list-lagoon-vars-%
-list-lagoon-vars-%: ## List variables from Lagoon
-		$(call step,List variables from Lagoon on $*...\n)
-		@lagoon -p $(LAGOON_PROJECT) list v --reveal -e $*
-
-define set_lagoon_secret
-printf "Setting secret on ${2}: %s = %s \n" "${1}" "${${1}}";
-lagoon -p $(LAGOON_PROJECT) a v -N "${1}" -V "${${1}}" -S runtime -e ${2} --force || true;
-endef
diff --git a/tools/make/override.mk b/tools/make/override.mk
deleted file mode 100644
index df8baac5e..000000000
--- a/tools/make/override.mk
+++ /dev/null
@@ -1,3 +0,0 @@
-# Docker CLI container
-CLI_SERVICE=app
-CLI_SHELL=bash
diff --git a/tools/make/project/composer.mk b/tools/make/project/composer.mk
deleted file mode 100644
index aefdbcc64..000000000
--- a/tools/make/project/composer.mk
+++ /dev/null
@@ -1,3 +0,0 @@
-PHONY += insert-post-install-hooks
-
-insert-post-install-hooks:
diff --git a/tools/make/project/db-sync.sh b/tools/make/project/db-sync.sh
deleted file mode 100644
index 2d69f38ed..000000000
--- a/tools/make/project/db-sync.sh
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/bin/sh
-
-set -e
-
-if [ ! -n "$OC_PROJECT_NAME" ]; then
-  echo "OC_PROJECT_NAME not set."
-  exit 1;
-fi
-
-oc login --token=${1} --server=https://api.arodevtest.hel.fi:6443
-oc project ${OC_PROJECT_NAME}
-
-OC_POD_NAME=$(oc get pods -o name | grep drupal-cron | grep -v deploy)
-
-if [ ! -n "$OC_POD_NAME" ]; then
-  echo "Failed to parse pod name."
-  exit 1
-fi
-
-oc rsh $OC_POD_NAME rm -f /tmp/dump.sql
-oc rsh $OC_POD_NAME drush sql:dump --structure-tables-key=common \
-  --extra-dump='--no-tablespaces --hex-blob' \
-  --result-file=/tmp/dump.sql
-
-oc rsync $OC_POD_NAME:/tmp/dump.sql /app
diff --git a/tools/make/project/install.mk b/tools/make/project/install.mk
deleted file mode 100644
index 1982e3da4..000000000
--- a/tools/make/project/install.mk
+++ /dev/null
@@ -1,49 +0,0 @@
-ifeq ($(DRUPAL_CONF_EXISTS),yes)
-	DRUPAL_NEW_TARGETS := up build drush-si drush-cr drush-locale-update drush-helfi-locale-import drush-unblock drush-uli
-else
-	DRUPAL_NEW_TARGETS := up build drush-si drush-helfi-enable-modules drush-locale-update drush-helfi-locale-import drush-unblock drush-uli
-endif
-DRUPAL_POST_INSTALL_TARGETS := drush-deploy drush-locale-update drush-helfi-locale-import drush-unblock
-
-OC_LOGIN_TOKEN ?= $(shell bash -c 'read -s -p "You must obtain an API token by visiting https://oauth-openshift.apps.arodevtest.hel.fi/oauth/token/request (Token):" token; echo $$token')
-
-SYNC_TARGETS := drush-sync-db
-
-ifneq ($(DUMP_SQL_EXISTS),yes)
-SYNC_TARGETS := oc-sync
-endif
-
-PHONY += oc-sync
-oc-sync:
-	@docker run --env-file .env -it --rm -v $(shell pwd):/app --name helfi-oc ghcr.io/city-of-helsinki/drupal-oc-cli:latest sh -c "chmod +x /app/tools/make/project/db-sync.sh && /app/tools/make/project/db-sync.sh $(OC_LOGIN_TOKEN)"
-	$(call drush,sql-query --file=${DOCKER_PROJECT_ROOT}/$(DUMP_SQL_FILENAME),SQL dump imported)
-	$(call drush,sql-query \"UPDATE file_managed SET uri = REPLACE(uri, 'azure://', 'public://');\",Sanitized Azure URIs)
-	$(call drush,cr)
-	$(call drush,cim -y)
-	$(call drush,cr)
-
-PHONY += drush-helfi-enable-modules
-drush-helfi-enable-modules: ## Enable modules and base configurations.
-	$(call step,Install base configurations...)
-	$(call drush,cr)
-	$(call drush,en -y helfi_platform_config helfi_platform_config_base)
-
-PHONY += drush-locale-update
-drush-locale-update: ## Update translations.
-	$(call step,Update translations...)
-	@mkdir -p public/sites/default/files/translations
-	$(call drush,state:set locale.translation_last_checked 0)
-	$(call drush,locale:check)
-	$(call drush,locale:update)
-	$(call drush,cr)
-
-PHONY += drush-helfi-locale-import
-drush-helfi-locale-import:  ## Update translations from helfi platform config.
-	$(call step,Import helfi platform config translations...)
-	$(call drush,helfi:locale-import helfi_platform_config)
-	$(call drush,cr)
-
-PHONY += drush-unblock
-drush-unblock: ## Get login link
-	$(call step,Unblocking helfi-admin...\n)
-	$(call drush,user:unblock --uid=1)
diff --git a/tools/make/qa.mk b/tools/make/qa.mk
index f4988fbea..4a9427e1d 100644
--- a/tools/make/qa.mk
+++ b/tools/make/qa.mk
@@ -1,73 +1,33 @@
-TEST_TARGETS += test-phpunit
-FIX_TARGETS :=
-LINT_PHP_TARGETS :=
-CS_INSTALLED := $(shell test -f $(COMPOSER_JSON_PATH)/vendor/bin/phpcs && echo yes || echo no)
-CS_CONF_EXISTS := $(shell test -f phpcs.xml.dist && echo yes || echo no)
-TESTSUITES ?= unit,kernel,functional
+LINT_PHP_STANDARDS := Drupal,DrupalPractice
+LINT_PHP_EXTENSIONS := inc,php,module,install,profile,theme
+
+LINT_PATHS_PHP := /app/public/modules/custom
+LINT_PATHS_PHP += /app/public/themes/custom
 
 PHONY += fix
-fix: ## Fix code style
-	$(call step,Fix code...)
-	$(call sub_step,Following targets will be run: $(FIX_TARGETS))
-	@$(MAKE) $(FIX_TARGETS)
+fix: fix-php ## Fix code style
 
-PHONY += lint
-lint: lint-php lint-js ## Check code style
+PHONY += fix-php
+fix-php:
+	$(call step,Fix code using phpcbf ($(LINT_PATHS_PHP))...)
+	$(call cs,phpcbf)
 
-PHONY += lint-js
-lint-js: DOCKER_NODE_IMG ?= node:$(NODE_VERSION)-alpine
-lint-js: WD := /app
-lint-js: ## Check code style for JS files
-	$(call step,Install linters...)
-	@docker run --rm -v "$(CURDIR)":$(WD):cached -w $(WD) $(DOCKER_NODE_IMG) yarn --cwd $(WEBROOT)/core install
-	$(call step,Check code style for JS files: $(DRUPAL_LINT_PATHS))
-	@docker run --rm -v "$(CURDIR)":$(WD):cached -w $(WD) $(DOCKER_NODE_IMG) \
-		$(WEBROOT)/core/node_modules/eslint/bin/eslint.js --color --ignore-pattern '**/vendor/*' \
-		--c ./$(WEBROOT)/core/.eslintrc.json --global nav,moment,responsiveNav:true $(LINT_PATHS_JS)
+PHONY += lint
+lint: lint-php ## Check code style
 
 PHONY += lint-php
 lint-php: ## Check code style for PHP files
-	$(call step,Check code style for PHP files...)
-	$(call sub_step,Following targets will be run: $(LINT_PHP_TARGETS))
-	@$(MAKE) $(LINT_PHP_TARGETS)
-	$(call test_result,lint-php,"[OK]")
+	$(call step,Check code style for PHP files ($(LINT_PATHS_PHP))...)
+	$(call cs,phpcs)
 
 PHONY += test
-test: ## Run tests
-	$(call group_step,Run test targets:${NO_COLOR} $(TEST_TARGETS)\n)
-	@$(MAKE) $(TEST_TARGETS)
-	$(call step,Tests completed.)
+test: test-phpunit ## Run tests
 
 PHONY += test-phpunit
 test-phpunit: ## Run PHPUnit tests
 	$(call step,Run PHPUnit tests...)
-ifeq ($(CI),true)
-	vendor/bin/phpunit -c phpunit.xml.dist --testsuite $(TESTSUITES)
-else
-	$(call docker_compose_exec,${DOCKER_PROJECT_ROOT}/vendor/bin/phpunit -c $(DOCKER_PROJECT_ROOT)/phpunit.xml.dist \
-		--testsuite $(TESTSUITES))
-endif
-	$(call test_result,test-phpunit,"[OK]")
-
-PHONY += test-phpunit-locally
-test-phpunit-locally:
-	@SIMPLETEST_BASE_URL=https://$(DRUPAL_HOSTNAME) SIMPLETEST_DB=mysql://$(DB_URL) \
-    		vendor/bin/phpunit -c $(CURDIR)/phpunit.xml.dist --testsuite $(TESTSUITES)
-
-define test_result
-	@echo "\n${YELLOW}${1}:${NO_COLOR} ${GREEN}${2}${NO_COLOR}"
-endef
+	$(call docker_compose_exec,/app/vendor/bin/phpunit -c /app/phpunit.xml.dist $(1))
 
-ifeq ($(CS_INSTALLED)-$(CS_CONF_EXISTS),yes-yes)
-define cs
-$(call docker_compose_exec,vendor/bin/$(1))
-endef
-else ifeq ($(CS_INSTALLED)-$(CS_CONF_EXISTS),yes-no)
-define cs
-$(call docker_compose_exec,vendor/bin/$(1) --standard=$(CS_STANDARDS) --extensions=$(CS_EXTS) --ignore=node_modules $(2))
-endef
-else
 define cs
-$(call warn,CodeSniffer is not installed!)
+	$(call docker_compose_exec,vendor/bin/$(1) --standard=$(LINT_PHP_STANDARDS) --extensions=$(LINT_PHP_EXTENSIONS) --ignore=node_modules $(LINT_PATHS_PHP))
 endef
-endif
diff --git a/tools/make/symfony.mk b/tools/make/symfony.mk
deleted file mode 100644
index 736720756..000000000
--- a/tools/make/symfony.mk
+++ /dev/null
@@ -1,78 +0,0 @@
-SF_FRESH_TARGETS := up build sf-cw sf-about sf-open
-FIX_TARGETS += fix-symfony
-LINT_PHP_TARGETS += lint-symfony
-CS_FIXER_INSTALLED := $(shell test -f $(COMPOSER_JSON_PATH)/vendor/bin/php-cs-fixer && echo yes || echo no)
-
-PHONY += encore-dev
-encore-dev: ## Do Encore development build
-	$(call step,Do Encore development build...)
-	$(call node_run,dev)
-
-PHONY += encore-watch
-encore-watch: ## Run Encore watch
-	$(call step,Do Encore watch...)
-	$(call node_run,watch)
-
-PHONY += sf-about
-sf-about: ## Displays information about the current project
-	$(call sf_console,about)
-
-PHONY += sf-cc
-sf-cc: ## Clear Symfony caches
-	$(call step,Clear Symfony caches...)
-	$(call sf_console,cache:clear)
-
-PHONY += sf-cw
-sf-cw: ## Warm Symfony caches
-	$(call step,Warm Symfony caches...)
-	$(call sf_console,cache:warmup)
-
-PHONY += sf-db-init
-sf-db-init: ## Setup database schema and load fixtures
-	$(call step,Setup database schema...)
-	$(call sf_console,doctrine:schema:update --force)
-	$(call sf_console,doctrine:fixtures:load -n)
-
-PHONY += sf-open
-sf-open: ## Warm Symfony caches
-	$(call step,See your Symfony application with:\n)
-	$(call output,https://$(APP_HOST))
-
-PHONY += sf-update
-sf-update: ## Update Symfony packages with Composer
-	$(call step,Update Symfony packages with Composer...\n)
-	$(call composer,update -W "doctrine/*" "symfony/*" "twig/*" --no-scripts)
-
-PHONY += fresh
-fresh: ## Build fresh development environment
-	@$(MAKE) $(SF_FRESH_TARGETS)
-
-PHONY += fix-symfony
-fix-symfony: ## Fix Symfony code style
-	$(call step,Fix Symfony code style...\n)
-	$(call cs_symfony,fix --ansi src)
-
-PHONY += lint-symfony
-lint-symfony: ## Lint Symfony code style
-	$(call step,Lint Symfony code style...\n)
-	$(call cs_symfony,fix --dry-run --diff --ansi --verbose src)
-
-ifeq ($(RUN_ON),docker)
-define sf_console
-	$(call docker_compose_exec,bin/console $(1))
-endef
-else
-define sf_console
-	@bin/console $(1)
-endef
-endif
-
-ifeq ($(CS_FIXER_INSTALLED),yes)
-define cs_symfony
-$(call docker_compose_exec,vendor/bin/php-cs-fixer $(1))
-endef
-else
-define cs_symfony
-$(call warn,PHP CS Fixer is not installed!)
-endef
-endif
diff --git a/tools/make/project/theme.mk b/tools/make/theme.mk
similarity index 98%
rename from tools/make/project/theme.mk
rename to tools/make/theme.mk
index fd34d5d12..2657775c0 100644
--- a/tools/make/project/theme.mk
+++ b/tools/make/theme.mk
@@ -1,4 +1,4 @@
-DEFAULT_NODE_VERSION ?= 18
+DEFAULT_NODE_VERSION ?= 20
 NODE_DOCKER_RUN_EXTRA_ARGS ?= -it
 
 ifeq ($(CI),true)
diff --git a/tools/make/utils.mk b/tools/make/utils.mk
deleted file mode 100644
index 0feb372a1..000000000
--- a/tools/make/utils.mk
+++ /dev/null
@@ -1,74 +0,0 @@
-# Colors
-NO_COLOR=\033[0m
-CYAN=\033[36m
-GREEN=\033[0;32m
-RED=\033[0;31m
-YELLOW=\033[0;33m
-
-PHONY += help
-help: ## List all make commands
-	$(call step,Available make commands:\n)
-	@cat $(MAKEFILE_LIST) | grep -e "^[a-zA-Z_\-]*: *.*## *" | awk 'BEGIN {FS = ":.*?## "}; {printf "${CYAN}%-30s${NO_COLOR} %s\n", $$1, $$2}' | sort
-
-PHONY += lt
-lt: ## Open localtunnel
-ifeq ($(shell command -v lt || echo no),no)
-	$(call warn,Install localtunnel with: ${YELLOW}npm install -g localtunnel${NO_COLOR})
-else
-	$(call step,Open localtunnel. Use CTRL+C to close localtunnel.\n)
-	@lt --port 443 --subdomain $(COMPOSE_PROJECT_NAME) --local-https --allow-invalid-cert
-endif
-
-define dbg
-	@printf "${GREEN}${1}:${NO_COLOR} ${2}\n"
-endef
-
-define group_step
-	@printf "\n🌟 ${YELLOW}${1}${NO_COLOR}\n"
-endef
-
-define has
-$(shell command -v ${1} > /dev/null 2>&1 && echo yes || echo no)
-endef
-
-define step
-	@printf "\n⭐ ${YELLOW}${1}${NO_COLOR}\n"
-endef
-
-define sub_step
-	@printf "\n   ${YELLOW}${1}${NO_COLOR}\n"
-endef
-
-define output
-	@echo "${1}"
-endef
-
-define warn
-	@printf "\n⚠️  ${1}\n\n"
-endef
-
-define copy
-	$(call output,Copy $(1) >> $(2))
-	@cp $(1) $(2)
-endef
-
-SED_Darwin := sed -i ''
-SED_Linux := sed -i
-
-define get_port
-$(shell netstat -aln|awk '$$6=="LISTEN"{if($$4~"[.:][0-9]+$$"){split($$4,a,/[:.]/);p2=a[length(a)];p[p2]=1;}}END{for(i=3000;i<3999&&p[i];i++){};if(i==3999){exit 1};print i}')
-endef
-
-define replace_string
-	$(call output,Replace $(1) >> $(2) in $(3))
-	@$(SED_$(UNAME_S)) 's/$(1)/$(2)/g' $(3)
-endef
-
-define remove_string
-	$(call output,Remove $(1) from $(2))
-	@$(SED_$(UNAME_S)) '/$(1)/d' $(2)
-endef
-
-define run
-	@${1} && printf "${2}\n" || (printf "${RED}${3}${NO_COLOR}\n" && exit 1)
-endef