diff --git a/contracts/Debot/Localization/LanguageStorage.sol b/contracts/Debot/Localization/LanguageStorage.sol new file mode 100644 index 0000000..a15c836 --- /dev/null +++ b/contracts/Debot/Localization/LanguageStorage.sol @@ -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); + } +} \ No newline at end of file diff --git a/contracts/Debot/Localization/MultiLanguage.sol b/contracts/Debot/Localization/MultiLanguage.sol new file mode 100644 index 0000000..d3331ba --- /dev/null +++ b/contracts/Debot/Localization/MultiLanguage.sol @@ -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); + _; + } +} \ No newline at end of file diff --git a/contracts/Debot/Localization/README.MD b/contracts/Debot/Localization/README.MD new file mode 100644 index 0000000..215fe4c --- /dev/null +++ b/contracts/Debot/Localization/README.MD @@ -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 call setEditorPubkey '{"newEditorPubkey": "your_pubkey>"}' --abi --sign +``` +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 call addStrings '{"ids": [], "str": []}' --abi --sign +``` +* Replace strings. + ```bash + tonos-cli --url call replaceStrings '{"ids": [], "str": []}' --abi --sign + ``` +* Remove strings. + ```bash + tonos-cli --url call removeStrings '{"ids": []}' --abi --sign + ``` +5. Set `Language` contract address to `Debot`. +```bash +tonos-cli --url call addLanguage '{"iso": "", "languageStorage": ""}' --abi --sign +``` +* Remove `Language` contract address from `Debot`. + ```bash + tonos-cli --url call removeLanguage '{"iso": ""}' --abi --sign + ``` +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) {} +``` \ No newline at end of file diff --git a/contracts/Debot/Localization/interfaces/ILanguageStorage.sol b/contracts/Debot/Localization/interfaces/ILanguageStorage.sol new file mode 100644 index 0000000..846d49a --- /dev/null +++ b/contracts/Debot/Localization/interfaces/ILanguageStorage.sol @@ -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); +} \ No newline at end of file