Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions contracts/Debot/Localization/LanguageStorage.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
pragma ton-solidity = 0.58.1;

import "../interfaces/ILanguageStorage.sol";
import "../access/OwnableExternal.sol";

library LanguageStorageExitCodes {
uint16 constant WRONG_INPUT_DATA = 130;
uint16 constant STRING_ALREADY_EXIST = 131;
uint16 constant STRING_NOT_EXIST = 132;
uint16 constant NULL_STRINGS = 133;
}

abstract contract LanguageStorage is ILanguageStorage, OwnableExternal {

// Used ISO 639‑1 code
string iso;

mapping(uint16=>string) m_strings;

constructor(string newIso, uint256 ownerPubkey) OwnableExternal(ownerPubkey) public {
iso = newIso;
}

function addStrings(uint16[] ids, string[] str) external virtual onlyOwner {
require(ids.length == str.length, LanguageStorageExitCodes.WRONG_INPUT_DATA);
tvm.accept();
for(uint16 i = 0; i < ids.length; i++) {
if(!m_strings.exists(ids[i])) {
m_strings[ids[i]] = str[i];
}
else {
revert(LanguageStorageExitCodes.STRING_ALREADY_EXIST);
}
}
}

function replaceStrings(uint16[] ids, string[] str) external virtual onlyOwner {
require(ids.length == str.length, LanguageStorageExitCodes.WRONG_INPUT_DATA);
tvm.accept();
for(uint16 i = 0; i < ids.length; i++) {
if(m_strings.exists(ids[i])) {
m_strings[ids[i]] = str[i];
}
else {
revert(LanguageStorageExitCodes.STRING_NOT_EXIST);
}
}
}

function removeStrings(uint16[] ids) external virtual onlyOwner {
tvm.accept();
for(uint16 i = 0; i < ids.length; i++) {
if(m_strings.exists(ids[i])) {
delete m_strings[ids[i]];
}
else {
revert(LanguageStorageExitCodes.STRING_NOT_EXIST);
}
}
}

function checkNull() internal virtual view returns(bool nullStrings) {
for((uint16 id, string str) : m_strings) {
if(str.empty()) {
nullStrings = true;
}
}
}

function getStrings(uint16[] ids) external virtual override view responsible returns(mapping(uint16=>string) data) {
if(!checkNull()) {
for(uint16 id : ids) {
if(m_strings.exists(id)) {
data[id] = m_strings[id];
}
else {
revert(LanguageStorageExitCodes.STRING_NOT_EXIST);
}
}
}
else {
revert(LanguageStorageExitCodes.NULL_STRINGS);
}
return{value: 0, flag: 64}(data);
}

function getLanguageInfo() external override view responsible returns(string _iso, mapping(uint16=>string) _strings) {
return{value: 0, flag:64}(iso, m_strings);
}
}
80 changes: 80 additions & 0 deletions contracts/Debot/Localization/MultiLanguage.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
pragma ton-solidity = 0.58.1;

import "../interfaces/ILanguageStorage.sol";

interface MultiLanguageEvents {
event NewLanguage(string iso, address languageStorage);
event RemoveLanguage(string iso, address languageStorage);
}

// Used ISO 639‑1
abstract contract MultiLanguage is MultiLanguageEvents {

uint256 public editorPubkey;
// Ex. ("EN" => language contract)
mapping(string=>address) m_languages;
// Ex. (tvm.hash("EN") => "EN")
mapping(uint256=>string) public m_iso;
// Lexicographically sorted iso
string[] public sortedIso;

constructor() public {
require(msg.pubkey() == tvm.pubkey() && tvm.pubkey() != 0, 100);
editorPubkey = tvm.pubkey();
}

function addLanguage(string iso, address languageStorage) external virtual onlyEditor {
tvm.accept();
emit MultiLanguageEvents.NewLanguage(iso, languageStorage);
m_languages[iso] = languageStorage;
m_iso[tvm.hash(iso)] = iso;
_sort();
}

function removeLanguage(string iso) external virtual onlyEditor {
tvm.accept();
emit MultiLanguageEvents.RemoveLanguage(iso, m_languages[iso]);
delete m_languages[iso];
delete m_iso[tvm.hash(iso)];
_sort();
}

function _sort() internal {
delete sortedIso;
for((uint256 isoHash, ) : m_iso) {
sortedIso.push(m_iso[isoHash]);
}
}

function _loadLanguageData(uint32 callbackFunctionId, uint32 callbackErrorFunctionId, string iso, uint16[] ids) internal virtual view {
if(m_languages.exists(iso)) {
optional(uint256) none;
ILanguageStorage(m_languages[iso]).getStrings{
sign: false,
pubkey: none,
time: uint64(now),
expire: 0,
callbackId: callbackFunctionId,
onErrorId: callbackErrorFunctionId
}(ids).extMsg;
}
}

function setEditorPubkey(uint256 newEditorPubkey) external virtual onlyEditor {
tvm.accept();
editorPubkey = newEditorPubkey;
}


function getLanguages() external view responsible returns(string[] iso, address[] languageStorage){
for((uint256 isoHash, ) : m_iso) {
languageStorage.push(m_languages[m_iso[isoHash]]);
}
return{value: 0, flag:64}(sortedIso, languageStorage);
}

modifier onlyEditor() {
require(msg.pubkey() == editorPubkey);
_;
}
}
87 changes: 87 additions & 0 deletions contracts/Debot/Localization/README.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Multi-language Debot

