Skip to content

Commit

Permalink
Added TextAssets and isRightToLeft.
Browse files Browse the repository at this point in the history
* Added `TextAssets` template class for easily loading
  text assets for multiple languages without having to
  manually define a class for them.
* Added `isRightToLeft` function to detect whether a
  certain language is written from right to left.
  • Loading branch information
sarmadka committed Nov 25, 2023
1 parent d811815 commit 817032f
Show file tree
Hide file tree
Showing 7 changed files with 358 additions and 67 deletions.
71 changes: 11 additions & 60 deletions I18n.alusus
Original file line number Diff line number Diff line change
@@ -1,63 +1,14 @@
import "Srl/String";
import "Srl/Array";
import "Srl/refs";
import "Spp";
import "Core/Basic";
import "Core/Data";

module I18n {
use Srl;
def strings: Map[String, String];

func initialize(buf: CharsPtr) {
strings.clear();
strings = parsePo(buf);
}

func string(val: CharsPtr): String {
def input: String(val);
def res: String = strings(input);
if res == "" return input else return res;
}

func parsePo(buf: CharsPtr): Map[String, String] {
def result: Map[String, String];
def lastId: String;
def isMsgid: Bool = false;
def isMsgstr: Bool = false;
while buf~cnt(0) != 0 {
if String.compare(buf, "msgid", 5) == 0 {
buf += 5;
lastId = parseString(buf);
} else if String.compare(buf, "msgstr", 6) == 0 {
buf += 6;
result(lastId) = parseString(buf);
} else if buf~cnt(0) == '#' {
while buf~cnt(0) != '\n' buf += 1;
} else {
buf += 1;
}
}
return result;
}

func parseString(buf: ref[CharsPtr]): String {
def result: String;
def len: ArchInt = 0;
while true {
while buf~cnt(0) == ' ' or buf~cnt(0) == '\n' buf += 1;
if buf~cnt(0) == '"' {
buf += 1;
def end: CharsPtr = String.find(buf, '\n')~cast[CharsPtr];
while end~cnt(0) != '"' end -= 1;
result.realloc(len + end~cast[ArchInt] - buf~cast[ArchInt]);
while buf~cast[ArchInt] < end~cast[ArchInt] {
if buf~cnt(0) == '\\' {
buf += 1;
if buf~cnt(0) == 'n' result.buf~cnt(len++) = '\n'
else result.buf~cnt(len++) = buf~cnt(0);
} else result.buf~cnt(len++) = buf~cnt(0);
buf += 1;
}
result.buf~cnt(len) = 0;
buf += 1;
} else {
break;
}
}
return result;
}
}

