Przyjazny URL z polskich znaków
- Teksty i String
- poziom: średni
- aktualizacja: 16 sierpnia 2026
Slug to krótka, bezpieczna część adresu, np. zolc-i-lodz zamiast tytułu „Żółć i Łódź!”. Jeśli wyczyścisz tytuł samym replaceAll("[^a-z]", "-"), z „łódź” zostanie --d- — polskie litery po prostu wypadną. Klasa Normalizer rozkłada większość znaków diakrytycznych na literę i osobny akcent, który wystarczy pominąć. Wyjątkiem jest ł: tej litery Normalizer nie rusza, więc kod obsługuje ją jawnie, a przy okazji scala wiele separatorów i tworzy unikalne warianty -2, -3.
import java.text.Normalizer;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
/**
* Tworzenie przyjaznych adresów URL z polskich tytułów — skryptyJAVA.pl
* Zapisz jako: PrzyjaznyUrl.java
*/
public final class PrzyjaznyUrl {
private static final Locale POLSKI = Locale.forLanguageTag("pl-PL");
private PrzyjaznyUrl() {
// Klasa zawiera wyłącznie metody statyczne.
}
/**
* Zamienia tekst na mały, ASCII-owy slug, np. „Żółć i Łódź!” na „zolc-i-lodz”.
* Dla null, pustego tekstu lub samych symboli zwraca pusty napis.
*/
public static String utworzSlug(String tekst) {
if (tekst == null || tekst.isBlank()) {
return "";
}
// Normalizer nie rozkłada litery ł, więc zamieniamy ją osobno.
String maleLitery = tekst.trim().toLowerCase(POLSKI).replace('ł', 'l');
String rozlozony = Normalizer.normalize(maleLitery, Normalizer.Form.NFD);
StringBuilder wynik = new StringBuilder(rozlozony.length());
for (int i = 0; i < rozlozony.length();) {
int znak = rozlozony.codePointAt(i);
i += Character.charCount(znak);
if ((znak >= 'a' && znak <= 'z') || (znak >= '0' && znak <= '9')) {
wynik.appendCodePoint(znak);
} else if (Character.getType(znak) == Character.NON_SPACING_MARK) {
// Akcent po rozłożeniu, np. kropka z „ż” — pomijamy go.
continue;
} else if (!wynik.isEmpty() && wynik.charAt(wynik.length() - 1) != '-') {
// Spacje, myślniki, emoji i interpunkcja stają się jednym separatorem.
wynik.append('-');
}
}
if (!wynik.isEmpty() && wynik.charAt(wynik.length() - 1) == '-') {
wynik.setLength(wynik.length() - 1);
}
return wynik.toString();
}
/**
* Tworzy slug i od razu rezerwuje go w zbiorze; przy kolizji dopisuje -2, -3 itd.
* Pusty wynik zastępuje bezpieczną nazwą „element”.
*/
public static String utworzUnikalnySlug(String tekst, Set<String> zajeteSlugi) {
String baza = utworzSlug(tekst);
if (baza.isEmpty()) {
baza = "element";
}
String kandydat = baza;
int numer = 2;
while (!zajeteSlugi.add(kandydat)) {
kandydat = baza + "-" + numer++;
}
return kandydat;
}
// ── PRZYKŁADY ───────────────────────────────────────────
public static void main(String[] args) {
List<String> tytuly = List.of(
"Żółć i Łódź!",
" Java 17 — praktyczny kurs ",
"Ceny: 50% taniej!!!",
"Zażółć gęślą jaźń",
"☕");
for (String tytul : tytuly) {
System.out.printf("%-30s -> %s%n", tytul, utworzSlug(tytul));
}
Set<String> zajete = new HashSet<>();
System.out.println(utworzUnikalnySlug("Kawa i Java", zajete));
System.out.println(utworzUnikalnySlug("Kawa i Java!", zajete));
System.out.println(utworzUnikalnySlug("Kawa i Java", zajete));
}
}
Jak użyć tego skryptu
- Skopiuj kod i zapisz go w pliku
PrzyjaznyUrl.java— dokładnie tak, jak nazywa się klasa publiczna — a następnie uruchom poleceniemjava PrzyjaznyUrl.java. Przykłady pokażą zamianę polskich tytułów oraz obsługę powtórzeń. - W swoim projekcie wywołaj
PrzyjaznyUrl.utworzSlug(tytul). Metoda zwraca wyłącznie małe litery ASCII, cyfry i pojedyncze myślniki, więc wynik można bezpiecznie umieścić w ścieżce URL. - Jeśli slug ma być unikalny w bieżącej kolekcji, przekaż mutowalny
Set<String>doutworzUnikalnySlug. Metoda sama doda wybrany wynik do zbioru. - W aplikacji z bazą danych zastąp zbiór sprawdzeniem kolumny
slugi załóż na niej indeksUNIQUE; w razie równoczesnej kolizji ponów próbę z kolejnym numerem. Pamiętaj też o długości — metoda niczego nie obcina, więc tytuł na 300 znaków da slug na 300 znaków. Przytnij go do szerokości kolumny (zwykle 100–200 znaków), najlepiej na ostatnim myślniku przed limitem, żeby nie urwać słowa w połowie.
Warto wiedzieć
Normalizerw trybieNFDrozkłada m.in.ą,ć,óiż, ale nie literęł. Jawnereplace('ł', 'l')nie jest ozdobnikiem — bez niego „Łódź” zmieniłaby się w „odz”.- Kod celowo pozostawia tylko alfabet łaciński ASCII i cyfry. Tytuł zapisany wyłącznie cyrylicą, alfabetem greckim albo emoji da pusty wynik; metoda unikalna zastąpi go wtedy nazwą
element. Gorzej z tekstem mieszanym: litery łacińskie, którychNFDnie rozkłada —ß,đ,æczy ligaturafi— zamieniają się w separator i po cichu rozrywają słowo, więc „Straße” dastra-e, a niestrasse. Jeśli spodziewasz się takich tytułów, dopisz własne reguły transliteracji przed wywołaniem metody. - Slug nie powinien być jedynym identyfikatorem rekordu. Tytuł prędzej czy później się zmieni, dlatego w bazie trzymaj stałe ID, a stary slug przekieruj kodem HTTP 301 na nowy adres — inaczej stracisz wszystkie linki, które ktoś zdążył zapisać.
- Przekazany zbiór jest modyfikowany przez
utworzUnikalnySlugi nie zapewnia bezpieczeństwa między wątkami. W aplikacji WWW ostateczną ochroną przed duplikatem powinno być ograniczenieUNIQUEw bazie.
Podobne skrypty
Walidacja danych: e-mail, PESEL i NIP
Zestaw gotowych metod sprawdzających adres e-mail, numer PESEL (data urodzenia, płeć i cyfra kontrolna) oraz NIP z sumą kontrolną modulo 11, z tolerancją myślników i spacji.
średni Zobacz kod → Teksty i StringFormatowanie i składanie tekstu
Zestaw przykładów String.format, StringBuilder i bloków tekstu: kwoty po polsku, tabela z równymi kolumnami, skracanie zbyt długich nazw i bezpiecznie zakodowany fragment HTML.
początkujący Zobacz kod → Teksty i StringSortowanie polskich napisów (Collator)
Sortowanie i porównywanie tekstów tak, jak układa je polski alfabet: Collator zamiast String.compareTo, opcjonalne rozróżnianie wielkości liter, bezpieczna obsługa null i sortowanie rekordów po nazwisku.
średni Zobacz kod →