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; } }