Pro každou významnější aplikaci (de fakto tu, od které se očekává oblíbenost mezi početnější skupinou uživatelů) je důležité její překlad do alepoň 2 jazyků. Způsobů, kterým je toto "vylepšení" realizováno je nespočet - stačí jen pátrat v nalezených výsledcích na Google vyhledávači. V čem vyniká a naopak zaostává mé řešení této problematiky se dozvíte v dalších řádcích.
Úvod do mého přístupu
Ještě před započetím psaní oněch kódů, jsem si stanovil jistá pravidla: Jednoduchost, nenáročnost na hardware (RAM i CPU), pochopitelnost (rozuměj jako čistota kódu), rozšířitelnost - čili jeden z principů OOP (viz. Wikipedie). Právě z důvodu rychlosti jsem se rozhodl, že nehodlám veškerý text pro komunikaci s uživatelem (zatím nepoužívám GUI) zapsat do "jazykového souborů/knihovny", jak tomu u spousty programů bývá, nýbrž vytvořit pole, jehož obsah se následně přidá po každém indexu do pole proměnného (zde ArrayList, kolekce generická). Tímto krokem zajistím měnší závislost na OS (operačním systému) a zároveň se mohu vyvarovat použití přílišného množství proměnných/objektů a algoritmů pro zápis do souboru i následného čtení z něj - má aplikace by byla obecně méně náročná a celkově rychlejší.
V následujících sekcích se vám pokusím popsat a objektivně zhodnotit mé řešení, kdy - z důvodu vyšší pochopitelnosti - budu doplňovat povídání samotnými ukázkami kódů a poskytovat vám užitečné odkazy jako osvětu do problematiky.
PS: Základní návrh vícejazyčnosti je sice jednodušší, avšak ne příliš praktický. My se pokusíme zhmotnit představu, kdy jazyk bude uživateli automaticky navržen s tou možností, že ho může při prvním spuštění aplikace svým vstupem změnit.
Návrh
Nyní si představme, že naše aplikace je vlastně operační systém - potřebuje zavaděč. Zde se sice nejedná o zavaděč v pravém slova smyslu (viz. Wikipedie) - tento má totiž za úkol načíst jazykový balíček a následně zavést (nabootovat) tu třídu, v které se nachází samotná logika programu (vlastně hlavní třídu, pouze bez statické metody "main").
Prezentace základní myšlenky - zavaděč a hlavní třída
Pokud máme vytvořené nejméně obě třídy, můžeme se vrhnout do práce - na počátku vytvoříme dva základní jazykové balíčky (o modifikátorech, o statice a modifikátorech ostatních):
Kód: Vybrat vše
package jádro;
public class Zavaděč{
//Český jazykový balíček:
private static String[] naplňČeštinu = null;
//Anglický jazykový balíček:
private static String[] naplňAngli = null;
}
Následně vytvoříme proměnnou kolekci ArrayList, kterou já osobně pojmenuji symbolicky:
Kód: Vybrat vše
//Protected kvůli přístupnosti tohoto objektu z jakékoli třídy v tomto balíku (zde "jádro"):
protected static ArrayList<String> neurčité = null;
ArrayList zásadně neinstancujeme!
Dále, již v metodě "main", vytvoříme (včetně inicializace) pár objektů:
Kód: Vybrat vše
//Textový řetězec (zde statický) určující lokaci, v jakém adresáři nacházet soubor pro uložení jazyku vybraného později uživatelem (zde lokace "C:\Users\[název uživatele]\AppData\Roaming\MaliAplij"):
String cestaKsložce = System.getenv("appdata") + File.separator + "MaliAplij";
//Určuje samotný název souboru (zde "jazyk.ini" - konfigurační soubor):
String cestaKsouboru = cestaKsložce + File.separatorChar+"jazyk.ini";
//Ukládá informaci o názvu souboru, se kterým budeme později pracovat:
String jazyk = "ZměnaJazyku.txt".
//Hlavní třída, která se po veškerém nastavení zavede:
Panřídící hlavní = new Panřídící();
Nyní nám stačí instanciovat samotné "jazykové pole". Nezapomeňme, že jedná se o jazykové balíčky do hlavní třídy, výstup (de fakto komunikaci s uživatelem) v třídě této vyřešíme později. Dále je třeba zmínit, aby index prvku v prvním poli (př. českém) odpovídal indexu jeho překladu v poli druhém (př. anglickém). Pokud nerozumíte, záhy pochopíte onu myšlenku:
Kód: Vybrat vše
//Instancování hlavních jazykových balíčků:
naplňČeštinu = new String[] {"Achoj, co mám ti říci?", "My jsme v kanále?!"};
naplňAngli = new String[] {"Hello, what I can say to you?", "We are in a channel?!"};
Nyní naše programování konečně začne být "napínavé" - vrháme se do hlavní logiky zavaděče!
Zaprvé, musíme vytvoři základní podmínku týkající se zjištění, zda uživatel spustil naši aplikaci poprvé, či již po několikáté. Jelikož se jedná pouze o dvojici podmínek, použijeme konstrukci "if-else". A jelikož hodláme vměstnat do souborového systému OS nový soubor, do "hlavičky" (kulatých závorek) podmínky "if" si necháme zjistit za pomoci metody "Files.exists()" zjistit, zda náš nově vytvořený soubor již je v soub. sys. přítomný. Zde je právě čas utvořit i zároveň instanciovat další textové pole zastupující jazykový balíček, tentokráte pouze do této třídy pro komunikaci s uživatelem.
PS: Skloubit následující pole s jazykovým balíčkem hlavním není dobrý nápad - zbytečně se do paměti uloží více prakticky dat a v onon poli se i, z důvodu zvýšeného množství Stringů v něm, hůře orientuje.
První spuštění:
Kód: Vybrat vše
//Pro operace se soubory budeme již používat novou třídu "Files":
if(Files.exists(Paths.get(cestaKsouboru)) == false){
//Podpůrné (mono třídní) jazykové balíčky:
String[] naplňMainČesky = {"Já protestuji! Je mě umožněno volat pouze v této podmínce!"};
String[] naplňMainAngli = {"I´am a rebel! You can call me only in this condition!"};
//Zase, pro nás ještě tajemné, proměnné pole sloužící k samotnému volání indexů v jazykových baličcích:
ArrayList<String> mainNeurčité = null;
//Scanner zajistí komunikaci s uživatelem:
Scanner zamol = new Scanner(System.in);
//Do textového řetězce se uloží informace ohledně nastaveného jazyku v OS:
String zjistiJazyk = System.getProperty("user.language");
//Určuje jazyk, který se dle uživatelské volby po dalším spuštění automaticky nahraje do kolekce, z které snadlo lze získávat jednotlivé indexy (resp. prvky):
boolean identifJazyk = true; //true = čeština; false = angličtina
}
Nyní se program pokusí automaticky zjistit jazyk upředňostňovaný daným jedincem - uživatelem -, aby dorozumívání se s ním bylo co nejjednodušší. Zároveň si v následujících řádcích skutečně odzkoušíme funkčnost volání prvků z abstraktních polí.
Kód: Vybrat vše
//Když systémový jazyk je čeština:
if(zjistiJazyk .toLowerCase().contains("cs")){
//Abstraktní pole naplníme češtinou pomocí třídy "Arrays" a metody "asList()":
mainNeurčité = new ArrayList<String>(Arrays.asList(naplňMainČesky));
System.out.println("Volám prvek číslo 0: "+mainNeurčité.get(0));
}
//Když systémový jazyk se jeví jako angličtina:
else{
//Naplnit angličtinou:
mainNeurčité = new ArrayList<String>(Arrays.asList(naplňMainAngli));
System.out.println("I call element with number zero: "+mainNeurčité.get(0));
//Identifikátor přepneme na protější hodnotu, false - angličtina:
identifJazyk = false;
}
Jelikož do dynamického pole "mainNeurčité" již nechceme přidávat/odebírat další prvky, pouze je volat, pro ušetření místa v RAM zavoláme metodu ".trimToSize()", která délku pole přizpůsobí nynějšímu počtu prvků v něm (jinak bývá 2x větší, viz. O metodě):
Kód: Vybrat vše
mainNeurčité.trimToSize();
V dalších řádcích se pokusíme s možným uživatelem "domluvit" o automaticky voleném jazyku při dalším spuštění.
PS: V praxi by i pozdější vstup programu se měl uskutečňovat za pomocí volání jednotlivých prvků z kolekce "mainNeurčité" - podpora vícejazyčnosti. Zde však kvůli pochopitelnosti tuto povinost vynecháme.
Kód: Vybrat vše
System.out.println("Přejete si tento jazyk, český, ponechat?");
System.out.println("1) Ano\n2) Kdepak");
//Získáme od uživatele vstup:
switch(zamol.nextInt()){
//Změna jazyku:
case 2:
System.out.println("1) Angličtina");
//Kdyby si uživatel výběr rozmyslel
System.out.println("2) Čeština");
switch(zamol.nextInt()){
case 1:
//Reálně se změní jazyk až později:
identifJazyk = false;
break;
default:
identifJazyk = true;
}
break;
default:
}
Teď, když již uživatel zvolil si upředňostňovaný jazyk, musíme ony potřebná data zapsat do určitého souboru. Jeho jméno již známe - nese ho String "jazyk" ("ZměnaJazyku.txt"). Ten vytvoříme v společně s adresářem určeným ve Stringu "cestaKsložce" (v "%appdata%").
Pokračujeme tedy v přepínači "switch" u možnosti "default":
Kód: Vybrat vše
default:
//Pro následující jednotlivé operace důrezně doporučuji vytvořit si vlasní metody nejlépe v oddělené třídě:
Files.createDirectory(Paths.get(cestaKsložce));
Files.createFile(Paths.get(cestaKsouboru+jazyk));
BufferedWriter napiš = Files.newBufferedWriter(Paths.get(cestaKsouboru+jazyk), Charset.defaultCharset());
napiš.write(String.valueOf(identifJazyk));
napiš.flush();
Scanner již nebudeme dále potřebovat, proto ho necháme napospas Garbage Collectoru):
Ostatní objekty, de fakto podpůrné jazykové balíčky atd., se nachází v samotné podmínce "if" - po vyjetí z ní se automaticky objekty deklarované v ní odstraní, potažmo uvolní místo v paměti - není tedy nutno je značit jako "null".
Kód: Vybrat vše
zamol = null;
Po další čas se budeme zaměřovat na možnost, která se uplatní vždy po zvolení preferovaného jazyka - další spuštění aplikace. Abychom tento pojem adaptovali našemu činění, vysvětleme si onu skutečnost takto: Při opakovaném spuštění či po zvolení upředňostňovaného jazyka (proto zde vynecháme podmínku "else", která by zabránila přechodu do ní z "if") se pokusíme získat data z onoho souboru "ZměnaJazyku.txt":
Několikáté spuštění:
Kód: Vybrat vše
//Čtení z souboru (reálně, znovu opakuji, je velice užitečné si vytvořit pro tento účel metody):
//Proměnný textový řetězec "StringBuilder" - společně se "StringBuffer" tvoří asi nejvhodnější objekt, do jakého lze ukládat proměnná data (de fakto abstraktní text) - u programátorů si získal oblibu hlavně díky rychlosti vkládání/mazání dat do/z něj. Dále obsahuje spoustu užitečných metod, viz. https://docs.oracle.com/javase/7/docs/api/java/lang/StringBuilder.html
StringBuilder data = new StringBuilder();
BufferedReader čti = Files.newBufferedReader(Paths.get(cestaKsouboru), Charset.defaultCharset());
//Jeden index v dynamickém poli "data" = jeden řádek v souboru.
String čtečka = "";
while ((čtečka = čti.readLine() )!= null) {
data.append(čtečka);
}
//Načítání jazykového balíku do Hlavní třídy dle identifikátoru obsaženého v souboru "ZměnaJazyku.txt":
if(data.equals(true)){
neurčité = new ArrayList<String>(Arrays.asList(naplňČeštinu)); //Čeština
}
else{
neurčité = new ArrayList<String>(Arrays.asList(naplňAngli)); //Angličtina
}
Nebojte, jsme téměř u konce - jako poslední zajistíme, komunikaci s hlavní třídou a zavaděčem. Zprostředkovatel bude jednoduchý "boolean" vytvořený v oné hlavní třídě (zde jednoduše "Panřídící"), který pomocí návratové hodnoty zajistí, aby zavaděč reagoval na onu hodnotu - buď aplikace bude ukončena, či "pseudomain" metoda (vyjímaje pomocnou metodu vracející onen "boolean", jediná ve třídě "Panřídící" - zde ji pojmenujeme jako "běž()") bude znovu zavolána.
Je nutno uvést příklad? Uživatel v hlavní třídě zadal špatnou hodnotu do Stringu, proto ho požádáme, aby akci znovu, a tentokráte správně, učinil - potřebujeme znovu zavolat metodu běž().
Samozřejmě, ukončování/restartování vašeho programu lze uskutečnit v těle "běž()". Já jsem toto povinost však přenechal zavaděči.
Inu, pojďmě ony poslední řádky dopsat:
Kód: Vybrat vše
//Zase ušetříme pomocí "ořezání délky" abstraktního pole místo v RAM:
neurčité.trimToSize();
//Zavedení hlavní třídy v cyklu zajišťující včasné (chtěné) ukončení programu:
while(hlavní.vraťStav() == true){ //Pokud se rovná "stav" false, vyjet z cyklu - vypnout aplikaci
hlavní.běž();
}
System.exit(0);
}
}
A metoda "vraťStav()" - jen pro orientaci:
Kód: Vybrat vše
//Obyčejný getter:
public boolean vraťStav(){
return stav;
Závěr
Jak jste si sami mohli všimnout, i z tohoto způsobu realizace vícejazyčnosti se odvíjí spousta jiných (jistě i obecně lepších) řešení této problematiky. Jako reakci na tento fakt bych konstoval tedy na rozloučenou: Naučil-li jsem vás novým pohledům, jsem rád; naleznete-li však řešení výhodnější, můžeme být rádi všichni.
Uvolněný zdrojový kód