## How to use

> TIP: You can use sample from [everscale-tip-samples](https://github.com/itgoldio/everscale-tip-samples/tree/main/demo)

1. Create & Inherit the `MultiLanguage.sol` in your `Debot` contract.
```sol
import "MultiLanguage.sol";

contract Debot is MultiLanguage {

constructor() MultiLanguage() {
tvm.accept();
}

}
```
2. Set the `editorKey` for add and remove languages.
> Default value is tvm.pubkey().
```bash
tonos-cli --url <url_endpoint> call <debot_address> setEditorPubkey '{"newEditorPubkey": "your_pubkey>"}' --abi <abi_path> --sign <keys_path>
```
3. Create & Deploy language contract. Inherit the `LanguageStorage.sol` in your new contract.
* `string newIso` - Language code in ISO 639‑1.
* `uint256 ownerPubkey` - Owner pubkey for this contract.
> Use different keys for all language contracts, it is safe and serves to generate other addresses.
```sol
import "LanguageStorage.sol";

contract Language is LanguageStorage {

constructor(string newIso, uint256 ownerPubkey) LanguageStorage(newIso, ownerPubkey) {
tvm.accept();
}

}
```
4. Add strings to `Language` contract.
```bash
tonos-cli --url <url_endpoint> call <language_address> addStrings '{"ids": [<array_of_id>], "str": [<array_of_strings>]}' --abi <abi_path> --sign <owner_keys_path>
```
* Replace strings.
```bash
tonos-cli --url <url_endpoint> call <language_address> replaceStrings '{"ids": [<array_of_id>], "str": [<array_of_strings>]}' --abi <abi_path> --sign <owner_keys_path>
```
* Remove strings.
```bash
tonos-cli --url <url_endpoint> call <language_address> removeStrings '{"ids": [<array_of_id>]}' --abi <abi_path> --sign <owner_keys_path>
```
5. Set `Language` contract address to `Debot`.
```bash
tonos-cli --url <url_endpoint> call <debot_address> addLanguage '{"iso": "<iso_code>", "languageStorage": "<language_address>"}' --abi <abi_path> --sign <editor_keys_path>
```
* Remove `Language` contract address from `Debot`.
```bash
tonos-cli --url <url_endpoint> call <debot_address> removeLanguage '{"iso": "<iso_code>"}' --abi <abi_path> --sign <editor_keys_path>
```
6. Read data from `Language` contract in `Debot`.
```sol
// Example

library StringCodes {
uint16 constant FIRST_STRING_ID = 0;
uint16 constant SECOND_STRING_ID = 1;
...
}

...
_loadLanguageData(
tvm.functionId(onSuccessDataLoad),
tvm.functionId(onErrorDataLoad),
"EN",
[
StringCodes.FIRST_STRING_ID,
StringCodes.SECOND_STRING_ID
...
]
);

function onSuccessDataLoad(mapping(uint16=>string) data) {
string first = data[StringCodes.FIRST_STRING_ID],
string second = data[StringCodes.SECOND_STRING_ID],
...
}
function onErrorDataLoad(uint32 sdkError, uint32 exitCode) {}
```
6 changes: 6 additions & 0 deletions contracts/Debot/Localization/interfaces/ILanguageStorage.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pragma ton-solidity = 0.58.1;

interface ILanguageStorage {
function getStrings(uint16[] ids) external view responsible returns(mapping(uint16=>string) data);
function getLanguageInfo() external view responsible returns(string iso, mapping(uint16=>string) strings);
}