import "I18n/strings";
import "I18n/TextAssets";
import "I18n/isRightToLeft";
82 changes: 82 additions & 0 deletions I18n/TextAssets.alusus
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
@merge module I18n {
class TextAssets [path: string, defaultLangCode: string] {
@shared def data: Map[String, SrdRef[this_type]];
@shared def current: SrdRef[this_type];
@shared def currentLanguage: String;

handler this~init() {}

def AstTemplateMap: alias Map[String, ref[Core.Basic.TiObject]];

// Define member variables.
preprocess {
def filenames: ptr[Fs.FileNames] = Fs.readDir(String(path) + "/" + defaultLangCode);
def i: Int;
for i = 0, i < filenames~cnt.count, ++i {
if filenames~cnt.names(i)(0) == '.' continue; // Skip . and .. and hidden files.
def name: String = String(filenames~cnt.names(i)~ptr).split(".")(0);
Spp.astMgr.insertAst(
(ast def name: String),
AstTemplateMap().set(String("name"), Core.Basic.TiStr(name))
);
}
}

func initialize() {
def d: SrdRef[this_type];

// Populate the map for all available locales.
preprocess {
def dirs: ptr[Fs.FileNames] = Fs.readDir(path);
def i: Int;
for i = 0, i < dirs~cnt.count, ++i {
if dirs~cnt.names(i)(0) == '.' continue; // Skip . and .. and hidden files.
Spp.astMgr.insertAst(ast d = SrdRef[this_type].construct());

def filenames: ptr[Fs.FileNames] = Fs.readDir(String(path) + "/" + dirs~cnt.names(i)~ptr);
def j: Int;
for j = 0, j < filenames~cnt.count, ++j {
if filenames~cnt.names(j)(0) == '.' continue; // Skip . and .. and hidden files.
def content: String = Fs.readFile(
String(path) + "/" + dirs~cnt.names(i)~ptr + "/" + filenames~cnt.names(j)~ptr
);
def varName: String = String(filenames~cnt.names(j)~ptr).split(".")(0);
Spp.astMgr.insertAst(
(ast d.varName = varData),
AstTemplateMap()
.set(String("varName"), Core.Data.Ast.Identifier(varName))
.set(String("varData"), Core.Data.Ast.StringLiteral(content))
);
}

Spp.astMgr.insertAst(
(ast data.set(String(lang), d)),
AstTemplateMap().set(String("lang"), Core.Basic.TiStr(String(dirs~cnt.names(i)~ptr)))
);
}
}

setCurrentLanguage(String(defaultLangCode));
}

func getAvailableLanguages(): Array[String] {
def langs: Array[String] = data.keys;
// Bring the default language to the front.
def i: Int;
for i = 0, i < langs.getLength(), ++i {
if langs(i) == defaultLangCode {
langs.remove(i);
langs.insert(0, String(defaultLangCode));
break;
}
}
return langs;
}

func setCurrentLanguage(langCode: String) {
if data(langCode).isNull() langCode = defaultLangCode;
current = data(langCode);
currentLanguage = langCode;
}
}
}
22 changes: 22 additions & 0 deletions I18n/isRightToLeft.alusus
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
@merge module I18n {
func isRightToLeft(languageCode: String): Bool {
@shared def rtlLanguages: Array[String]({
String("ar"),
String("arc"),
String("dv"),
String("fa"),
String("ha"),
String("he"),
String("kh"),
String("ks"),
String("ku"),
String("ps"),
String("ur"),
String("yi")
});
languageCode = languageCode.split("_")(0);
def i: Int;
for i = 0, i < rtlLanguages.getLength(), ++i if rtlLanguages(i) == languageCode return true;
return false;
}
}
62 changes: 62 additions & 0 deletions I18n/strings.alusus
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
@merge module I18n {
def strings: Map[String, String];

func initializeStrings(buf: CharsPtr) {
strings.clear();
strings = parsePo(buf);
}

func string(val: CharsPtr): String {
def input: String(val);
def res: String = strings(input);
if res == "" return input else return res;
}

func parsePo(buf: CharsPtr): Map[String, String] {
def result: Map[String, String];
def lastId: String;
def isMsgid: Bool = false;
def isMsgstr: Bool = false;
while buf~cnt(0) != 0 {
if String.compare(buf, "msgid", 5) == 0 {
buf += 5;
lastId = parseString(buf);
} else if String.compare(buf, "msgstr", 6) == 0 {
buf += 6;
result(lastId) = parseString(buf);
} else if buf~cnt(0) == '#' {
while buf~cnt(0) != '\n' buf += 1;
} else {
buf += 1;
}
}
return result;
}

func parseString(buf: ref[CharsPtr]): String {
def result: String;
def len: ArchInt = 0;
while true {
while buf~cnt(0) == ' ' or buf~cnt(0) == '\n' buf += 1;
if buf~cnt(0) == '"' {
buf += 1;
def end: CharsPtr = String.find(buf, '\n')~cast[CharsPtr];
while end~cnt(0) != '"' end -= 1;
result.realloc(len + end~cast[ArchInt] - buf~cast[ArchInt]);
while buf~cast[ArchInt] < end~cast[ArchInt] {
if buf~cnt(0) == '\\' {
buf += 1;
if buf~cnt(0) == 'n' result.buf~cnt(len++) = '\n'
else result.buf~cnt(len++) = buf~cnt(0);
} else result.buf~cnt(len++) = buf~cnt(0);
buf += 1;
}
result.buf~cnt(len) = 0;
buf += 1;
} else {
break;
}
}
return result;
}
}
102 changes: 98 additions & 4 deletions readme.ar.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,27 +26,27 @@ Apm.importFile("Alusus/I18n");
<div dir=rtl>

```
تـرجمة.هيئ(محتوى_po)؛
تـرجمة.هيئ_النصوص(محتوى_po)؛
```

</div>

```
I18n.initialize(poContent);
I18n.initializeStrings(poContent);
```

أو

<div dir=rtl>

```
تـرجمة.هيئ(مـتم.نـم.اقرأ_ملف("en.po"))؛
تـرجمة.هيئ_النصوص(مـتم.نـم.اقرأ_ملف("en.po"))؛
```

</div>

```
I18n.initialize(Srl.Fs.readFile("lang.po"));
I18n.initializeStrings(Srl.Fs.readFile("lang.po"));
```

