Skip to content

Commit

Permalink
Merge pull request #2070 from hydephp/internationalization
Browse files Browse the repository at this point in the history
[1.x] Automatically transliterate logographic inputs for slug generation hydephp/develop@464bb8b
  • Loading branch information
caendesilva authored and github-actions[bot] committed Dec 22, 2024
1 parent 6d3207b commit a9c7e9f
Show file tree
Hide file tree
Showing 8 changed files with 275 additions and 9 deletions.
13 changes: 13 additions & 0 deletions src/Foundation/Concerns/ImplementsStringHelpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,19 @@ public static function makeTitle(string $value): string
));
}

public static function makeSlug(string $value): string
{
// Expand camelCase and PascalCase to separate words
$value = preg_replace('/([a-z])([A-Z])/', '$1 $2', $value);

// Transliterate international characters to ASCII
$value = Str::transliterate($value);

// Todo: In v2.0 we will use the following dictionary: ['@' => 'at', '&' => 'and']

return Str::slug($value);
}

public static function normalizeNewlines(string $string): string
{
return str_replace("\r\n", "\n", $string);
Expand Down
4 changes: 2 additions & 2 deletions src/Framework/Actions/CreatesNewMarkdownPostFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

use Hyde\Framework\Exceptions\FileConflictException;
use Hyde\Facades\Filesystem;
use Hyde\Hyde;
use Hyde\Pages\MarkdownPost;
use Illuminate\Support\Carbon;
use Illuminate\Support\Str;

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

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

/**
Expand Down
2 changes: 1 addition & 1 deletion src/Framework/Actions/CreatesNewPageSourceFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ protected function fileName(string $title): string
}

// And return a slug made from just the title without the subdirectory
return Str::slug(basename($title));
return Hyde::makeSlug(basename($title));
}

protected function normalizeSubdirectory(string $title): string
Expand Down
8 changes: 4 additions & 4 deletions src/Framework/Features/Navigation/DocumentationSidebar.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
use Hyde\Support\Facades\Render;
use Hyde\Support\Models\Route;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;

use function collect;

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

public function isGroupActive(string $group): bool
{
return Str::slug(Render::getPage()->navigationMenuGroup()) === $group
|| $this->isPageIndexPage() && $this->shouldIndexPageBeActive($group);
$normalized = Hyde::makeSlug(Render::getPage()->navigationMenuGroup() ?? 'other');

return ($normalized === $group) || ($this->isPageIndexPage() && $this->shouldIndexPageBeActive($group));
}

public function makeGroupTitle(string $group): string
Expand Down
3 changes: 1 addition & 2 deletions src/Framework/Features/Navigation/NavItem.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use Hyde\Foundation\Facades\Routes;
use Hyde\Hyde;
use Hyde\Support\Models\Route;
use Illuminate\Support\Str;
use Stringable;

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

protected static function normalizeGroupKey(?string $group): ?string
{
return $group ? Str::slug($group) : null;
return $group ? Hyde::makeSlug($group) : null;
}
}
6 changes: 6 additions & 0 deletions tests/Feature/HydeKernelTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
* @covers \Hyde\Hyde
*
* @see \Hyde\Framework\Testing\Unit\HydeHelperFacadeMakeTitleTest
* @see \Hyde\Framework\Testing\Unit\HydeHelperFacadeMakeSlugTest
* @see \Hyde\Framework\Testing\Feature\HydeExtensionFeatureTest
*/
class HydeKernelTest extends TestCase
Expand Down Expand Up @@ -108,6 +109,11 @@ public function testMakeTitleHelperReturnsTitleFromPageSlug()
$this->assertSame('Foo Bar', Hyde::makeTitle('foo-bar'));
}

public function testMakeSlugHelperReturnsSlugFromTitle()
{
$this->assertSame('foo-bar', Hyde::makeSlug('Foo Bar'));
}

