diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index f36b01518e..91f4ffce4f 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -17,6 +17,7 @@ jobs:
     permissions:
       contents: read
     outputs:
+      should_run_i18n: ${{ steps.found_paths.outputs.i18n == 'true' || steps.should_force.outputs.should_force == 'true' }}
       should_run_web: ${{ steps.found_paths.outputs.web == 'true' || steps.should_force.outputs.should_force == 'true' }}
       should_run_server: ${{ steps.found_paths.outputs.server == 'true' || steps.should_force.outputs.should_force == 'true' }}
       should_run_cli: ${{ steps.found_paths.outputs.cli == 'true' || steps.should_force.outputs.should_force == 'true' }}
@@ -36,6 +37,8 @@ jobs:
         uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
         with:
           filters: |
+            i18n:
+              - 'i18n/**'
             web:
               - 'web/**'
               - 'i18n/**'
@@ -262,6 +265,46 @@ jobs:
         run: npm run test:cov
         if: ${{ !cancelled() }}
 
+  i18n-tests:
+    name: Test i18n
+    needs: pre-job
+    if: ${{ needs.pre-job.outputs.should_run_i18n == 'true' }}
+    runs-on: ubuntu-latest
+    permissions:
+      contents: read
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+        with:
+          persist-credentials: false
+
+      - name: Setup Node
+        uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
+        with:
+          node-version-file: './web/.nvmrc'
+
+      - name: Install dependencies
+        run: npm --prefix=web ci
+
+      - name: Format
+        run: npm --prefix=web run format:i18n
+
+      - name: Find file changes
+        uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4
+        id: verify-changed-files
+        with:
+          files: |
+            i18n/**
+
+      - name: Verify files have not changed
+        if: steps.verify-changed-files.outputs.files_changed == 'true'
+        env:
+          CHANGED_FILES: ${{ steps.verify-changed-files.outputs.changed_files }}
+        run: |
+          echo "ERROR: i18n files not up to date!"
+          echo "Changed files: ${CHANGED_FILES}"
+          exit 1
+
   e2e-tests-lint:
     name: End-to-End Lint
     needs: pre-job
diff --git a/i18n/en.json b/i18n/en.json
index 05b236b33a..e4fc825cda 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -1,32 +1,4 @@
 {
-  "new_pin_code_subtitle": "This is your first time accessing the locked folder. Create a PIN code to securely access this page",
-  "enter_your_pin_code": "Enter your PIN code",
-  "enter_your_pin_code_subtitle": "Enter your PIN code to access the locked folder",
-  "pin_verification": "PIN code verification",
-  "wrong_pin_code": "Wrong PIN code",
-  "nothing_here_yet": "Nothing here yet",
-  "move_to_locked_folder": "Move to Locked Folder",
-  "remove_from_locked_folder": "Remove from Locked Folder",
-  "move_to_locked_folder_confirmation": "These photos and video will be removed from all albums, and only viewable from the Locked Folder",
-  "remove_from_locked_folder_confirmation": "Are you sure you want to move these photos and videos out of Locked Folder? They will be visible in your library",
-  "move": "Move",
-  "no_locked_photos_message": "Photos and videos in Locked Folder are hidden and won't show up as you browser your library.",
-  "locked_folder": "Locked Folder",
-  "add_to_locked_folder": "Add to Locked Folder",
-  "move_off_locked_folder": "Move out of Locked Folder",
-  "user_pin_code_settings": "PIN Code",
-  "user_pin_code_settings_description": "Manage your PIN code",
-  "current_pin_code": "Current PIN code",
-  "new_pin_code": "New PIN code",
-  "setup_pin_code": "Setup a PIN code",
-  "confirm_new_pin_code": "Confirm new PIN code",
-  "change_pin_code": "Change PIN code",
-  "unable_to_change_pin_code": "Unable to change PIN code",
-  "unable_to_setup_pin_code": "Unable to setup PIN code",
-  "pin_code_changed_successfully": "Successfully changed PIN code",
-  "pin_code_setup_successfully": "Successfully setup a PIN code",
-  "pin_code_reset_successfully": "Successfully reset PIN code",
-  "reset_pin_code": "Reset PIN code",
   "about": "About",
   "account": "Account",
   "account_settings": "Account Settings",
@@ -54,6 +26,7 @@
   "add_to_album": "Add to album",
   "add_to_album_bottom_sheet_added": "Added to {album}",
   "add_to_album_bottom_sheet_already_exists": "Already in {album}",
+  "add_to_locked_folder": "Add to Locked Folder",
   "add_to_shared_album": "Add to shared album",
   "add_url": "Add URL",
   "added_to_archive": "Added to archive",
@@ -640,6 +613,7 @@
   "change_password_form_new_password": "New Password",
   "change_password_form_password_mismatch": "Passwords do not match",
   "change_password_form_reenter_new_password": "Re-enter New Password",
+  "change_pin_code": "Change PIN code",
   "change_your_password": "Change your password",
   "changed_visibility_successfully": "Changed visibility successfully",
   "check_all": "Check All",
@@ -680,6 +654,7 @@
   "confirm_delete_face": "Are you sure you want to delete {name} face from the asset?",
   "confirm_delete_shared_link": "Are you sure you want to delete this shared link?",
   "confirm_keep_this_delete_others": "All other assets in the stack will be deleted except for this asset. Are you sure you want to continue?",
+  "confirm_new_pin_code": "Confirm new PIN code",
   "confirm_password": "Confirm password",
   "contain": "Contain",
   "context": "Context",
@@ -722,9 +697,11 @@
   "create_tag_description": "Create a new tag. For nested tags, please enter the full path of the tag including forward slashes.",
   "create_user": "Create user",
   "created": "Created",
+  "created_at": "Created",
   "crop": "Crop",
   "curated_object_page_title": "Things",
   "current_device": "Current device",
+  "current_pin_code": "Current PIN code",
   "current_server_address": "Current server address",
   "custom_locale": "Custom Locale",
   "custom_locale_description": "Format dates and numbers based on the language and the region",
@@ -837,6 +814,7 @@
   "editor_crop_tool_h2_aspect_ratios": "Aspect ratios",
   "editor_crop_tool_h2_rotation": "Rotation",
   "email": "Email",
+  "email_notifications": "Email notifications",
   "empty_folder": "This folder is empty",
   "empty_trash": "Empty trash",
   "empty_trash_confirmation": "Are you sure you want to empty the trash? This will remove all the assets in trash permanently from Immich.\nYou cannot undo this action!",
@@ -845,6 +823,8 @@
   "end_date": "End date",
   "enqueued": "Enqueued",
   "enter_wifi_name": "Enter Wi-Fi name",
+  "enter_your_pin_code": "Enter your PIN code",
+  "enter_your_pin_code_subtitle": "Enter your PIN code to access the locked folder",
   "error": "Error",
   "error_change_sort_album": "Failed to change album sort order",
   "error_delete_face": "Error deleting face from asset",
@@ -852,7 +832,6 @@
   "error_saving_image": "Error: {error}",
   "error_title": "Error - Something went wrong",
   "errors": {
-    "unable_to_move_to_locked_folder": "Unable to move to locked folder",
     "cannot_navigate_next_asset": "Cannot navigate to the next asset",
     "cannot_navigate_previous_asset": "Cannot navigate to previous asset",
     "cant_apply_changes": "Can't apply changes",
@@ -940,6 +919,7 @@
     "unable_to_log_out_all_devices": "Unable to log out all devices",
     "unable_to_log_out_device": "Unable to log out device",
     "unable_to_login_with_oauth": "Unable to login with OAuth",
+    "unable_to_move_to_locked_folder": "Unable to move to locked folder",
     "unable_to_play_video": "Unable to play video",
     "unable_to_reassign_assets_existing_person": "Unable to reassign assets to {name, select, null {an existing person} other {{name}}}",
     "unable_to_reassign_assets_new_person": "Unable to reassign assets to a new person",
@@ -1080,6 +1060,7 @@
   "home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
   "host": "Host",
   "hour": "Hour",
+  "id": "ID",
   "ignore_icloud_photos": "Ignore iCloud photos",
   "ignore_icloud_photos_description": "Photos that are stored on iCloud will not be uploaded to the Immich server",
   "image": "Image",
@@ -1161,6 +1142,7 @@
   "location_picker_latitude_hint": "Enter your latitude here",
   "location_picker_longitude_error": "Enter a valid longitude",
   "location_picker_longitude_hint": "Enter your longitude here",
+  "locked_folder": "Locked Folder",
   "log_out": "Log out",
   "log_out_all_devices": "Log Out All Devices",
   "logged_out_all_devices": "Logged out all devices",
@@ -1229,8 +1211,8 @@
   "map_settings_only_show_favorites": "Show Favorite Only",
   "map_settings_theme_settings": "Map Theme",
   "map_zoom_to_see_photos": "Zoom out to see photos",
-  "mark_as_read": "Mark as read",
   "mark_all_as_read": "Mark all as read",
+  "mark_as_read": "Mark as read",
   "marked_all_as_read": "Marked all as read",
   "matches": "Matches",
   "media_type": "Media type",
@@ -1258,6 +1240,10 @@
   "month": "Month",
   "monthly_title_text_date_format": "MMMM y",
   "more": "More",
+  "move": "Move",
+  "move_off_locked_folder": "Move out of Locked Folder",
+  "move_to_locked_folder": "Move to Locked Folder",
+  "move_to_locked_folder_confirmation": "These photos and video will be removed from all albums, and only viewable from the Locked Folder",
   "moved_to_archive": "Moved {count, plural, one {# asset} other {# assets}} to archive",
   "moved_to_library": "Moved {count, plural, one {# asset} other {# assets}} to library",
   "moved_to_trash": "Moved to trash",
@@ -1274,6 +1260,8 @@
   "new_api_key": "New API Key",
   "new_password": "New password",
   "new_person": "New person",
+  "new_pin_code": "New PIN code",
+  "new_pin_code_subtitle": "This is your first time accessing the locked folder. Create a PIN code to securely access this page",
   "new_user_created": "New user created",
   "new_version_available": "NEW VERSION AVAILABLE",
   "newest_first": "Newest first",
@@ -1291,23 +1279,24 @@
   "no_explore_results_message": "Upload more photos to explore your collection.",
   "no_favorites_message": "Add favorites to quickly find your best pictures and videos",
   "no_libraries_message": "Create an external library to view your photos and videos",
+  "no_locked_photos_message": "Photos and videos in Locked Folder are hidden and won't show up as you browser your library.",
   "no_name": "No Name",
+  "no_notifications": "No notifications",
   "no_people_found": "No matching people found",
   "no_places": "No places",
   "no_results": "No results",
   "no_results_description": "Try a synonym or more general keyword",
-  "no_notifications": "No notifications",
   "no_shared_albums_message": "Create an album to share photos and videos with people in your network",
   "not_in_any_album": "Not in any album",
   "not_selected": "Not selected",
   "note_apply_storage_label_to_previously_uploaded assets": "Note: To apply the Storage Label to previously uploaded assets, run the",
   "notes": "Notes",
+  "nothing_here_yet": "Nothing here yet",
   "notification_permission_dialog_content": "To enable notifications, go to Settings and select allow.",
   "notification_permission_list_tile_content": "Grant permission to enable notifications.",
   "notification_permission_list_tile_enable_button": "Enable Notifications",
   "notification_permission_list_tile_title": "Notification Permission",
   "notification_toggle_setting_description": "Enable email notifications",
-  "email_notifications": "Email notifications",
   "notifications": "Notifications",
   "notifications_setting_description": "Manage notifications",
   "oauth": "OAuth",
@@ -1395,6 +1384,10 @@
   "photos_count": "{count, plural, one {{count, number} Photo} other {{count, number} Photos}}",
   "photos_from_previous_years": "Photos from previous years",
   "pick_a_location": "Pick a location",
+  "pin_code_changed_successfully": "Successfully changed PIN code",
+  "pin_code_reset_successfully": "Successfully reset PIN code",
+  "pin_code_setup_successfully": "Successfully setup a PIN code",
+  "pin_verification": "PIN code verification",
   "place": "Place",
   "places": "Places",
   "places_count": "{count, plural, one {{count, number} Place} other {{count, number} Places}}",
@@ -1492,6 +1485,8 @@
   "remove_deleted_assets": "Remove Deleted Assets",
   "remove_from_album": "Remove from album",
   "remove_from_favorites": "Remove from favorites",
+  "remove_from_locked_folder": "Remove from Locked Folder",
+  "remove_from_locked_folder_confirmation": "Are you sure you want to move these photos and videos out of Locked Folder? They will be visible in your library",
   "remove_from_shared_link": "Remove from shared link",
   "remove_memory": "Remove memory",
   "remove_photo_from_memory": "Remove photo from this memory",
@@ -1515,6 +1510,7 @@
   "reset": "Reset",
   "reset_password": "Reset password",
   "reset_people_visibility": "Reset people visibility",
+  "reset_pin_code": "Reset PIN code",
   "reset_to_default": "Reset to default",
   "resolve_duplicates": "Resolve duplicates",
   "resolved_all_duplicates": "Resolved all duplicates",
@@ -1655,6 +1651,7 @@
   "settings": "Settings",
   "settings_require_restart": "Please restart Immich to apply this setting",
   "settings_saved": "Settings saved",
+  "setup_pin_code": "Setup a PIN code",
   "share": "Share",
   "share_add_photos": "Add photos",
   "share_assets_selected": "{count} selected",
@@ -1771,8 +1768,8 @@
   "stop_sharing_photos_with_user": "Stop sharing your photos with this user",
   "storage": "Storage space",
   "storage_label": "Storage label",
-  "storage_usage": "{used} of {available} used",
   "storage_quota": "Storage Quota",
+  "storage_usage": "{used} of {available} used",
   "submit": "Submit",
   "suggestions": "Suggestions",
   "sunrise_on_the_beach": "Sunrise on the beach",
@@ -1840,6 +1837,8 @@
   "trash_page_title": "Trash ({count})",
   "trashed_items_will_be_permanently_deleted_after": "Trashed items will be permanently deleted after {days, plural, one {# day} other {# days}}.",
   "type": "Type",
+  "unable_to_change_pin_code": "Unable to change PIN code",
+  "unable_to_setup_pin_code": "Unable to setup PIN code",
   "unarchive": "Unarchive",
   "unarchived_count": "{count, plural, other {Unarchived #}}",
   "unfavorite": "Unfavorite",
@@ -1863,6 +1862,7 @@
   "untracked_files": "Untracked files",
   "untracked_files_decription": "These files are not tracked by the application. They can be the results of failed moves, interrupted uploads, or left behind due to a bug",
   "up_next": "Up next",
+  "updated_at": "Updated",
   "updated_password": "Updated password",
   "upload": "Upload",
   "upload_concurrency": "Upload concurrency",
@@ -1877,7 +1877,6 @@
   "upload_success": "Upload success, refresh the page to see new upload assets.",
   "upload_to_immich": "Upload to Immich ({count})",
   "uploading": "Uploading",
-  "id": "ID",
   "url": "URL",
   "usage": "Usage",
   "use_current_connection": "use current connection",
@@ -1886,8 +1885,8 @@
   "user_has_been_deleted": "This user has been deleted.",
   "user_id": "User ID",
   "user_liked": "{user} liked {type, select, photo {this photo} video {this video} asset {this asset} other {it}}",
-  "created_at": "Created",
-  "updated_at": "Updated",
+  "user_pin_code_settings": "PIN Code",
+  "user_pin_code_settings_description": "Manage your PIN code",
   "user_purchase_settings": "Purchase",
   "user_purchase_settings_description": "Manage your purchase",
   "user_role_set": "Set {user} as {role}",
@@ -1937,6 +1936,7 @@
   "welcome": "Welcome",
   "welcome_to_immich": "Welcome to Immich",
   "wifi_name": "Wi-Fi Name",
+  "wrong_pin_code": "Wrong PIN code",
   "year": "Year",
   "years_ago": "{years, plural, one {# year} other {# years}} ago",
   "yes": "Yes",
diff --git a/web/package.json b/web/package.json
index 7bf5e36189..94f48a7d97 100644
--- a/web/package.json
+++ b/web/package.json
@@ -18,7 +18,8 @@
     "lint:p": "eslint-p . --max-warnings 0 --concurrency=4",
     "lint:fix": "npm run lint -- --fix",
     "format": "prettier --check .",
-    "format:fix": "prettier --write .",
+    "format:fix": "prettier --write . && npm run format:i18n",
+    "format:i18n": "npx --yes sort-json ../i18n/*.json",
     "test": "vitest --run",
     "test:cov": "vitest --coverage",
     "test:watch": "vitest dev",
diff --git a/web/src/routes/(user)/locked/[[photos=photos]]/[[assetId=id]]/+page.ts b/web/src/routes/(user)/locked/[[photos=photos]]/[[assetId=id]]/+page.ts
index 9b9d86a4b3..445917f0d0 100644
--- a/web/src/routes/(user)/locked/[[photos=photos]]/[[assetId=id]]/+page.ts
+++ b/web/src/routes/(user)/locked/[[photos=photos]]/[[assetId=id]]/+page.ts
@@ -7,7 +7,7 @@ import { redirect } from '@sveltejs/kit';
 import type { PageLoad } from './$types';
 
 export const load = (async ({ params, url }) => {
-  await authenticate();
+  await authenticate(url);
   const { isElevated, pinCode } = await getAuthStatus();
 
   if (!isElevated || !pinCode) {
diff --git a/web/src/routes/auth/pin-prompt/+page.ts b/web/src/routes/auth/pin-prompt/+page.ts
index e2b79605d8..b0d248ebe6 100644
--- a/web/src/routes/auth/pin-prompt/+page.ts
+++ b/web/src/routes/auth/pin-prompt/+page.ts
@@ -4,7 +4,7 @@ import { getAuthStatus } from '@immich/sdk';
 import type { PageLoad } from './$types';
 
 export const load = (async ({ url }) => {
-  await authenticate();
+  await authenticate(url);
 
   const { pinCode } = await getAuthStatus();