First Commit
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
/netbeans
|
||||
/.idea
|
||||
/nbproject
|
||||
@@ -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');
|
||||
```
|
||||
@@ -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";
|
||||
Executable
+18
@@ -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";
|
||||
});
|
||||
Executable
+18
@@ -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";
|
||||
});
|
||||
Executable
+13
@@ -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);
|
||||
}
|
||||
}
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
include_once 'vendor/autoload.php';
|
||||
|
||||
use \Lang\Lang;
|
||||
|
||||
Lang::buildStrings("app/");
|
||||
|
||||
Lang::getString("module_name", "submodule_a");
|
||||
@@ -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/"
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+45
@@ -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"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
include '../bootstrap.php';
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user