* استبدل سلاسل المحارف المضمنة بالدالة `تـرجمة.نص`:
Expand All @@ -63,3 +63,97 @@ I18n.initialize(Srl.Fs.readFile("lang.po"));
Console.print(I18n.string("My localized string"));
```

## معرفة اتجاه اللغة

لمعرفة اتجاه اللغة، اي معرفة إن كانت تكتب من اليمين أو اليسار، نستخدم دالة `هل_اللغة_يمينية` (`isRightToLeft`):

<div dir=rtl>

```
تـرجمة.هل_اللغة_يمينية(نـص("ar")) // ترجع 1
تـرجمة.هل_اللغة_يمينية(نـص("ar_EG")) // ترجع 1
تـرجمة.هل_اللغة_يمينية(نـص("en")) // ترجع 0
```

</div>

```
I18n.isRightToLeft(String("ar")) // returns true
I18n.isRightToLeft(String("ar_EG")) // returns true
I18n.isRightToLeft(String("en")) // returns false
```

## صنف مـوارد_نصية (TextAssets)

يمكّنك هذا الصنف من تحميل ملفات نصية بعدة لغات من مجلد وتوفيرها تلقائيا كصنف يحتوي خصلة نصية لكل من
هذه الملفات، ثم يحملها لك تلقائيا مقسمة إلى لغات حسب المجلدات المتوفرة. يستقبل هذا الصنف القالب
معطيين، أحدهما مسار مجلد الموارد النصية والآخر اللغة المبدئية، ويطلب أن يحتوي مجلد الموارد النصية
على مجلد لكل لغة برمز اللغة كاسم الجلد، وفي كل من هذه المجلدات توضع الملفات النصية بنفس الأسماء لكل
اللغات. سيعرف الصنف متغيرا لكل من هذه الملفات وسيَحمل المتغير اسم الملف بدون الامتداد.

**ملاحظة:**
هذا الصنف سيحمل الموارد كلها ولكل اللغات أثناء الترجمة وليس أثناء التنفيذ، ما يتيح استخدامه لترجمة
واجهات المستخدم في تطيبقات الويب.

على سبيل المثال، إذا كان لديك المجلد التالي:

```
assets/
en/
file1.txt
file2.txt
strings.po
ar/
file1.txt
file2.txt
strings.po
```

فبعد تهيئة الصنف سيكون لديك صنف يحتوي على `تـطبيق` (`Map`) فيه مفتاحين: ar و en، ولكل مفتاح نموذج من
الصنف. والصنف في هذه الحالة سيحتوي على ثلاثة متغيرات من صنف `نـص` (`String`) وهي: file1 و file2
و strings.

لتهيئة واستخدام الصنف:

<div dir=rtl>

```
تـرجمة.مـوارد_نصية["./الموارد_النصية"، "ar"].هيئ()؛
تـرجمة.مـوارد_نصية["./الموارد_النصية"، "ar"].حدد_اللغة_الحالية(اللغة_الحالية)؛
عرف النصوص: نـص = تـرجمة.مـوارد_نصية["./الموارد_النصية"، "ar"].الحالي.النصوص؛
تـرجمة.هيئ_النصوص(النصوص)؛
طـرفية.اطبع("%s\ج"، تـرجمة.نص("مثال نصي"))؛
```

</div>

```
I18n.TextAssets["./text_assets", "en"].initialize();
I18n.TextAssets["./text_assets", "en"].setCurrentLanguage(currentLang);
def strings: String = I18n.TextAssets["./text_assets", "en"].current.strings;
I18n.initializeStrings(strings);
Console.print("%s\n", I18n.string("string example"));
```

يمكن الوصول إلى رمز اللغة الحالية باستخدام المتغير `اللغة_الحالية` (`currentLanguage`). يفضل الاعتماد
على هذا المتغير لأنه يضمن عدم استخدام لغة غير متوفرة ضمن الترجمات الحالية. مثلا، لو استدعيت دالة
`حدد_اللغة_الحالية` (`setCurrentLanguage`) برمز لغة غير متوفر ضمن الترجمات فإن الصنف سيختار اللغة
المبدئية بدل اختيار لغة لا ترجمة لها.

### مـوارد_نصية.هات_اللغات_المتوفرة (TextAssets.getAvailableLanguages)

ترجع هذه الدالة قائمة باللغات المتوفرة. يجب أن تُستدعى دالة `هيئ` (`initialize`) قبل استدعاء هذه
الدالة. تحرص هذه الدالة على وضع اللغة المبدئية في مقدمة المصفوفة المرجعة.

<div dir=rtl>

```
دالة هات_اللغات_المتوفرة(): مـصفوفة[نـص]؛
```

</div>

```
func getAvailableLanguages(): Array[String];
```

Loading

0 comments on commit 817032f

Please sign in to comment.