-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathLocalizedPaths.php
More file actions
136 lines (115 loc) · 4.49 KB
/
Copy pathLocalizedPaths.php
File metadata and controls
136 lines (115 loc) · 4.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
// src/Libs/Italix/Mvc/LocalizedPaths.php
declare(strict_types=1);
namespace Italix\Mvc;
/**
* Canonical URL paths in, localised ones out — and back again.
*
* /about-us -> /chi-siamo (it)
* /chi-siamo -> /about-us (the middleware, before dispatch)
*
* ## Why this is not the translator's job
*
* It used to live on `Italix\Mvc\Translator`, which therefore did two unrelated
* things: looking up messages, and mapping URL segments. They share a directory
* of language files and nothing else. A page asks the first constantly and the
* second almost never — measured on the application that grew this framework:
* 339 calls to `get()` against 3 to the whole slug half.
*
* Keeping them together meant that adopting a better message translator would
* have dragged routing along with it. They are separate now so that either can
* be replaced on its own.
*
* Reads `{lang_dir}/{locale}/slugs.php`:
*
* return [
* 'paths' => ['/about-us' => '/chi-siamo'], // whole paths, exact
* 'segments' => ['news' => 'notizie'], // one segment at a time
* ];
*/
final class LocalizedPaths
{
private string $canonical_lang;
private string $lang_dir;
/** @var array<string, array{paths: array, paths_rev: array, segments: array, segments_rev: array}> */
private array $loaded = [];
public function __construct(string $canonical_lang, string $lang_dir)
{
$this->canonical_lang = $canonical_lang;
$this->lang_dir = rtrim($lang_dir, '/') . '/';
}
public function canonical_lang(): string
{
return $this->canonical_lang;
}
/**
* Canonical path -> the path this locale writes.
*
* The canonical language is a no-op by definition: its paths are already
* canonical, and translating them would need a map to itself.
*/
public function slug(string $canonical_path, string $locale): string
{
if ($locale === $this->canonical_lang) {
return $canonical_path;
}
$data = $this->data($locale);
// Whole-path first: the same segment can translate differently depending
// on where it sits, and only an exact match knows that.
if (isset($data['paths'][$canonical_path])) {
return $data['paths'][$canonical_path];
}
return $this->map_segments($canonical_path, $data['segments']);
}
/**
* Localised path -> canonical path, for the middleware to rewrite an
* incoming URI before routing sees it.
*
* An unknown path passes through unchanged: the router will produce the 404,
* and it can say more about it than this can.
*/
public function resolve(string $incoming_path, string $locale): string
{
if ($locale === $this->canonical_lang) {
return $incoming_path;
}
$data = $this->data($locale);
if (isset($data['paths_rev'][$incoming_path])) {
return $data['paths_rev'][$incoming_path];
}
return $this->map_segments($incoming_path, $data['segments_rev']);
}
/** The full localised URL, language prefix included: `/it/chi-siamo`. */
public function url(string $canonical_path, string $locale): string
{
return '/' . $locale . $this->slug($canonical_path, $locale);
}
private function map_segments(string $path, array $map): string
{
$segments = explode('/', ltrim($path, '/'));
$translated = array_map(static fn ($seg) => $map[$seg] ?? $seg, $segments);
return '/' . implode('/', $translated);
}
/** @return array{paths: array, paths_rev: array, segments: array, segments_rev: array} */
private function data(string $locale): array
{
if (!isset($this->loaded[$locale])) {
$file = $this->lang_dir . $locale . '/slugs.php';
$slugs = is_file($file) ? require $file : [];
$paths = $slugs['paths'] ?? [];
$segments = $slugs['segments'] ?? [];
$this->loaded[$locale] = [
'paths' => $paths,
'paths_rev' => array_flip($paths),
'segments' => $segments,
'segments_rev' => array_flip($segments),
];
}
return $this->loaded[$locale];
}
}