First Commit

This commit is contained in:
2026-08-19 11:22:14 -03:00
commit 8d49b22915
15 changed files with 612 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
/netbeans
/.idea
/nbproject
+134
View File
@@ -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
<?php
$lang["my_module"]["greeting"] = "Hello, World!";
$lang["my_module"]["farewell"] = "Goodbye!";
```
**File:** `app/MyModule/lang/pt_br.php`
```php
<?php
$lang["my_module"]["greeting"] = "Olá, Mundo!";
$lang["my_module"]["farewell"] = "Tchau!";
```
The structure loaded into memory is:
```
$_LANG[locale][module][string_key] = "value"
```
## Usage
### 1. Build the string database
Call `buildStrings()` once at bootstrap, passing one or more directories to scan.
```php
use Lang\Lang;
// Single directory
Lang::buildStrings('app/');
// Multiple directories
Lang::buildStrings(['app/', 'plugins/']);
```
### 2. Retrieve a string
```php
// Get a single string (defaults to 'en' locale)
Lang::getString('greeting', 'my_module'); // "Hello, World!"
Lang::getString('greeting', 'my_module', 'pt_br'); // "Olá, Mundo!"
// Get all strings for a module
Lang::getStrings('my_module'); // ['greeting' => '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');
```
+20
View File
@@ -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)"
}
}
@@ -0,0 +1,2 @@
<?php
$lang["submodule_a"]["module_name"] = "Module a Submodule a";
@@ -0,0 +1,2 @@
<?php
$lang["submodule_a"]["module_name"] = "Módulo a Submódulo a";
+18
View File
@@ -0,0 +1,18 @@
<?php
use Routes\RouteCollection as RouteCollection;
RouteCollection::add("WEB", "/module_a/submodule_b/", function(){
// Doing house keeping work
$a = 1;
$b = 2;
$c = $a + $b;
})->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";
});
+18
View File
@@ -0,0 +1,18 @@
<?php
use Routes\RouteCollection as RouteCollection;
RouteCollection::add("WEB", "/module_b/submodule_a/", function(){
// Doing house keeping work
$a = 1;
$b = 2;
$c = $a + $b;
})->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";
});
+13
View File
@@ -0,0 +1,13 @@
<?php
use Routes\RouteCollection as RouteCollection;
RouteCollection::group("/module_b/Submodule_b", function(){
RouteCollection::get ("/", "\App\Module_b\Submodule_b\Submodule_bController@index");
RouteCollection::get ("/form", "\App\Module_b\Submodule_b\Submodule_bController@create");
RouteCollection::post ("/", "\App\Module_b\Submodule_b\Submodule_bController@store");
RouteCollection::get ("/[i:id]", "\App\Module_b\Submodule_b\Submodule_bController@show");
RouteCollection::get ("/[i:id]/edit", "\App\Module_b\Submodule_b\Submodule_bController@edit");
RouteCollection::put ("/[i:id]/edit", "\App\Module_b\Submodule_b\Submodule_bController@update");
RouteCollection::delete("/[i:id]", "\App\Module_b\Submodule_b\Submodule_bController@destroy");
});
@@ -0,0 +1,8 @@
<?php
namespace App\Module_b\Submodule_b;
class Submodule_b
{
}
@@ -0,0 +1,122 @@
<?php
namespace App\Module_b\Submodule_b;
use App\Module_b\Submodule_b\Submodule_b as Submodule_b;
class Submodule_bController {
/**
* Index
* Show the main Users list
*/
function index(){
$list = Array();
for($i = 0; $i <= 10; $i++){
$obj = Array();
$obj['id'] = $i;
$obj['value'] = md5($i);
$obj['description'] = sha1($i);
$list[] = $obj;
}
header('Content-Type: application/json; charset=utf-8');
echo json_encode($list);
}
/**
* Create
*
* Render the main Users formulary
*/
function create(){
Output::setView('form', ['id' => 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);
}
}
+9
View File
@@ -0,0 +1,9 @@
<?php
include_once 'vendor/autoload.php';
use \Lang\Lang;
Lang::buildStrings("app/");
Lang::getString("module_name", "submodule_a");
+23
View File
@@ -0,0 +1,23 @@
{
"name": "urfat/lang_sandbox",
"type": "project",
"repositories": [
{
"type": "path",
"url": "../",
"options": {
"symlink": true
}
}
],
"minimum-stability": "dev",
"prefer-stable": true,
"require": {
"urfat/lang": "*"
},
"autoload": {
"psr-4": {
"App\\": "app/"
}
}
}
+45
View File
@@ -0,0 +1,45 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "ce8328cb4f81460d55c98dada7c22944",
"packages": [
{
"name": "inforsistemas/routes",
"version": "dev-master",
"dist": {
"type": "path",
"url": "..",
"reference": "05a330d65e4745ab7ad2f798ae46600b689e8658"
},
"type": "library",
"autoload": {
"psr-4": {
"Routes\\": "src/Routes/"
}
},
"authors": [
{
"name": "Artur Welp",
"email": "ahwelp@universo.univates.br"
}
],
"description": "Different Routes",
"transport-options": {
"symlink": true,
"relative": true
}
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "dev",
"stability-flags": {},
"prefer-stable": true,
"prefer-lowest": false,
"platform": {},
"platform-dev": {},
"plugin-api-version": "2.9.0"
}
+3
View File
@@ -0,0 +1,3 @@
<?php
include '../bootstrap.php';
+192
View File
@@ -0,0 +1,192 @@
<?php
namespace Lang;
/**
* Class to handle internationalization
* This class
* - Scrape files on directories
* - Load string from files
* - Cache strings on memory
* - Load string
*/
class Lang
{
private static ?\Memcached $memcached = null;
private static string $memcachedKey = 'urfat_lang_strings';
private static int $memcachedTtl = 3600;
/**
* Configure the Memcached instance to use for caching
*
* @param \Memcached $instance
* @param string $key Cache key name
* @param int $ttl Time-to-live in seconds
*
* @return void
*/
public static function setMemcached(\Memcached $instance, string $key = 'urfat_lang_strings', int $ttl = 3600): void
{
self::$memcached = $instance;
self::$memcachedKey = $key;
self::$memcachedTtl = $ttl;
}
/**
* Store the String list on a cache method
*
* @param string $method
*
* @return void
*/
private static function cacheStrings(string $method = "SESSION"): void
{
global $_LANG;
switch ($method){
case "SESSION":
if(session_id() == '' || !isset($_SESSION)) {
session_start();
}
$_SESSION['cache']['strings'] = $_LANG;
break;
case "MEMCACHED":
if (self::$memcached === null) {
throw new \Exception("lang: Memcached instance not configured. Call Lang::setMemcached() first.");
}
self::$memcached->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);
}
}