Skip to content

Commit 464bb8b

Browse files
authored
Merge pull request #2070 from hydephp/internationalization
[1.x] Automatically transliterate logographic inputs for slug generation
2 parents ce23e41 + 829f0e4 commit 464bb8b

File tree

11 files changed

+296
-11
lines changed

11 files changed

+296
-11
lines changed

RELEASE_NOTES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ This serves two purposes:
1616
- All page types now support the `description` front matter field (used in page metadata) in https://github.com/hydephp/develop/pull/1884
1717
- Added a new `Filesystem::findFiles()` method to find files in a directory in https://github.com/hydephp/develop/pull/2064
1818
- Added `webp` to the list of default media extensions in https://github.com/hydephp/framework/pull/663
19+
- Added a new slug generation helper to improve internationalization support in https://github.com/hydephp/develop/pull/2070
1920

2021
### Changed
2122
- Changed the `Hyde` facade to use a `@mixin` annotation instead of single method annotations in https://github.com/hydephp/develop/pull/1919
@@ -26,6 +27,7 @@ This serves two purposes:
2627
- The `torchlight:install` command is now hidden from the command list as it's already installed in https://github.com/hydephp/develop/pull/1879
2728
- Updated the home page fallback link in the 404 template to lead to the site root in https://github.com/hydephp/develop/pull/1880 (fixes https://github.com/hydephp/develop/issues/1781)
2829
- Normalized remote URL checks so that protocol relative URLs `//` are consistently considered to be remote in all places in https://github.com/hydephp/develop/pull/1882 (fixes https://github.com/hydephp/develop/issues/1788)
30+
- Page slugs are now generated using our automatically internationalizing slug generator to transliterate input to ASCII in https://github.com/hydephp/develop/pull/2070
2931
- Replaced internal usages of glob functions with our improved file finder in https://github.com/hydephp/develop/pull/2064
3032
- Updated to HydeFront v3.4 in https://github.com/hydephp/develop/pull/1803
3133
- Realtime Compiler: Virtual routes are now managed through the service container in https://github.com/hydephp/develop/pull/1858
@@ -46,6 +48,7 @@ This serves two purposes:
4648
- Fixed heading permalinks button text showing in Google Search previews https://github.com/hydephp/develop/issues/1801 in https://github.com/hydephp/develop/pull/1803
4749
- Fixed routing issues with nested 404 pages where an index page does not exist https://github.com/hydephp/develop/issues/1781 in https://github.com/hydephp/develop/pull/1880
4850
- Fixed URL metadata for blog posts not using customized post output directories in https://github.com/hydephp/develop/pull/1889
51+
- Fixed lacking support for logographic slug generation https://github.com/hydephp/hyde/issues/269 in https://github.com/hydephp/develop/pull/2070
4952
- Improved printed documentation views in https://github.com/hydephp/develop/pull/2005
5053
- Fixed "BuildService finding non-existent files to copy in Debian" https://github.com/hydephp/framework/issues/662 in https://github.com/hydephp/develop/pull/2064
5154
- Fixed "Undefined constant `Hyde\Foundation\Kernel\GLOB_BRACE`" https://github.com/hydephp/hyde/issues/270 in https://github.com/hydephp/develop/pull/2064

docs/_data/partials/hyde-pages-api/hyde-kernel-string-methods.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<section id="hyde-kernel-string-methods">
22

33
<!-- Start generated docs for Hyde\Foundation\Concerns\ImplementsStringHelpers -->
4-
<!-- Generated by HydePHP DocGen script at 2023-03-11 11:17:34 in 0.07ms -->
4+
<!-- Generated by HydePHP DocGen script at 2024-12-22 09:14:25 in 0.07ms -->
55

66
#### `makeTitle()`
77

@@ -11,6 +11,14 @@ No description provided.
1111
Hyde::makeTitle(string $value): string
1212
```
1313

14+
#### `makeSlug()`
15+
16+
No description provided.
17+
18+
```php
19+
Hyde::makeSlug(string $value): string
20+
```
21+
1422
#### `normalizeNewlines()`
1523

1624
No description provided.

docs/architecture-concepts/the-hydekernel.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ Hyde::routes(): Hyde\Foundation\Kernel\RouteCollection
140140
<section id="hyde-kernel-string-methods">
141141

142142
<!-- Start generated docs for Hyde\Foundation\Concerns\ImplementsStringHelpers -->
143-
<!-- Generated by HydePHP DocGen script at 2023-03-11 11:17:34 in 0.07ms -->
143+
<!-- Generated by HydePHP DocGen script at 2024-12-22 09:14:25 in 0.07ms -->
144144

145145
#### `makeTitle()`
146146

@@ -150,6 +150,14 @@ No description provided.
150150
Hyde::makeTitle(string $value): string
151151
```
152152

