搞区块链开发,合约要是只能说一种语言,那用户不得抓瞎?全球玩家一堆,英语、中文、俄文、韩文全得招呼上!Solidity里实现多语言支持,听起来高大上,其实就是让合约动态吐出不同语言的提示、错误信息,或者接口描述,服务全球用户。多语言支持的核心套路Solidity里搞多语言支持,核心是把不同语言的
搞区块链开发,合约要是只能说一种语言,那用户不得抓瞎?全球玩家一堆,英语、中文、俄文、韩文全得招呼上!Solidity里实现多语言支持,听起来高大上,其实就是让合约动态吐出不同语言的提示、错误信息,或者接口描述,服务全球用户。
Solidity里搞多语言支持,核心是把不同语言的字符串存下来,根据用户选择的语言代码(比如en、zh)动态返回对应文本。EVM没内置国际化库,字符串得存进存储(mapping或数组),用bytes32或string存文本。bytes32省Gas但长度有限(32字节,英文OK,中文吃力),string灵活但存储成本高。语言切换靠用户传入语言代码,合约查mapping返回对应文本。错误信息、事件描述、甚至前端显示的提示都能多语言化。
关键点:存储设计得高效,SSTORE/SLOAD成本高,尽量用bytes32或静态数组。语言代码用ISO 639-1标准(如en、zh),映射到文本。权限控制得加,防止恶意修改语言数据。可升级合约支持动态添加语言。事件记录语言切换,方便链上查。
Gas上,存储string单条20k Gas,10k。读取~2k。批量初始化多语言数据得控制规模,防Gas超限。拿ERC20合约举例:bytes32transfer的错误信息可以动态返回Insufficient balance(英语)或余额不足(中文)。
用Hardhat建环境,写合约和测试,测Gas成本。
跑命令初始化:
mkdir i18n-demo
cd i18n-demo
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox @openzeppelin/contracts @openzeppelin/contracts-upgradeable
npm install ethers
npx hardhat init
选TypeScript,装依赖:
npm install --save-dev ts-node typescript @types/node @types/mocha
目录结构:
i18n-demo/
├── contracts/
│ ├── BasicI18n.sol
│ ├── MappingI18n.sol
│ ├── DynamicI18n.sol
│ ├── UpgradableI18n.sol
├── scripts/
│ ├── deploy.ts
├── test/
│ ├── I18n.test.ts
├── hardhat.config.ts
├── tsconfig.json
├── package.json
tsconfig.json配置:
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"strict": true,
"esModuleInterop": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"outDir": "./dist",
"rootDir": "./"
},
"include": ["hardhat.config.ts", "scripts", "test"]
}
hardhat.config.ts配置:
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
const config: HardhatUserConfig = {
solidity: {
version: "0.8.20",
settings: {
optimizer: {
enabled: true,
runs: 200
}
}
},
networks: {
hardhat: {
chainId: 1337,
},
},
};
export default config;
启动节点:
npx hardhat node
环境OK,直接上代码!
先搞个简单版,用数组存多语言错误信息,语言固定。
contracts/BasicI18n.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract BasicI18nToken is ERC20, Ownable {
string[3] public languages = ["en", "zh", "es"];
mapping(string => mapping(string => string)) public messages;
event LanguageSet(address indexed user, string language);
event TransferFailed(address indexed from, address indexed to, uint256 amount, string message);
constructor() ERC20("BasicI18nToken", "BIT") Ownable() {
_mint(msg.sender, 1000000 * 10**decimals());
// English
messages["en"]["insufficient_balance"] = "Insufficient balance";
messages["en"]["invalid_recipient"] = "Invalid recipient";
// Chinese
messages["zh"]["insufficient_balance"] = "\xe4\xbd\x99\xe9\xa2\x9d\xe4\xb8\x8d\xe8\xb6\xb3"; // 余额不足
messages["zh"]["invalid_recipient"] = "\xe6\x97\xa0\xe6\x95\x88\xe6\x8e\xa5\xe6\x94\xb6\xe8\x80\x85"; // 无效接收者
// Spanish
messages["es"]["insufficient_balance"] = "Saldo insuficiente";
messages["es"]["invalid_recipient"] = "Destinatario invalido";
}
function setLanguage(string memory language) public {
bool valid = false;
for (uint256 i = 0; i < languages.length; i++) {
if (keccak256(abi.encodePacked(languages[i])) == keccak256(abi.encodePacked(language))) {
valid = true;
break;
}
}
require(valid, "Unsupported language");
emit LanguageSet(msg.sender, language);
}
function transfer(address to, uint256 amount, string memory language) public returns (bool) {
if (to == address(0)) {
emit TransferFailed(msg.sender, to, amount, messages[language]["invalid_recipient"]);
revert(messages[language]["invalid_recipient"]);
}
if (balanceOf(msg.sender) < amount) {
emit TransferFailed(msg.sender, to, amount, messages[language]["insufficient_balance"]);
revert(messages[language]["insufficient_balance"]);
}
return super.transfer(to, amount);
}
}
languages数组存支持的语言代码(en, zh, es)。messages映射:language -> key -> message。setLanguage:验证语言代码,触发事件。transfer:根据语言返回对应错误信息。string存储支持任意语言。string~20k Gas)。string存储)。setLanguage:~5k Gas。transfer失败:~10k Gas(含事件)。test/I18n.test.ts:
import { ethers } from "hardhat";
import { expect } from "chai";
import { BasicI18nToken } from "../typechain-types";
describe("BasicI18n", function () {
let token: BasicI18nToken;
let owner: any, user1: any;
beforeEach(async function () {
[owner, user1] = await ethers.getSigners();
const TokenFactory = await ethers.getContractFactory("BasicI18nToken");
token = await TokenFactory.deploy();
await token.deployed();
});
it("sets valid language", async function () {
await expect(token.setLanguage("en")).to.emit(token, "LanguageSet").withArgs(owner.address, "en");
await expect(token.setLanguage("zh")).to.emit(token, "LanguageSet").withArgs(owner.address, "zh");
});
it("reverts on invalid language", async function () {
await expect(token.setLanguage("fr")).to.be.revertedWith("Unsupported language");
});
it("returns language-specific errors", async function () {
await expect(token.transfer(ethers.constants.AddressZero, ethers.utils.parseEther("500"), "en"))
.to.be.revertedWith("Invalid recipient");
await expect(token.transfer(ethers.constants.AddressZero, ethers.utils.parseEther("500"), "zh"))
.to.be.revertedWith("\xe6\x97\xa0\xe6\x95\x88\xe6\x8e\xa5\xe6\x94\xb6\xe8\x80\x85");
await token.transfer(user1.address, ethers.utils.parseEther("1000"), "en");
await expect(token.connect(user1).transfer(owner.address, ethers.utils.parseEther("2000"), "es"))
.to.be.revertedWith("Saldo insuficiente");
});
});
跑测试:
npx hardhat test
静态数组不灵活,搞个动态映射,支持随时加语言。
contracts/MappingI18n.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MappingI18nToken is ERC20, Ownable {
mapping(string => bool) public supportedLanguages;
mapping(string => mapping(string => bytes32)) public messages;
event LanguageAdded(string language);
event MessageUpdated(string language, string key, bytes32 message);
event LanguageSet(address indexed user, string language);
event TransferFailed(address indexed from, address indexed to, uint256 amount, bytes32 message);
constructor() ERC20("MappingI18nToken", "MIT") Ownable() {
_mint(msg.sender, 1000000 * 10**decimals());
supportedLanguages["en"] = true;
supportedLanguages["zh"] = true;
messages["en"]["insufficient_balance"] = bytes32("Insufficient balance");
messages["en"]["invalid_recipient"] = bytes32("Invalid recipient");
messages["zh"]["insufficient_balance"] = bytes32("\xe4\xbd\x99\xe9\xa2\x9d\xe4\xb8\x8d\xe8\xb6\xb3");
messages["zh"]["invalid_recipient"] = bytes32("\xe6\x97\xa0\xe6\x95\x88\xe6\x8e\xa5\xe6\x94\xb6\xe8\x80\x85");
}
function addLanguage(string memory language) public onlyOwner {
require(!supportedLanguages[language], "Language exists");
supportedLanguages[language] = true;
emit LanguageAdded(language);
}
function updateMessage(string memory language, string memory key, bytes32 message) public onlyOwner {
require(supportedLanguages[language], "Unsupported language");
messages[language][key] = message;
emit MessageUpdated(language, key, message);
}
function setLanguage(string memory language) public {
require(supportedLanguages[language], "Unsupported language");
emit LanguageSet(msg.sender, language);
}
function transfer(address to, uint256 amount, string memory language) public returns (bool) {
require(supportedLanguages[language], "Unsupported language");
if (to == address(0)) {
emit TransferFailed(msg.sender, to, amount, messages[language]["invalid_recipient"]);
revert(string(abi.encodePacked(messages[language]["invalid_recipient"])));
}
if (balanceOf(msg.sender) < amount) {
emit TransferFailed(msg.sender, to, amount, messages[language]["insufficient_balance"]);
revert(string(abi.encodePacked(messages[language]["insufficient_balance"])));
}
return super.transfer(to, amount);
}
}
supportedLanguages:映射标记支持语言。messages:用bytes32存消息,省Gas。addLanguage:动态加语言,onlyOwner。updateMessage:更新语言消息。transfer:返回bytes32转string的错误信息。onlyOwner控制语言和消息更新。bytes32限制长度,防溢出。bytes32不支持长文本。addLanguage:~20k Gas。updateMessage:~15k Gas。transfer失败:~8k Gas。test/I18n.test.ts(add):
import { MappingI18nToken } from "../typechain-types";
describe("MappingI18n", function () {
let token: MappingI18nToken;
let owner: any, user1: any;
beforeEach(async function () {
[owner, user1] = await ethers.getSigners();
const TokenFactory = await ethers.getContractFactory("MappingI18nToken");
token = await TokenFactory.deploy();
await token.deployed();
});
it("adds new language", async function () {
await expect(token.addLanguage("es"))
.to.emit(token, "LanguageAdded")
.withArgs("es");
await token.updateMessage("es", "insufficient_balance", ethers.utils.formatBytes32String("Saldo insuficiente"));
await expect(token.connect(user1).transfer(owner.address, ethers.utils.parseEther("2000"), "es"))
.to.be.revertedWith("Saldo insuficiente");
});
it("updates message", async function () {
await expect(token.updateMessage("en", "insufficient_balance", ethers.utils.formatBytes32String("Not enough balance")))
.to.emit(token, "MessageUpdated")
.withArgs("en", "insufficient_balance", ethers.utils.formatBytes32String("Not enough balance"));
await expect(token.connect(user1).transfer(owner.address, ethers.utils.parseEther("2000"), "en"))
.to.be.revertedWith("Not enough balance");
});
it("blocks invalid language", async function () {
await expect(token.transfer(owner.address, ethers.utils.parseEther("500"), "fr"))
.to.be.revertedWith("Unsupported language");
});
});
让用户动态切换语言,存用户偏好。
contracts/DynamicI18n.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract DynamicI18nToken is ERC20, Ownable, ReentrancyGuard {
mapping(string => bool) public supportedLanguages;
mapping(string => mapping(string => bytes32)) public messages;
mapping(address => string) public userLanguages;
string public defaultLanguage = "en";
event LanguageAdded(string language);
event MessageUpdated(string language, string key, bytes32 message);
event UserLanguageSet(address indexed user, string language);
event TransferFailed(address indexed from, address indexed to, uint256 amount, bytes32 message);
constructor() ERC20("DynamicI18nToken", "DIT") Ownable() {
_mint(msg.sender, 1000000 * 10**decimals());
supportedLanguages["en"] = true;
supportedLanguages["zh"] = true;
messages["en"]["insufficient_balance"] = bytes32("Insufficient balance");
messages["en"]["invalid_recipient"] = bytes32("Invalid recipient");
messages["zh"]["insufficient_balance"] = bytes32("\xe4\xbd\x99\xe9\xa2\x9d\xe4\xb8\x8d\xe8\xb6\xb3");
messages["zh"]["invalid_recipient"] = bytes32("\xe6\x97\xa0\xe6\x95\x88\xe6\x8e\xa5\xe6\x94\xb6\xe8\x80\x85");
}
function addLanguage(string memory language) public onlyOwner {
require(!supportedLanguages[language], "Language exists");
supportedLanguages[language] = true;
emit LanguageAdded(language);
}
function updateMessage(string memory language, string memory key, bytes32 message) public onlyOwner {
require(supportedLanguages[language], "Unsupported language");
messages[language][key] = message;
emit MessageUpdated(language, key, message);
}
function setUserLanguage(string memory language) public nonReentrant {
require(supportedLanguages[language], "Unsupported language");
userLanguages[msg.sender] = language;
emit UserLanguageSet(msg.sender, language);
}
function transfer(address to, uint256 amount) public nonReentrant returns (bool) {
string memory language = bytes(userLanguages[msg.sender]).length == 0 ? defaultLanguage : userLanguages[msg.sender];
if (to == address(0)) {
emit TransferFailed(msg.sender, to, amount, messages[language]["invalid_recipient"]);
revert(string(abi.encodePacked(messages[language]["invalid_recipient"])));
}
if (balanceOf(msg.sender) < amount) {
emit TransferFailed(msg.sender, to, amount, messages[language]["insufficient_balance"]);
revert(string(abi.encodePacked(messages[language]["insufficient_balance"])));
}
return super.transfer(to, amount);
}
}
userLanguages:存用户语言偏好。setUserLanguage:用户设置语言,防重入。transfer:优先用用户语言,无则用默认en。bytes32存消息,省Gas。nonReentrant防重入。bytes32长度限制。setUserLanguage:~25k Gas。transfer失败:~10k Gas。test/I18n.test.ts(add):
import { DynamicI18nToken } from "../typechain-types";
describe("DynamicI18n", function () {
let token: DynamicI18nToken;
let owner: any, user1: any;
beforeEach(async function () {
[owner, user1] = await ethers.getSigners();
const TokenFactory = await ethers.getContractFactory("DynamicI18nToken");
token = await TokenFactory.deploy();
await token.deployed();
});
it("sets user language", async function () {
await expect(token.connect(user1).setUserLanguage("zh"))
.to.emit(token, "UserLanguageSet")
.withArgs(user1.address, "zh");
await expect(token.connect(user1).transfer(owner.address, ethers.utils.parseEther("2000")))
.to.be.revertedWith("\xe4\xbd\x99\xe9\xa2\x9d\xe4\xb8\x8d\xe8\xb6\xb3");
});
it("uses default language", async function () {
await expect(token.connect(user1).transfer(owner.address, ethers.utils.parseEther("2000")))
.to.be.revertedWith("Insufficient balance");
});
it("blocks invalid user language", async function () {
await expect(token.connect(user1).setUserLanguage("fr"))
.to.be.revertedWith("Unsupported language");
});
});
en生效。语言支持得能扩展,搞可升级合约,动态加语言。
contracts/UpgradableI18n.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
contract UpgradableI18nToken is ERC20Upgradeable, OwnableUpgradeable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
mapping(string => bool) public supportedLanguages;
mapping(string => mapping(string => bytes32)) public messages;
mapping(address => string) public userLanguages;
string public defaultLanguage;
event LanguageAdded(string language);
event MessageUpdated(string language, string key, bytes32 message);
event UserLanguageSet(address indexed user, string language);
event TransferFailed(address indexed from, address indexed to, uint256 amount, bytes32 message);
function initialize() public initializer {
__ERC20_init("UpgradableI18nToken", "UIT");
__Ownable_init();
__UUPSUpgradeable_init();
__ReentrancyGuard_init();
_mint(msg.sender, 1000000 * 10**decimals());
supportedLanguages["en"] = true;
supportedLanguages["zh"] = true;
defaultLanguage = "en";
messages["en"]["insufficient_balance"] = bytes32("Insufficient balance");
messages["en"]["invalid_recipient"] = bytes32("Invalid recipient");
messages["zh"]["insufficient_balance"] = bytes32("\xe4\xbd\x99\xe9\xa2\x9d\xe4\xb8\x8d\xe8\xb6\xb3");
messages["zh"]["invalid_recipient"] = bytes32("\xe6\x97\xa0\xe6\x95\x88\xe6\x8e\xa5\xe6\x94\xb6\xe8\x80\x85");
}
function addLanguage(string memory language) public onlyOwner {
require(!supportedLanguages[language], "Language exists");
supportedLanguages[language] = true;
emit LanguageAdded(language);
}
function updateMessage(string memory language, string memory key, bytes32 message) public onlyOwner {
require(supportedLanguages[language], "Unsupported language");
messages[language][key] = message;
emit MessageUpdated(language, key, message);
}
function setUserLanguage(string memory language) public nonReentrant {
require(supportedLanguages[language], "Unsupported language");
userLanguages[msg.sender] = language;
emit UserLanguageSet(msg.sender, language);
}
function transfer(address to, uint256 amount) public nonReentrant returns (bool) {
string memory language = bytes(userLanguages[msg.sender]).length == 0 ? defaultLanguage : userLanguages[msg.sender];
if (to == address(0)) {
emit TransferFailed(msg.sender, to, amount, messages[language]["invalid_recipient"]);
revert(string(abi.encodePacked(messages[language]["invalid_recipient"])));
}
if (balanceOf(msg.sender) < amount) {
emit TransferFailed(msg.sender, to, amount, messages[language]["insufficient_balance"]);
revert(string(abi.encodePacked(messages[language]["insufficient_balance"])));
}
return super.transfer(to, amount);
}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
}
contracts/UpgradableI18nV2.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
contract UpgradableI18nTokenV2 is ERC20Upgradeable, OwnableUpgradeable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
mapping(string => bool) public supportedLanguages;
mapping(string => mapping(string => bytes32)) public messages;
mapping(address => string) public userLanguages;
string public defaultLanguage;
uint256 public languageFee; // Fee for setting language
event LanguageAdded(string language);
event MessageUpdated(string language, string key, bytes32 message);
event UserLanguageSet(address indexed user, string language);
event TransferFailed(address indexed from, address indexed to, uint256 amount, bytes32 message);
function initialize() public initializer {
__ERC20_init("UpgradableI18nTokenV2", "UITV2");
__Ownable_init();
__UUPSUpgradeable_init();
__ReentrancyGuard_init();
_mint(msg.sender, 1000000 * 10**decimals());
supportedLanguages["en"] = true;
supportedLanguages["zh"] = true;
defaultLanguage = "en";
languageFee = 1 * 10**15; // 0.001 token fee
messages["en"]["insufficient_balance"] = bytes32("Insufficient balance");
messages["en"]["invalid_recipient"] = bytes32("Invalid recipient");
messages["zh"]["insufficient_balance"] = bytes32("\xe4\xbd\x99\xe9\xa2\x9d\xe4\xb8\x8d\xe8\xb6\xb3");
messages["zh"]["invalid_recipient"] = bytes32("\xe6\x97\xa0\xe6\x95\x88\xe6\x8e\xa5\xe6\x94\xb6\xe8\x80\x85");
}
function addLanguage(string memory language) public onlyOwner {
require(!supportedLanguages[language], "Language exists");
supportedLanguages[language] = true;
emit LanguageAdded(language);
}
function updateMessage(string memory language, string memory key, bytes32 message) public onlyOwner {
require(supportedLanguages[language], "Unsupported language");
messages[language][key] = message;
emit MessageUpdated(language, key, message);
}
function setUserLanguage(string memory language) public nonReentrant {
require(supportedLanguages[language], "Unsupported language");
require(balanceOf(msg.sender) >= languageFee, "Insufficient balance for fee");
_burn(msg.sender, languageFee);
userLanguages[msg.sender] = language;
emit UserLanguageSet(msg.sender, language);
}
function transfer(address to, uint256 amount) public nonReentrant returns (bool) {
string memory language = bytes(userLanguages[msg.sender]).length == 0 ? defaultLanguage : userLanguages[msg.sender];
if (to == address(0)) {
emit TransferFailed(msg.sender, to, amount, messages[language]["invalid_recipient"]);
revert(string(abi.encodePacked(messages[language]["invalid_recipient"])));
}
if (balanceOf(msg.sender) < amount) {
emit TransferFailed(msg.sender, to, amount, messages[language]["insufficient_balance"]);
revert(string(abi.encodePacked(messages[language]["insufficient_balance"])));
}
return super.transfer(to, amount);
}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
}
contracts/UUPSProxy.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
contract UUPSProxy is ERC1967Proxy {
constructor(address logic, bytes memory data) ERC1967Proxy(logic, data) {}
}
UpgradableI18nToken:UUPS代理,支持多语言。initialize:初始化代币、语言、消息。setUserLanguage:用户设置语言,V2加费用。UpgradableI18nTokenV2:新增languageFee,保持存储布局。nonReentrant防重入。setUserLanguage:~30k Gas(含费)。transfer失败:~10k Gas。test/I18n.test.ts(add):
import { UUPSProxy, UpgradableI18nToken, UpgradableI18nTokenV2 } from "../typechain-types";
describe("UpgradableI18n", function () {
let proxy: UUPSProxy;
let token: UpgradableI18nToken;
let tokenV2: UpgradableI18nTokenV2;
let owner: any, user1: any;
beforeEach(async function () {
[owner, user1] = await ethers.getSigners();
const TokenFactory = await ethers.getContractFactory("UpgradableI18nToken");
token = await TokenFactory.deploy();
await token.deployed();
const ProxyFactory = await ethers.getContractFactory("UUPSProxy");
const initData = TokenFactory.interface.encodeFunctionData("initialize");
proxy = await ProxyFactory.deploy(token.address, initData);
await proxy.deployed();
const TokenV2Factory = await ethers.getContractFactory("UpgradableI18nTokenV2");
tokenV2 = await TokenV2Factory.deploy();
await tokenV2.deployed();
});
it("sets user language", async function () {
const proxyAsToken = await ethers.getContractFactory("UpgradableI18nToken").then(f => f.attach(proxy.address));
await proxyAsToken.connect(user1).setUserLanguage("zh");
await expect(proxyAsToken.connect(user1).transfer(owner.address, ethers.utils.parseEther("2000")))
.to.be.revertedWith("\xe4\xbd\x99\xe9\xa2\x9d\xe4\xb8\x8d\xe8\xb6\xb3");
});
it("upgrades and applies language fee", async function () {
const proxyAsToken = await ethers.getContractFactory("UpgradableI18nToken").then(f => f.attach(proxy.address));
await proxyAsToken.upgradeTo(tokenV2.address);
const proxyAsTokenV2 = await ethers.getContractFactory("UpgradableI18nTokenV2").then(f => f.attach(proxy.address));
await proxyAsTokenV2.transfer(user1.address, ethers.utils.parseEther("1000"));
await expect(proxyAsTokenV2.connect(user1).setUserLanguage("zh"))
.to.emit(proxyAsTokenV2, "UserLanguageSet")
.withArgs(user1.address, "zh");
expect(await proxyAsTokenV2.balanceOf(user1.address)).to.equal(ethers.utils.parseEther("999"));
});
});
scripts/deploy.ts:
import { ethers } from "hardhat";
async function main() {
const [owner] = await ethers.getSigners();
const BasicI18nFactory = await ethers.getContractFactory("BasicI18nToken");
const basicI18n = await BasicI18nFactory.deploy();
await basicI18n.deployed();
console.log(`BasicI18nToken deployed to: ${basicI18n.address}`);
const MappingI18nFactory = await ethers.getContractFactory("MappingI18nToken");
const mappingI18n = await MappingI18nFactory.deploy();
await mappingI18n.deployed();
console.log(`MappingI18nToken deployed to: ${mappingI18n.address}`);
const DynamicI18nFactory = await ethers.getContractFactory("DynamicI18nToken");
const dynamicI18n = await DynamicI18nFactory.deploy();
await dynamicI18n.deployed();
console.log(`DynamicI18nToken deployed to: ${dynamicI18n.address}`);
const UpgradableI18nFactory = await ethers.getContractFactory("UpgradableI18nToken");
const upgradableI18n = await UpgradableI18nFactory.deploy();
await upgradableI18n.deployed();
const initData = UpgradableI18nFactory.interface.encodeFunctionData("initialize");
const ProxyFactory = await ethers.getContractFactory("UUPSProxy");
const proxy = await ProxyFactory.deploy(upgradableI18n.address, initData);
await proxy.deployed();
console.log(`UpgradableI18nToken deployed to: ${upgradableI18n.address}, Proxy: ${proxy.address}`);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
跑部署:
npx hardhat run scripts/deploy.ts --network hardhat
bytes32省Gas,string灵活。onlyOwner锁语言和消息更新。如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!