206 lines
5.5 KiB
PHP
206 lines
5.5 KiB
PHP
<?php
|
|
/*
|
|
* languagestore.php
|
|
*
|
|
* Small SQLite-backed translation module.
|
|
*
|
|
* Database:
|
|
* data/racket-sandbox.sqlite
|
|
*/
|
|
|
|
class LanguageStoreException extends Exception
|
|
{
|
|
}
|
|
|
|
class LanguageStore
|
|
{
|
|
private $db;
|
|
private $fallbackLanguage;
|
|
|
|
public function __construct($dbFile, $fallbackLanguage = 'en')
|
|
{
|
|
$dir = dirname($dbFile);
|
|
|
|
if (!is_dir($dir)) {
|
|
if (!mkdir($dir, 0700, true)) {
|
|
throw new LanguageStoreException('Could not create database directory: ' . $dir);
|
|
}
|
|
}
|
|
|
|
$this->fallbackLanguage = $this->safeLanguage($fallbackLanguage);
|
|
|
|
$this->db = new PDO('sqlite:' . $dbFile);
|
|
$this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
$this->db->exec('PRAGMA journal_mode = WAL');
|
|
$this->db->exec('PRAGMA busy_timeout = 5000');
|
|
|
|
$this->init();
|
|
}
|
|
|
|
private function init()
|
|
{
|
|
$this->db->exec(
|
|
'CREATE TABLE IF NOT EXISTS app_translations (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
translation_key TEXT NOT NULL,
|
|
language TEXT NOT NULL,
|
|
text TEXT NOT NULL,
|
|
created_at INTEGER NOT NULL,
|
|
updated_at INTEGER NOT NULL,
|
|
UNIQUE(translation_key, language)
|
|
)'
|
|
);
|
|
|
|
$this->db->exec(
|
|
'CREATE INDEX IF NOT EXISTS idx_app_translations_language
|
|
ON app_translations(language, translation_key)'
|
|
);
|
|
}
|
|
|
|
public function translate($key, $language, $fallback = null)
|
|
{
|
|
$key = $this->safeKey($key);
|
|
$language = $this->safeLanguage($language);
|
|
|
|
$text = $this->findText($key, $language);
|
|
|
|
if ($text !== null) {
|
|
return $text;
|
|
}
|
|
|
|
if ($language !== $this->fallbackLanguage) {
|
|
$text = $this->findText($key, $this->fallbackLanguage);
|
|
|
|
if ($text !== null) {
|
|
return $text;
|
|
}
|
|
}
|
|
|
|
return $fallback !== null ? (string)$fallback : $key;
|
|
}
|
|
|
|
public function setTranslation($key, $language, $text)
|
|
{
|
|
$key = $this->safeKey($key);
|
|
$language = $this->safeLanguage($language);
|
|
$text = (string)$text;
|
|
$now = time();
|
|
|
|
$stmt = $this->db->prepare(
|
|
'INSERT INTO app_translations
|
|
(translation_key, language, text, created_at, updated_at)
|
|
VALUES
|
|
(:translation_key, :language, :text, :created_at, :updated_at)
|
|
ON CONFLICT(translation_key, language)
|
|
DO UPDATE SET text = excluded.text, updated_at = excluded.updated_at'
|
|
);
|
|
|
|
$stmt->execute(array(
|
|
':translation_key' => $key,
|
|
':language' => $language,
|
|
':text' => $text,
|
|
':created_at' => $now,
|
|
':updated_at' => $now,
|
|
));
|
|
}
|
|
|
|
public function seedDefaults($translations)
|
|
{
|
|
foreach ($translations as $key => $byLanguage) {
|
|
foreach ($byLanguage as $language => $text) {
|
|
if (!$this->hasTranslation($key, $language)) {
|
|
$this->setTranslation($key, $language, $text);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public function listTranslations($language = null)
|
|
{
|
|
if ($language !== null && $language !== '') {
|
|
$stmt = $this->db->prepare(
|
|
'SELECT translation_key, language, text, created_at, updated_at
|
|
FROM app_translations
|
|
WHERE language = :language
|
|
ORDER BY translation_key'
|
|
);
|
|
$stmt->execute(array(':language' => $this->safeLanguage($language)));
|
|
} else {
|
|
$stmt = $this->db->query(
|
|
'SELECT translation_key, language, text, created_at, updated_at
|
|
FROM app_translations
|
|
ORDER BY translation_key, language'
|
|
);
|
|
}
|
|
|
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
}
|
|
|
|
public function supportedLanguages()
|
|
{
|
|
return array('en', 'nl');
|
|
}
|
|
|
|
public function languageLabel($language)
|
|
{
|
|
if ($language === 'nl') {
|
|
return 'Nederlands';
|
|
}
|
|
|
|
if ($language === 'en') {
|
|
return 'English';
|
|
}
|
|
|
|
return $language;
|
|
}
|
|
|
|
private function findText($key, $language)
|
|
{
|
|
$stmt = $this->db->prepare(
|
|
'SELECT text
|
|
FROM app_translations
|
|
WHERE translation_key = :translation_key
|
|
AND language = :language'
|
|
);
|
|
$stmt->execute(array(
|
|
':translation_key' => $key,
|
|
':language' => $language,
|
|
));
|
|
|
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
return $row ? (string)$row['text'] : null;
|
|
}
|
|
|
|
private function hasTranslation($key, $language)
|
|
{
|
|
return $this->findText($this->safeKey($key), $this->safeLanguage($language)) !== null;
|
|
}
|
|
|
|
private function safeKey($key)
|
|
{
|
|
$key = trim((string)$key);
|
|
|
|
if ($key === '') {
|
|
throw new LanguageStoreException('Translation key is required.');
|
|
}
|
|
|
|
if (strlen($key) > 160) {
|
|
throw new LanguageStoreException('Translation key is too long.');
|
|
}
|
|
|
|
return $key;
|
|
}
|
|
|
|
private function safeLanguage($language)
|
|
{
|
|
$language = strtolower(trim((string)$language));
|
|
|
|
if (!preg_match('/^[a-z][a-z0-9_-]{1,15}$/', $language)) {
|
|
throw new LanguageStoreException('Invalid language code.');
|
|
}
|
|
|
|
return $language;
|
|
}
|
|
}
|