153+
#### `makeSlug()`
154+
155+
No description provided.
156+
157+
```php
158+
Hyde::makeSlug(string $value): string
159+
```
160+
153161
#### `normalizeNewlines()`
154162

155163
No description provided.

packages/framework/src/Foundation/Concerns/ImplementsStringHelpers.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,19 @@ public static function makeTitle(string $value): string
3838
));
3939
}
4040

41+
public static function makeSlug(string $value): string
42+
{
43+
// Expand camelCase and PascalCase to separate words
44+
$value = preg_replace('/([a-z])([A-Z])/', '$1 $2', $value);
45+
46+
// Transliterate international characters to ASCII
47+
$value = Str::transliterate($value);
48+
49+
// Todo: In v2.0 we will use the following dictionary: ['@' => 'at', '&' => 'and']
50+
51+
return Str::slug($value);
52+
}
53+
4154
public static function normalizeNewlines(string $string): string
4255
{
4356
return str_replace("\r\n", "\n", $string);

packages/framework/src/Framework/Actions/CreatesNewMarkdownPostFile.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66

77
use Hyde\Framework\Exceptions\FileConflictException;
88
use Hyde\Facades\Filesystem;
9+
use Hyde\Hyde;
910
use Hyde\Pages\MarkdownPost;
1011
use Illuminate\Support\Carbon;
11-
use Illuminate\Support\Str;
1212

1313
/**
1414
* Offloads logic for the make:post command.
@@ -48,7 +48,7 @@ public function __construct(string $title, ?string $description, ?string $catego
4848
$this->customContent = $customContent;
4949

5050
$this->date = Carbon::make($date ?? Carbon::now())->format('Y-m-d H:i');
51-
$this->identifier = Str::slug($title);
51+
$this->identifier = Hyde::makeSlug($title);
5252
}
5353

5454
/**

packages/framework/src/Framework/Actions/CreatesNewPageSourceFile.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ protected function fileName(string $title): string
8181
}
8282

8383
// And return a slug made from just the title without the subdirectory
84-
return Str::slug(basename($title));
84+
return Hyde::makeSlug(basename($title));
8585
}
8686

8787
protected function normalizeSubdirectory(string $title): string

packages/framework/src/Framework/Features/Navigation/DocumentationSidebar.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
use Hyde\Support\Facades\Render;
1212
use Hyde\Support\Models\Route;
1313
use Illuminate\Support\Collection;
14-
use Illuminate\Support\Str;
1514

1615
use function collect;
1716

@@ -48,14 +47,15 @@ public function getGroups(): array
4847
public function getItemsInGroup(?string $group): Collection
4948
{
5049
return $this->items->filter(function (NavItem $item) use ($group): bool {
51-
return ($item->getGroup() === $group) || ($item->getGroup() === Str::slug($group));
50+
return ($item->getGroup() === $group) || ($item->getGroup() === Hyde::makeSlug($group));
5251
})->sortBy('navigation.priority')->values();
5352
}
5453

5554
public function isGroupActive(string $group): bool
5655
{
57-
return Str::slug(Render::getPage()->navigationMenuGroup()) === $group
58-
|| $this->isPageIndexPage() && $this->shouldIndexPageBeActive($group);
56+
$normalized = Hyde::makeSlug(Render::getPage()->navigationMenuGroup() ?? 'other');
57+
58+
return ($normalized === $group) || ($this->isPageIndexPage() && $this->shouldIndexPageBeActive($group));
5959
}
6060

6161
public function makeGroupTitle(string $group): string

packages/framework/src/Framework/Features/Navigation/NavItem.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
use Hyde\Foundation\Facades\Routes;
88
use Hyde\Hyde;
99
use Hyde\Support\Models\Route;
10-
use Illuminate\Support\Str;
1110
use Stringable;
1211

1312
/**
@@ -133,6 +132,6 @@ protected static function getRouteGroup(Route $route): ?string
133132

134133
protected static function normalizeGroupKey(?string $group): ?string
135134
{
136-
return $group ? Str::slug($group) : null;
135+
return $group ? Hyde::makeSlug($group) : null;
137136
}
138137
}

packages/framework/tests/Feature/HydeKernelTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
* @covers \Hyde\Hyde
3333
*
3434
* @see \Hyde\Framework\Testing\Unit\HydeHelperFacadeMakeTitleTest
35+
* @see \Hyde\Framework\Testing\Unit\HydeHelperFacadeMakeSlugTest
3536
* @see \Hyde\Framework\Testing\Feature\HydeExtensionFeatureTest
3637
*/
3738
class HydeKernelTest extends TestCase
@@ -108,6 +109,11 @@ public function testMakeTitleHelperReturnsTitleFromPageSlug()
108109
$this->assertSame('Foo Bar', Hyde::makeTitle('foo-bar'));
109110
}
110111