public function testNormalizeNewlinesReplacesCarriageReturnsWithUnixEndings()
{
$this->assertSame("foo\nbar\nbaz", Hyde::normalizeNewlines("foo\nbar\r\nbaz"));
Expand Down
117 changes: 117 additions & 0 deletions tests/Feature/InternationalizationTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?php

declare(strict_types=1);

namespace Hyde\Framework\Testing\Feature;

use Hyde\Facades\Filesystem;
use Hyde\Framework\Actions\CreatesNewMarkdownPostFile;
use Hyde\Framework\Actions\StaticPageBuilder;
use Hyde\Hyde;
use Hyde\Pages\MarkdownPost;
use Hyde\Testing\TestCase;

/**
* @coversNothing High level test to ensure the internationalization features are working.
*/
class InternationalizationTest extends TestCase
{
/**
* @dataProvider internationalCharacterSetsProvider
*/
public function testCanCreateBlogPostFilesWithInternationalCharacterSets(
string $title,
string $description,
string $expectedSlug,
string $expectedTitle
) {
$creator = new CreatesNewMarkdownPostFile($title, $description, 'blog', 'default', '2024-12-22 10:45');
$path = $creator->save();

$this->assertSame("_posts/$expectedSlug.md", $path);
$this->assertSame($expectedSlug, $creator->getIdentifier());
$this->assertSame($creator->getIdentifier(), Hyde::makeSlug($title));
$this->assertFileExists($path);

$contents = file_get_contents($path);

if (str_contains($title, ' ')) {
$expectedTitle = "'$expectedTitle'";
}

if (str_contains($description, ' ')) {
$description = "'$description'";
}

$this->assertStringContainsString("title: $expectedTitle", $contents);
$this->assertSame(<<<EOF
---
title: {$expectedTitle}
description: {$description}
category: blog
author: default
date: '2024-12-22 10:45'
---
## Write something awesome.
EOF, $contents);

Filesystem::unlink($path);
}

/**
* @dataProvider internationalCharacterSetsProvider
*/
public function testCanCompileBlogPostFilesWithInternationalCharacterSets(
string $title,
string $description,
string $expectedSlug,
string $expectedTitle
) {
$page = new MarkdownPost($expectedSlug, [
'title' => $title,
'description' => $description,
'category' => 'blog',
'author' => 'default',
'date' => '2024-12-22 10:45',
]);

$path = StaticPageBuilder::handle($page);

$this->assertSame(Hyde::path("_site/posts/$expectedSlug.html"), $path);
$this->assertFileExists($path);

$contents = file_get_contents($path);

$this->assertStringContainsString("<title>HydePHP - $expectedTitle</title>", $contents);
$this->assertStringContainsString("<h1 itemprop=\"headline\" class=\"mb-4\">$expectedTitle</h1>", $contents);
$this->assertStringContainsString("<meta name=\"description\" content=\"$description\">", $contents);

Filesystem::unlink($path);
}

public static function internationalCharacterSetsProvider(): array
{
return [
'Chinese (Simplified)' => [
'你好世界',
'简短描述',
'ni-hao-shi-jie',
'你好世界',
],
'Japanese' => [
'こんにちは世界',
'短い説明',
'konnichihashi-jie',
'こんにちは世界',
],
'Korean' => [
'안녕하세요 세계',
'짧은 설명',
'annyeonghaseyo-segye',
'안녕하세요 세계',
],
];
}
}
131 changes: 131 additions & 0 deletions tests/Unit/HydeHelperFacadeMakeSlugTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<?php

declare(strict_types=1);

namespace Hyde\Framework\Testing\Unit;

use Hyde\Hyde;
use Hyde\Testing\UnitTestCase;

class HydeHelperFacadeMakeSlugTest extends UnitTestCase
{
protected static bool $needsKernel = true;

public function testMakeSlugHelperConvertsTitleCaseToSlug()
{
$this->assertSame('hello-world', Hyde::makeSlug('Hello World'));
}

public function testMakeSlugHelperConvertsKebabCaseToSlug()
{
$this->assertSame('hello-world', Hyde::makeSlug('hello-world'));
}

public function testMakeSlugHelperConvertsSnakeCaseToSlug()
{
$this->assertSame('hello-world', Hyde::makeSlug('hello_world'));
}

public function testMakeSlugHelperConvertsCamelCaseToSlug()
{
$this->assertSame('hello-world', Hyde::makeSlug('helloWorld'));
}

public function testMakeSlugHelperConvertsPascalCaseToSlug()
{
$this->assertSame('hello-world', Hyde::makeSlug('HelloWorld'));
}

public function testMakeSlugHelperHandlesMultipleSpaces()
{
$this->assertSame('hello-world', Hyde::makeSlug('Hello World'));
}

public function testMakeSlugHelperHandlesSpecialCharacters()
{
$this->assertSame('hello-world', Hyde::makeSlug('Hello & World!'));
}

public function testMakeSlugHelperConvertsUppercaseToLowercase()
{
$this->assertSame('hello-world', Hyde::makeSlug('HELLO WORLD'));
$this->assertSame('hello-world', Hyde::makeSlug('HELLO_WORLD'));
}

public function testMakeSlugHelperHandlesNumbers()
{
$this->assertSame('hello-world-123', Hyde::makeSlug('Hello World 123'));
}

public function testMakeSlugHelperTransliteratesChineseCharacters()
{
$this->assertSame('ni-hao-shi-jie', Hyde::makeSlug('你好世界'));
}

public function testMakeSlugHelperTransliteratesJapaneseCharacters()
{
$this->assertSame('konnichihashi-jie', Hyde::makeSlug('こんにちは世界'));
}

public function testMakeSlugHelperTransliteratesKoreanCharacters()
{
$this->assertSame('annyeongsegye', Hyde::makeSlug('안녕세계'));
}

public function testMakeSlugHelperTransliteratesArabicCharacters()
{
$this->assertSame('mrhb-bllm', Hyde::makeSlug('مرحبا بالعالم'));
}

public function testMakeSlugHelperTransliteratesRussianCharacters()
{
$this->assertSame('privet-mir', Hyde::makeSlug('Привет мир'));
}

public function testMakeSlugHelperTransliteratesAccentedLatinCharacters()
{
$this->assertSame('hello-world', Hyde::makeSlug('hèllô wórld'));
$this->assertSame('uber-strasse', Hyde::makeSlug('über straße'));
}

public function testMakeSlugHelperHandlesMixedScripts()
{
$this->assertSame('hello-ni-hao-world', Hyde::makeSlug('Hello 你好 World'));
$this->assertSame('privet-world', Hyde::makeSlug('Привет World'));
}

public function testMakeSlugHelperHandlesEmojis()
{
$this->assertSame('hello-world', Hyde::makeSlug('Hello 👋 World'));
$this->assertSame('world', Hyde::makeSlug('😊 World'));
}

public function testMakeSlugHelperHandlesComplexMixedInput()
{
$this->assertSame(
'hello-ni-hao-privet-bonjour-world-123',
Hyde::makeSlug('Hello 你好 Привет Bonjóur World 123!')
);
}

public function testMakeSlugHelperHandlesEdgeCases()
{
$this->assertSame('', Hyde::makeSlug(''));
$this->assertSame('at', Hyde::makeSlug('!@#$%^&*()'));
$this->assertSame('', Hyde::makeSlug('... ...'));
$this->assertSame('multiple-dashes', Hyde::makeSlug('multiple---dashes'));
}

public function testMakeSlugHelperPreservesValidCharacters()
{
$this->assertSame('abc-123', Hyde::makeSlug('abc-123'));
$this->assertSame('test-slug', Hyde::makeSlug('test-slug'));
}

public function testMakeSlugHelperHandlesWhitespace()
{
$this->assertSame('trim-spaces', Hyde::makeSlug(' trim spaces '));
$this->assertSame('newline-test', Hyde::makeSlug("newline\ntest"));
$this->assertSame('tab-test', Hyde::makeSlug("tab\ttest"));
}
}

0 comments on commit a9c7e9f

Please sign in to comment.