From 8d49b229150312e3614adf2cabe9bbe050b772e1 Mon Sep 17 00:00:00 2001 From: Artur Welp Date: Wed, 19 Aug 2026 11:22:14 -0300 Subject: [PATCH] First Commit --- .gitignore | 3 + README.md | 134 ++++++++++++ composer.json | 20 ++ sandbox/app/Module_a/Submodule_a/lang/en.php | 2 + .../app/Module_a/Submodule_a/lang/pt_br.php | 2 + sandbox/app/Module_a/Submodule_b/Routes.php | 18 ++ sandbox/app/Module_b/Submodule_c/Routes.php | 18 ++ sandbox/app/Module_b/Submodule_d/Routes.php | 13 ++ .../app/Module_b/Submodule_d/Submodule_b.php | 8 + .../Submodule_d/Submodule_bController.php | 122 +++++++++++ sandbox/bootstrap.php | 9 + sandbox/composer.json | 23 +++ sandbox/composer.lock | 45 ++++ sandbox/public/index.php | 3 + src/Lang/Lang.php | 192 ++++++++++++++++++ 15 files changed, 612 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 composer.json create mode 100644 sandbox/app/Module_a/Submodule_a/lang/en.php create mode 100644 sandbox/app/Module_a/Submodule_a/lang/pt_br.php create mode 100755 sandbox/app/Module_a/Submodule_b/Routes.php create mode 100755 sandbox/app/Module_b/Submodule_c/Routes.php create mode 100755 sandbox/app/Module_b/Submodule_d/Routes.php create mode 100644 sandbox/app/Module_b/Submodule_d/Submodule_b.php create mode 100644 sandbox/app/Module_b/Submodule_d/Submodule_bController.php create mode 100755 sandbox/bootstrap.php create mode 100644 sandbox/composer.json create mode 100644 sandbox/composer.lock create mode 100644 sandbox/public/index.php create mode 100644 src/Lang/Lang.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..20a2004 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/netbeans +/.idea +/nbproject \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..d9a099d --- /dev/null +++ b/README.md @@ -0,0 +1,134 @@ +# urfat/lang + +A simple, zero-dependency PHP internationalization (i18n) library. It auto-discovers translation files from your app directories and caches them in the session or Memcached. + +## Requirements + +- PHP 8.0+ +- `ext-memcached` _(optional, only for Memcached caching)_ + +## Installation + +```bash +composer require inforsistemas/lang +``` + +## How it works + +The library scans your app directories recursively for files matching the pattern `lang/*.php`. Each file defines a `$lang` array where the filename (without `.php`) becomes the locale key. + +**File:** `app/MyModule/lang/en.php` +```php + 'Hello, World!', ...] +Lang::getStrings('my_module', 'pt_br'); // ['greeting' => 'Olá, Mundo!', ...] +``` + +If a key is not found, the key name itself is returned as a fallback. + +## Caching + +Strings are cached after the first directory scan to avoid re-reading files on every request. + +### SESSION (default) + +No configuration needed. Strings are stored in `$_SESSION['cache']['strings']`. + +```php +Lang::buildStrings('app/'); // uses SESSION by default +Lang::buildStrings('app/', false, 'SESSION'); // explicit +``` + +### Memcached + +```php +$memcached = new Memcached(); +$memcached->addServer('127.0.0.1', 11211); + +Lang::setMemcached($memcached, 'my_app_lang', 3600); // key, ttl (seconds) +Lang::buildStrings('app/', false, 'MEMCACHED'); +``` + +### Forcing a rebuild + +Pass `true` as the second argument to ignore the cache and rescan all files. + +```php +Lang::buildStrings('app/', rebuild: true); +``` + +## API Reference + +```php +// Configure Memcached (optional) +Lang::setMemcached(\Memcached $instance, string $key = 'urfat_lang_strings', int $ttl = 3600): void + +// Scan directories and populate the string database +Lang::buildStrings(string|array $folders, bool $rebuild = false, string $cache = 'SESSION'): void + +// Get a single translated string +Lang::getString(string $string, string $module = '', string $lang = 'en'): string + +// Get all strings for a module +Lang::getStrings(string $module = '', string $lang = 'en'): array +``` + +## Directory structure example + +``` +app/ + UserModule/ + lang/ + en.php + pt_br.php + OrderModule/ + lang/ + en.php + pt_br.php +``` + +```php +Lang::buildStrings('app/'); +Lang::getString('page_title', 'user_module'); +Lang::getString('page_title', 'order_module', 'pt_br'); +``` diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..835b1b2 --- /dev/null +++ b/composer.json @@ -0,0 +1,20 @@ +{ + "name" : "inforsistemas/lang", + "description" : "A simple internationalization library", + "type" : "library", + "authors" : [ + { + "name" : "Artur Welp", + "email" : "ahwelp@universo.univates.br" + } + ], + "autoload" : { + "psr-4" : { + "Lang\\" : "src/Lang/" + } + }, + "require" : { }, + "suggest" : { + "ext-memcached" : "Necessary for Memcached (MEMCACHED in buildStrings)" + } +} diff --git a/sandbox/app/Module_a/Submodule_a/lang/en.php b/sandbox/app/Module_a/Submodule_a/lang/en.php new file mode 100644 index 0000000..fe5c495 --- /dev/null +++ b/sandbox/app/Module_a/Submodule_a/lang/en.php @@ -0,0 +1,2 @@ +doIgnore(); + +RouteCollection::get("/module_a/submodule_b/", function(){ + echo "Executin the /module_a/submodule_b/ route"; +}); + +RouteCollection::get("/module_a/submodule_b/[d:value]", function($value){ + echo "Executing the /module_a/submodule_b/ route and receaved the value $value"; +}); diff --git a/sandbox/app/Module_b/Submodule_c/Routes.php b/sandbox/app/Module_b/Submodule_c/Routes.php new file mode 100755 index 0000000..a5744f9 --- /dev/null +++ b/sandbox/app/Module_b/Submodule_c/Routes.php @@ -0,0 +1,18 @@ +doIgnore(); + +RouteCollection::get("/module_b/submodule_a/", function(){ + echo "Executin the /module_b/submodule_a/ route"; +}); + +RouteCollection::get("/module_b/submodule_a/[d:value]", function($value){ + echo "Executing the /module_b/submodule_a/ route and receaved the value $value"; +}); diff --git a/sandbox/app/Module_b/Submodule_d/Routes.php b/sandbox/app/Module_b/Submodule_d/Routes.php new file mode 100755 index 0000000..4a60ebf --- /dev/null +++ b/sandbox/app/Module_b/Submodule_d/Routes.php @@ -0,0 +1,13 @@ + 0]); + Output::render(); + } + + /** + * Store + * + * Store the param on the database + * @param Users $users + */ + function store(Submodule_b $Submodule_b){ + $info = array_merge(["message" => "The following data was receaved by POST on the store function"], $_POST); + + header('Content-Type: application/json; charset=utf-8'); + echo json_encode($info); + } + + /** + * Show + * + * Render one register + * + * @param Users $users + */ + function show(Submodule_b $Submodule_b){ + $i = rand(0, 1000); + $info = [ + 'id' => $i, + "value" => md5($i), + "description" => sha1($i), + ]; + + header('Content-Type: application/json; charset=utf-8'); + echo json_encode($info); + } + + /** + * Edit + * + * Render the formular for a database Users + * + * @param Users $users + */ + function edit(Submodule_b $Submodule_b){ + $info = array_merge(["message" => "The following data was receaved by POST on the edit function"], $_POST); + + header('Content-Type: application/json; charset=utf-8'); + echo json_encode($info); + } + + /** + * Update + * Store the changes of the param on the database + * + * @param Users $users + */ + function update(Submodule_b $Submodule_b){ + $info = array_merge(["message" => "The following data was receaved by POST on the update function"], $_POST); + + header('Content-Type: application/json; charset=utf-8'); + echo json_encode($info); + } + + /** + * Destroy + * If the object has soft delete. + * + * @param Users $users + */ + function destroy(Submodule_b $Submodule_b){ + $info = array_merge(["message" => "The following data was receaved by POST on the destroy function"], $_POST); + + header('Content-Type: application/json; charset=utf-8'); + echo json_encode($info); + } + + /** + * Purge + * Remove object even with soft delete. + * + * @param Users $users + */ + function purge(Submodule_b $Submodule_b){ + $info = array_merge(["message" => "The following data was receaved by POST on the destroy function"], $_POST); + + header('Content-Type: application/json; charset=utf-8'); + echo json_encode($info); + } +} diff --git a/sandbox/bootstrap.php b/sandbox/bootstrap.php new file mode 100755 index 0000000..b0dd06a --- /dev/null +++ b/sandbox/bootstrap.php @@ -0,0 +1,9 @@ +set(self::$memcachedKey, $_LANG, self::$memcachedTtl); + break; + default: + if(session_id() == '' || !isset($_SESSION)) { + session_start(); + } + $_SESSION['cache']['strings'] = $_LANG; + break; + } + } + + /** + * Load the string cache on the system + * + * @param string $method + * + * @return bool true = there is cache | false = no cache found + */ + private static function loadCacheString(string $method = "SESSION"): bool + { + global $_LANG; + + switch ($method){ + case "MEMCACHED": + if (self::$memcached === null) { + throw new \Exception("lang: Memcached instance not configured. Call Lang::setMemcached() first."); + } + $cached = self::$memcached->get(self::$memcachedKey); + if ($cached !== false) { + $_LANG = $cached; + return true; + } + return false; + default: + if (session_id() == '' || !isset($_SESSION)) { + session_start(); + } + if (isset($_SESSION['cache']['strings'])) { + $_LANG = $_SESSION['cache']['strings']; + return true; + } + return false; + } + } + + /** + * Obtain all strings for a module + * + * @param string $module + * @param string $lang + * + * @return Array + */ + public static function getStrings(string $module = '', string $lang = 'en'): Array + { + global $_LANG; + + if (isset($_LANG[$lang][$module])) { + return $_LANG[$lang][$module]; + } + + return $module; + } + + /** + * Obtain a string strings for a module + * + * @param string $string + * @param string $module + * @param string $lang + * + * @return string + */ + public static function getString(string $string = '', string $module = '', string $lang = 'en'): string + { + global $_LANG; + + if (isset($_LANG[$lang][$string]) && is_string($_LANG[$lang][$string])) { + return $_LANG[$lang][$string]; + } + + if (isset($_LANG[$lang][$module][$string])) { + return $_LANG[$lang][$module][$string]; + } + + return $string; + } + + /** + * Load all system string + * + * @param string|Array $folders Array of folders to scrape. You can passa a string for a single folder + * @param bool $rebuild Shoul we ignore the system cache and rebuild the string database + * @param string $cache How to cache the strings in memory/disk + * + * @return void + */ + public static function buildStrings( + string|Array $folders, + bool $rebuild = false, + string $cache = "SESSION" + ): void + { + global $_LANG; + + if(is_string($folders)){ + $folders = [$folders]; + } + + $hasCache = self::loadCacheString($cache); + + // We have everything on cache + if ($hasCache && !$rebuild) { + return; + } + + foreach($folders as $folder){ + $rdi = new RecursiveDirectoryIterator($folder); + foreach (new RecursiveIteratorIterator($rdi) as $file) { + if (preg_match("/lang\/.*\.php$/", $file)) { + $included_lang = explode('/', $file); + $included_lang = end($included_lang); + $included_lang = str_replace('.php', '', $included_lang); + $lang = array(); + include_once $file; + $_LANG[$included_lang][array_keys($lang)[0]] = array_values($lang)[0]; + } + } + } + + self::cacheStrings($cache); + + } +}