112+
public function testMakeSlugHelperReturnsSlugFromTitle()
113+
{
114+
$this->assertSame('foo-bar', Hyde::makeSlug('Foo Bar'));
115+
}
116+
111117
public function testNormalizeNewlinesReplacesCarriageReturnsWithUnixEndings()
112118
{
113119
$this->assertSame("foo\nbar\nbaz", Hyde::normalizeNewlines("foo\nbar\r\nbaz"));
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Hyde\Framework\Testing\Feature;
6+
7+
use Hyde\Facades\Filesystem;
8+
use Hyde\Framework\Actions\CreatesNewMarkdownPostFile;
9+
use Hyde\Framework\Actions\StaticPageBuilder;
10+
use Hyde\Hyde;
11+
use Hyde\Pages\MarkdownPost;
12+
use Hyde\Testing\TestCase;
13+
14+
/**
15+
* @coversNothing High level test to ensure the internationalization features are working.
16+
*/
17+
class InternationalizationTest extends TestCase
18+
{
19+
/**
20+
* @dataProvider internationalCharacterSetsProvider
21+
*/
22+
public function testCanCreateBlogPostFilesWithInternationalCharacterSets(
23+
string $title,
24+
string $description,
25+
string $expectedSlug,
26+
string $expectedTitle
27+
) {
28+
$creator = new CreatesNewMarkdownPostFile($title, $description, 'blog', 'default', '2024-12-22 10:45');
29+
$path = $creator->save();
30+
31+
$this->assertSame("_posts/$expectedSlug.md", $path);
32+
$this->assertSame($expectedSlug, $creator->getIdentifier());
33+
$this->assertSame($creator->getIdentifier(), Hyde::makeSlug($title));
34+
$this->assertFileExists($path);
35+
36+
$contents = file_get_contents($path);
37+
38+
if (str_contains($title, ' ')) {
39+
$expectedTitle = "'$expectedTitle'";
40+
}
41+
42+
if (str_contains($description, ' ')) {
43+
$description = "'$description'";
44+
}
45+
46+
$this->assertStringContainsString("title: $expectedTitle", $contents);
47+
$this->assertSame(<<<EOF
48+
---
49+
title: {$expectedTitle}
50+
description: {$description}
51+
category: blog
52+
author: default
53+
date: '2024-12-22 10:45'
54+
---
55+
56+
## Write something awesome.
57+
58+
EOF, $contents);
59+
60+
Filesystem::unlink($path);
61+
}
62+
63+
/**
64+
* @dataProvider internationalCharacterSetsProvider
65+
*/
66+
public function testCanCompileBlogPostFilesWithInternationalCharacterSets(
67+
string $title,
68+
string $description,
69+
string $expectedSlug,
70+
string $expectedTitle
71+
) {
72+
$page = new MarkdownPost($expectedSlug, [
73+
'title' => $title,
74+
'description' => $description,
75+
'category' => 'blog',
76+
'author' => 'default',
77+
'date' => '2024-12-22 10:45',
78+
]);
79+
80+
$path = StaticPageBuilder::handle($page);
81+
82+
$this->assertSame(Hyde::path("_site/posts/$expectedSlug.html"), $path);
83+
$this->assertFileExists($path);
84+
85+
$contents = file_get_contents($path);
86+
87+
$this->assertStringContainsString("<title>HydePHP - $expectedTitle</title>", $contents);
88+
$this->assertStringContainsString("<h1 itemprop=\"headline\" class=\"mb-4\">$expectedTitle</h1>", $contents);
89+
$this->assertStringContainsString("<meta name=\"description\" content=\"$description\">", $contents);
90+
91+
Filesystem::unlink($path);
92+
}
93+
94+
public static function internationalCharacterSetsProvider(): array
95+
{
96+
return [
97+
'Chinese (Simplified)' => [
98+
'你好世界',
99+
'简短描述',
100+
'ni-hao-shi-jie',
101+
'你好世界',
102+
],
103+
'Japanese' => [
104+
'こんにちは世界',
105+
'短い説明',
106+
'konnichihashi-jie',
107+
'こんにちは世界',
108+
],
109+
'Korean' => [
110+
'안녕하세요 세계',
111+
'짧은 설명',
112+
'annyeonghaseyo-segye',
113+
'안녕하세요 세계',
114+
],
115+
];
116+
}
117+
}

0 commit comments

Comments
 (0)