@@ -84,7 +97,7 @@ export const BuilderChallenges = ({
target="_blank"
rel="noopener noreferrer"
>
- Code
+
@@ -94,7 +107,7 @@ export const BuilderChallenges = ({
target="_blank"
rel="noopener noreferrer"
>
- Demo
+
|
@@ -130,19 +143,25 @@ export const BuilderChallenges = ({
{isMyProfile ? (
- Start a new challenge
+
- Show off your skills. Learn everything you need to build on Ethereum!
+
) : (
- This builder hasn't completed any challenges.
+
)}
diff --git a/packages/react-app/src/components/builder/BuilderProfileCard.jsx b/packages/react-app/src/components/builder/BuilderProfileCard.jsx
index 9537cc82..c63c0e05 100644
--- a/packages/react-app/src/components/builder/BuilderProfileCard.jsx
+++ b/packages/react-app/src/components/builder/BuilderProfileCard.jsx
@@ -28,6 +28,7 @@ import {
useClipboard,
} from "@chakra-ui/react";
import { CopyIcon, QuestionOutlineIcon } from "@chakra-ui/icons";
+import { FormattedMessage, useIntl } from "react-intl";
import QRPunkBlockie from "../QrPunkBlockie";
import SocialLink from "../SocialLink";
import useDisplayAddress from "../../hooks/useDisplayAddress";
@@ -56,6 +57,7 @@ const BuilderProfileCard = ({ builder, mainnetProvider, isMyProfile, userProvide
const { isOpen, onOpen, onClose } = useDisclosure();
const { hasCopied, onCopy } = useClipboard(builder?.id);
const { borderColor, secondaryFontColor } = useCustomColorModes();
+ const intl = useIntl();
const shortAddress = ellipsizedAddress(builder?.id);
const hasEns = ens !== shortAddress;
@@ -85,9 +87,15 @@ const BuilderProfileCard = ({ builder, mainnetProvider, isMyProfile, userProvide
const invalidSocials = validateSocials(socialLinkCleaned);
if (invalidSocials.length !== 0) {
toast({
- description: `The usernames for the following socials are not correct: ${invalidSocials
- .map(([social]) => social)
- .join(", ")}`,
+ description: intl.formatMessage(
+ {
+ id: "builderProfileCard.error.invalid-socials",
+ defaultMessage: "The usernames for the following socials are not correct: {invalidSocials}",
+ },
+ {
+ invalidSocials: invalidSocials.map(([social]) => social).join(", "),
+ },
+ ),
status: "error",
variant: toastVariant,
});
@@ -100,7 +108,10 @@ const BuilderProfileCard = ({ builder, mainnetProvider, isMyProfile, userProvide
signMessage = await getUpdateSocialsSignMessage(address);
} catch (error) {
toast({
- description: " Sorry, the server is overloaded. π§―ππ₯",
+ description: intl.formatMessage({
+ id: "error.server-overloaded",
+ defaultMessage: "Sorry, the server is overloaded. π§―ππ₯",
+ }),
status: "error",
variant: toastVariant,
});
@@ -113,7 +124,10 @@ const BuilderProfileCard = ({ builder, mainnetProvider, isMyProfile, userProvide
signature = await userProvider.send("personal_sign", [signMessage, address]);
} catch (error) {
toast({
- description: "Couldn't get a signature from the Wallet",
+ description: intl.formatMessage({
+ id: "error.signature-from-wallet",
+ defaultMessage: "Couldn't get a signature from the Wallet",
+ }),
status: "error",
variant: toastVariant,
});
@@ -127,7 +141,10 @@ const BuilderProfileCard = ({ builder, mainnetProvider, isMyProfile, userProvide
if (error.status === 401) {
toast({
status: "error",
- description: "Access error",
+ description: intl.formatMessage({
+ id: "error.access-error",
+ defaultMessage: "Access error",
+ }),
variant: toastVariant,
});
setIsUpdatingSocials(false);
@@ -135,7 +152,10 @@ const BuilderProfileCard = ({ builder, mainnetProvider, isMyProfile, userProvide
}
toast({
status: "error",
- description: "Can't update your socials. Please try again.",
+ description: intl.formatMessage({
+ id: "builderProfileCard.error.updating-socials",
+ defaultMessage: "Can't update your socials. Please try again.",
+ }),
variant: toastVariant,
});
setIsUpdatingSocials(false);
@@ -143,7 +163,10 @@ const BuilderProfileCard = ({ builder, mainnetProvider, isMyProfile, userProvide
}
toast({
- description: "Your social links have been updated",
+ description: intl.formatMessage({
+ id: "builderProfileCard.success.updating-socials",
+ defaultMessage: "Your social links have been updated",
+ }),
status: "success",
variant: toastVariant,
});
@@ -289,8 +312,18 @@ const BuilderProfileCard = ({ builder, mainnetProvider, isMyProfile, userProvide
isMyProfile && (
- You haven't set your socials{" "}
-
+ {" "}
+
+ }
+ >
@@ -299,11 +332,15 @@ const BuilderProfileCard = ({ builder, mainnetProvider, isMyProfile, userProvide
)}
{isMyProfile && (
)}
- Joined {joinedDateDisplay}
+
@@ -312,7 +349,9 @@ const BuilderProfileCard = ({ builder, mainnetProvider, isMyProfile, userProvide
- Update your socials
+
+
+
{Object.entries(socials).map(([socialId, socialData]) => (
@@ -336,7 +375,7 @@ const BuilderProfileCard = ({ builder, mainnetProvider, isMyProfile, userProvide
))}
diff --git a/packages/react-app/src/components/builder/BuilderProfileHeader.jsx b/packages/react-app/src/components/builder/BuilderProfileHeader.jsx
index 4f78cec7..532c0b78 100644
--- a/packages/react-app/src/components/builder/BuilderProfileHeader.jsx
+++ b/packages/react-app/src/components/builder/BuilderProfileHeader.jsx
@@ -1,6 +1,7 @@
import React from "react";
import { Flex, HStack, Tag, Text } from "@chakra-ui/react";
import { InfoOutlineIcon } from "@chakra-ui/icons";
+import { FormattedMessage } from "react-intl";
import { userFunctionDescription } from "../../helpers/constants";
import useCustomColorModes from "../../hooks/useCustomColorModes";
@@ -18,7 +19,7 @@ export const BuilderProfileHeader = ({ acceptedChallenges, builder }) => {
{acceptedChallenges.length}
- challenges completed
+
@@ -37,7 +38,7 @@ export const BuilderProfileHeader = ({ acceptedChallenges, builder }) => {
)}
- Role
+
diff --git a/packages/react-app/src/components/builder/JoinedBuidlGuidlBanner.jsx b/packages/react-app/src/components/builder/JoinedBuidlGuidlBanner.jsx
index 57079db2..c06d5334 100644
--- a/packages/react-app/src/components/builder/JoinedBuidlGuidlBanner.jsx
+++ b/packages/react-app/src/components/builder/JoinedBuidlGuidlBanner.jsx
@@ -1,5 +1,6 @@
import React from "react";
import { Button, Center, Flex, Image, Link, Text, VStack, chakra, useColorModeValue } from "@chakra-ui/react";
+import { FormattedMessage } from "react-intl";
import CrossedSwordsIcon from "../icons/CrossedSwordsIcon";
const BG_FRONTEND_URL = "https://buidlguidl.com";
@@ -36,7 +37,10 @@ export const JoinedBuidlGuidlBanner = ({ builderAddress }) => {
fontWeight="extrabold"
px="20px"
>
- This builder has upgraded to BuidlGuidl
+
diff --git a/packages/react-app/src/data/api.js b/packages/react-app/src/data/api.js
index 2915f16f..bf93e13c 100644
--- a/packages/react-app/src/data/api.js
+++ b/packages/react-app/src/data/api.js
@@ -197,9 +197,9 @@ export const getDraftBuilds = async address => {
}
};
-export const getChallengeReadme = async (challengeId, version) => {
+export const getChallengeReadme = async (challengeId, version, intl) => {
try {
- const response = await axios.get(getGithubChallengeReadmeUrl(challengeId, version));
+ const response = await axios.get(getGithubChallengeReadmeUrl(challengeId, version, intl));
return response.data;
} catch (err) {
console.log("error fetching challenge README", err);
diff --git a/packages/react-app/src/data/challenges.js b/packages/react-app/src/data/challenges.js
index a5217afd..3ea4c9ac 100644
--- a/packages/react-app/src/data/challenges.js
+++ b/packages/react-app/src/data/challenges.js
@@ -1,45 +1,69 @@
-export const challengeInfo = {
+export const getChallengeInfo = intl => ({
"simple-nft-example": {
id: 0,
branchName: "challenge-0-simple-nft",
- label: "π© Challenge 0: π Simple NFT Example",
+ label: intl.formatMessage({
+ id: "challenges.challenge-0-simple-nft.label",
+ defaultMessage: "π© Challenge 0: π Simple NFT Example",
+ }),
disabled: false,
- description:
- "π« Create a simple NFT to learn basics of π scaffold-eth. You'll use π·ββοΈ HardHat to compile and deploy smart contracts. Then, you'll use a template React app full of important Ethereum components and hooks. Finally, you'll deploy an NFT to a public network to share with friends! π",
+ description: intl.formatMessage({
+ id: "challenges.challenge-0-simple-nft.description",
+ defaultMessage:
+ "π« Create a simple NFT to learn basics of π scaffold-eth. You'll use π·ββοΈ HardHat to compile and deploy smart contracts. Then, you'll use a template React app full of important Ethereum components and hooks. Finally, you'll deploy an NFT to a public network to share with friends! π",
+ }),
previewImage: "/assets/challenges/simpleNFT.svg",
dependencies: [],
},
"decentralized-staking": {
id: 1,
branchName: "challenge-1-decentralized-staking",
- label: "π© Challenge 1: π₯© Decentralized Staking App ",
+ label: intl.formatMessage({
+ id: "challenges.challenge-1-decentralized-staking.label",
+ defaultMessage: "π© Challenge 1: π₯© Decentralized Staking App ",
+ }),
disabled: false,
- description:
- "π¦Έ A superpower of Ethereum is allowing you, the builder, to create a simple set of rules that an adversarial group of players can use to work together. In this challenge, you create a decentralized application where users can coordinate a group funding effort. The users only have to trust the code.",
+ description: intl.formatMessage({
+ id: "challenges.challenge-1-decentralized-staking.description",
+ defaultMessage:
+ "π¦Έ A superpower of Ethereum is allowing you, the builder, to create a simple set of rules that an adversarial group of players can use to work together. In this challenge, you create a decentralized application where users can coordinate a group funding effort. The users only have to trust the code.",
+ }),
previewImage: "/assets/challenges/stakingToken.svg",
dependencies: [],
},
"token-vendor": {
id: 2,
branchName: "challenge-2-token-vendor",
- label: "π© Challenge 2: π΅ Token Vendor",
+ label: intl.formatMessage({
+ id: "challenges.challenge-2-token-vendor.label",
+ defaultMessage: "π© Challenge 2: π΅ Token Vendor",
+ }),
icon: "/assets/key_icon.svg",
disabled: false,
- description:
- 'π€ Smart contracts are kind of like "always on" vending machines that anyone can access. Let\'s make a decentralized, digital currency (an ERC20 token). Then, let\'s build an unstoppable vending machine that will buy and sell the currency. We\'ll learn about the "approve" pattern for ERC20s and how contract to contract interactions work.',
+ description: intl.formatMessage({
+ id: "challenges.challenge-2-token-vendor.description",
+ defaultMessage:
+ 'π€ Smart contracts are kind of like "always on" vending machines that anyone can access. Let\'s make a decentralized, digital currency (an ERC20 token). Then, let\'s build an unstoppable vending machine that will buy and sell the currency. We\'ll learn about the "approve" pattern for ERC20s and how contract to contract interactions work.',
+ }),
previewImage: "/assets/challenges/tokenVendor.svg",
dependencies: [],
},
"buidl-guidl": {
id: 4,
branchName: "",
- label: "Eligible to join π°οΈ BuidlGuidl",
+ label: intl.formatMessage({
+ id: "challenges.buidl-guidl.label",
+ defaultMessage: "Eligible to join π°οΈ BuidlGuidl",
+ }),
icon: "/assets/vault_icon.svg",
// Not a challenge, just a checkpoint in the Challenge timeline.
checkpoint: true,
disabled: false,
- description:
- "The BuidlGuidl is a curated group of Ethereum builders creating products, prototypes, and tutorials to enrich the web3 ecosystem. A place to show off your builds and meet other builders. Start crafting your Web3 portfolio by submitting your DEX, Multisig or SVG NFT build.",
+ description: intl.formatMessage({
+ id: "challenges.buidl-guidl.description",
+ defaultMessage:
+ "The BuidlGuidl is a curated group of Ethereum builders creating products, prototypes, and tutorials to enrich the web3 ecosystem. A place to show off your builds and meet other builders. Start crafting your Web3 portfolio by submitting your DEX, Multisig or SVG NFT build.",
+ }),
previewImage: "assets/bg.png",
dependencies: ["simple-nft-example", "decentralized-staking", "token-vendor"],
externalLink: {
@@ -50,40 +74,64 @@ export const challengeInfo = {
"dice-game": {
id: 3,
branchName: "challenge-3-dice-game",
- label: "π© Challenge 3: π² Dice Game",
+ label: intl.formatMessage({
+ id: "challenges.challenge-3-dice-game.label",
+ defaultMessage: "π© Challenge 3: π² Dice Game",
+ }),
disabled: false,
- description:
- "π° Randomness is tricky on a public deterministic blockchain. The block hash is the result proof-of-work (for now) and some builders use this as a weak form of randomness. In this challenge you will take advantage of a Dice Game contract by predicting the randomness in order to only roll winning dice!",
+ description: intl.formatMessage({
+ id: "challenges.challenge-3-dice-game.description",
+ defaultMessage:
+ "π° Randomness is tricky on a public deterministic blockchain. The block hash is the result proof-of-work (for now) and some builders use this as a weak form of randomness. In this challenge you will take advantage of a Dice Game contract by predicting the randomness in order to only roll winning dice!",
+ }),
previewImage: "/assets/challenges/diceGame.svg",
dependencies: ["simple-nft-example", "decentralized-staking", "token-vendor"],
},
"minimum-viable-exchange": {
id: 5,
branchName: "challenge-4-dex",
- label: "βοΈ Build a DEX Challenge",
+ label: intl.formatMessage({
+ id: "challenges.challenge-4-dex.label",
+ defaultMessage: "βοΈ Build a DEX Challenge",
+ }),
disabled: false,
- description:
- "π΅ Build an exchange that swaps ETH to tokens and tokens to ETH. π° This is possible because the smart contract holds reserves of both assets and has a price function based on the ratio of the reserves. Liquidity providers are issued a token that represents their share of the reserves and fees...",
+ description: intl.formatMessage({
+ id: "challenges.challenge-4-dex.description",
+ defaultMessage:
+ "π΅ Build an exchange that swaps ETH to tokens and tokens to ETH. π° This is possible because the smart contract holds reserves of both assets and has a price function based on the ratio of the reserves. Liquidity providers are issued a token that represents their share of the reserves and fees...",
+ }),
previewImage: "assets/challenges/dex.svg",
dependencies: ["simple-nft-example", "decentralized-staking", "token-vendor", "dice-game"],
},
"state-channels": {
id: 9,
branchName: "challenge-9-state-channels",
- label: "πΊ A State Channel Application Challenge",
+ label: intl.formatMessage({
+ id: "challenges.challenge-9-state-channels.label",
+ defaultMessage: "πΊ A State Channel Application Challenge",
+ }),
disabled: false,
- description:
- "π£οΈ The Ethereum blockchain has great decentralization & security properties but these properties come at a price: transaction throughput is low, and transactions can be expensive. This makes many traditional web applications infeasible on a blockchain... or does it? State channels look to solve these problems by allowing participants to securely transact off-chain while keeping interaction with Ethereum Mainnet at a minimum.",
+ description: intl.formatMessage({
+ id: "challenges.challenge-9-state-channels.description",
+ defaultMessage:
+ "π£οΈ The Ethereum blockchain has great decentralization & security properties but these properties come at a price: transaction throughput is low, and transactions can be expensive. This makes many traditional web applications infeasible on a blockchain... or does it? State channels look to solve these problems by allowing participants to securely transact off-chain while keeping interaction with Ethereum Mainnet at a minimum.",
+ }),
previewImage: "assets/challenges/state.svg",
dependencies: ["simple-nft-example", "decentralized-staking", "token-vendor", "dice-game"],
},
"learn-multisig": {
id: 6,
branchName: "challenge-3-multi-sig",
- label: "π Multisig Wallet Challenge",
+ label: intl.formatMessage({
+ id: "challenges.challenge-3-multi-sig.label",
+ defaultMessage: "π Multisig Wallet Challenge",
+ }),
disabled: false,
- description:
- 'π©βπ©βπ§βπ§ Using a smart contract as a wallet we can secure assets by requiring multiple accounts to "vote" on transactions. The contract will keep track of transactions in an array of structs and owners will confirm or reject each one. Any transaction with enough confirmations can "execute".',
+ description: intl.formatMessage({
+ id: "challenges.challenge-3-multi-sig.description",
+ defaultMessage:
+ 'π©βπ©βπ§βπ§ Using a smart contract as a wallet we can secure assets by requiring multiple accounts to "vote" on transactions. The contract will keep track of transactions in an array of structs and owners will confirm or reject each one. Any transaction with enough confirmations can "execute".',
+ }),
previewImage: "assets/challenges/multiSig.svg",
// Challenge locked until the builder completed these challenges
dependencies: ["simple-nft-example", "decentralized-staking", "token-vendor", "dice-game"],
@@ -97,10 +145,16 @@ export const challengeInfo = {
"nft-cohort": {
id: 7,
branchName: "challenge-5-svg-nft-cohort",
- label: "π SVG NFT π« Building Cohort Challenge",
+ label: intl.formatMessage({
+ id: "challenges.challenge-5-svg-nft-cohort.label",
+ defaultMessage: "π SVG NFT π« Building Cohort Challenge",
+ }),
disabled: false,
- description:
- "π§ Tinker around with cutting edge smart contracts that render SVGs in Solidity. 𧫠We quickly discovered that the render function needs to be public... π€ This allows NFTs that own other NFTs to render their stash. Just wait until you see an Optimistic Loogie and a Fancy Loogie swimming around in the same Loogie Tank!",
+ description: intl.formatMessage({
+ id: "challenges.challenge-5-svg-nft-cohort.description",
+ defaultMessage:
+ "π§ Tinker around with cutting edge smart contracts that render SVGs in Solidity. 𧫠We quickly discovered that the render function needs to be public... π€ This allows NFTs that own other NFTs to render their stash. Just wait until you see an Optimistic Loogie and a Fancy Loogie swimming around in the same Loogie Tank!",
+ }),
previewImage: "assets/challenges/dynamicSvgNFT.svg",
// Challenge locked until the builder completed these challenges
dependencies: ["simple-nft-example", "decentralized-staking", "token-vendor", "dice-game"],
@@ -111,12 +165,12 @@ export const challengeInfo = {
claim: "Join the π SVG NFT π« Building Cohort",
},
},
-};
+});
const githubChallengesRepoBaseRawUrl = {
js: "https://raw.githubusercontent.com/scaffold-eth/scaffold-eth-challenges",
ts: "https://raw.githubusercontent.com/scaffold-eth/scaffold-eth-typescript-challenges",
};
-export const getGithubChallengeReadmeUrl = (challengeId, version) =>
- `${githubChallengesRepoBaseRawUrl[version]}/${challengeInfo[challengeId].branchName}/README.md`;
+export const getGithubChallengeReadmeUrl = (challengeId, version, intl) =>
+ `${githubChallengesRepoBaseRawUrl[version]}/${getChallengeInfo(intl)[challengeId].branchName}/README.md`;
diff --git a/packages/react-app/src/index.jsx b/packages/react-app/src/index.jsx
index 846a703c..64033624 100644
--- a/packages/react-app/src/index.jsx
+++ b/packages/react-app/src/index.jsx
@@ -1,19 +1,33 @@
-import React from "react";
+import React, { useState } from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import "@fontsource/space-grotesk/400.css";
import "@fontsource/space-grotesk/500.css";
import "./index.css";
import { ChakraProvider, ColorModeScript } from "@chakra-ui/react";
+import { IntlProvider } from "react-intl";
import theme from "./theme";
import App from "./App";
-ReactDOM.render(
-
-
-
-
-
- ,
- document.getElementById("root"),
+import translationEn from "./lang/en.json";
+import translationEs from "./lang/es.json";
+
+const translations = {
+ en: translationEn,
+ es: translationEs,
+};
+
+// TODO: change from ui
+const userLocale = "en";
+
+const Root = () => (
+
+
+
+
+
+
+
+
);
+ReactDOM.render(, document.getElementById("root"));
diff --git a/packages/react-app/src/lang/en.json b/packages/react-app/src/lang/en.json
new file mode 100644
index 00000000..bc63d4f7
--- /dev/null
+++ b/packages/react-app/src/lang/en.json
@@ -0,0 +1,98 @@
+{
+ "account.connect-wallet": "Connect Wallet",
+ "announcementBanner": "Hey builder!! The BuidlGuidl is hosting a π Scaffold-Eth 2 hackathon. We are giving 10 ETH away to the best projects. {br}Come join the fun and learn the latest scaffold-eth techniques! Let's build a bunch of apps!",
+ "builderChallenges.challenges": "Challenges",
+ "builderChallenges.empty-state.button": "Start a challenge",
+ "builderChallenges.empty-state.description": "Show off your skills. Learn everything you need to build on Ethereum!",
+ "builderChallenges.empty-state.other-profile": "This builder hasn't completed any challenges.",
+ "builderChallenges.empty-state.title": "Start a new challenge",
+ "builderChallenges.start-challenge": "Start a challenge",
+ "builderChallenges.table.contract": "Contract",
+ "builderChallenges.table.live-demo": "Live Demo",
+ "builderChallenges.table.name": "Name",
+ "builderChallenges.table.status": "Status",
+ "builderChallenges.table.updated": "Updated",
+ "builderProfileCard.error.invalid-socials": "The usernames for the following socials are not correct: {invalidSocials}",
+ "builderProfileCard.error.updating-socials": "Can't update your socials. Please try again.",
+ "builderProfileCard.joined": "Joined {date}",
+ "builderProfileCard.modal-socials.header": "Update your socials",
+ "builderProfileCard.set-socials.tooltip": "It's our way of reaching out to you. We could sponsor you an ENS, offer to be part of a build or set up an ETH stream for you.",
+ "builderProfileCard.set-socials.warning": "You haven't set your socials",
+ "builderProfileCard.success.updating-socials": "Your social links have been updated",
+ "builderProfileCard.update-socials": "Update socials",
+ "builderProfileHeader.challenges": "challenges completed",
+ "builderProfileView.error-getting-challenges": "Can't get challenges metadata. Please try again",
+ "challengeDetailView.github-button": "View it on Github",
+ "challengeDetailView.modal.header": "Submit Challenge",
+ "challengeDetailView.submit-button": "Submit challenge",
+ "challengeDetailView.submit-button.tooltip.register": "You need to register as a builder",
+ "challengeStatusTag.modal.header": "Review feedback",
+ "challengeStatusTag.see-comments": "See comments",
+ "challengeSubmission.challenge-submitted": "Challenge submitted!",
+ "challengeSubmission.connect-wallet": "Connect your wallet to submit this Challenge.",
+ "challengeSubmission.deployed-url": "Deployed URL",
+ "challengeSubmission.deployed-url.tooltip": "Your deployed challenge URL on surge / s3 / ipfs",
+ "challengeSubmission.disabled": "This challenge is disabled.",
+ "challengeSubmission.error.both-fields-required": "Both fields are required",
+ "challengeSubmission.error.incorrect-contract.description": "Please submit your verified contractβs address on a valid testnet. e.g. https://goerli.etherscan.io/address/**Your Contract Address**",
+ "challengeSubmission.error.incorrect-contract.title": "Incorrect Etherscan Contract URL",
+ "challengeSubmission.error.invalid-url.description": "Valid URLs start with http:// or https://",
+ "challengeSubmission.error.invalid-url.title": "Please provide a valid URL",
+ "challengeSubmission.etherscan-url": "Etherscan Contract URL",
+ "challengeSubmission.etherscan-url.tooltip": "Your verified contract URL on Etherscan",
+ "challenges.buidl-guidl.description": "The BuidlGuidl is a curated group of Ethereum builders creating products, prototypes, and tutorials to enrich the web3 ecosystem. A place to show off your builds and meet other builders. Start crafting your Web3 portfolio by submitting your DEX, Multisig or SVG NFT build.",
+ "challenges.buidl-guidl.label": "Eligible to join π°οΈ BuidlGuidl",
+ "challenges.challenge-0-simple-nft.description": "π« Create a simple NFT to learn basics of π scaffold-eth. You'll use π·ββοΈ HardHat to compile and deploy smart contracts. Then, you'll use a template React app full of important Ethereum components and hooks. Finally, you'll deploy an NFT to a public network to share with friends! π",
+ "challenges.challenge-0-simple-nft.label": "π© Challenge 0: π Simple NFT Example",
+ "challenges.challenge-1-decentralized-staking.description": "π¦Έ A superpower of Ethereum is allowing you, the builder, to create a simple set of rules that an adversarial group of players can use to work together. In this challenge, you create a decentralized application where users can coordinate a group funding effort. The users only have to trust the code.",
+ "challenges.challenge-1-decentralized-staking.label": "π© Challenge 1: π₯© Decentralized Staking App",
+ "challenges.challenge-2-token-vendor.description": "π€ Smart contracts are kind of like \"always on\" vending machines that anyone can access. Let's make a decentralized, digital currency (an ERC20 token). Then, let's build an unstoppable vending machine that will buy and sell the currency. We'll learn about the \"approve\" pattern for ERC20s and how contract to contract interactions work.",
+ "challenges.challenge-2-token-vendor.label": "π© Challenge 2: π΅ Token Vendor",
+ "challenges.challenge-3-dice-game.description": "π° Randomness is tricky on a public deterministic blockchain. The block hash is the result proof-of-work (for now) and some builders use this as a weak form of randomness. In this challenge you will take advantage of a Dice Game contract by predicting the randomness in order to only roll winning dice!",
+ "challenges.challenge-3-dice-game.label": "π© Challenge 3: π² Dice Game",
+ "challenges.challenge-3-multi-sig.description": "π©βπ©βπ§βπ§ Using a smart contract as a wallet we can secure assets by requiring multiple accounts to \"vote\" on transactions. The contract will keep track of transactions in an array of structs and owners will confirm or reject each one. Any transaction with enough confirmations can \"execute\".",
+ "challenges.challenge-3-multi-sig.label": "π Multisig Wallet Challenge",
+ "challenges.challenge-4-dex.description": "π΅ Build an exchange that swaps ETH to tokens and tokens to ETH. π° This is possible because the smart contract holds reserves of both assets and has a price function based on the ratio of the reserves. Liquidity providers are issued a token that represents their share of the reserves and fees...",
+ "challenges.challenge-4-dex.label": "βοΈ Build a DEX Challenge",
+ "challenges.challenge-5-svg-nft-cohort.description": "π§ Tinker around with cutting edge smart contracts that render SVGs in Solidity. 𧫠We quickly discovered that the render function needs to be public... π€ This allows NFTs that own other NFTs to render their stash. Just wait until you see an Optimistic Loogie and a Fancy Loogie swimming around in the same Loogie Tank!",
+ "challenges.challenge-5-svg-nft-cohort.label": "π SVG NFT π« Building Cohort Challenge",
+ "challenges.challenge-9-state-channels.description": "π£οΈ The Ethereum blockchain has great decentralization & security properties but these properties come at a price: transaction throughput is low, and transactions can be expensive. This makes many traditional web applications infeasible on a blockchain... or does it? State channels look to solve these problems by allowing participants to securely transact off-chain while keeping interaction with Ethereum Mainnet at a minimum.",
+ "challenges.challenge-9-state-channels.label": "πΊ A State Channel Application Challenge",
+ "error.access-error": "Access error",
+ "error.server-overloaded": "Sorry, the server is overloaded. π§―ππ₯",
+ "error.signature-from-wallet": "Couldn't get a signature from the Wallet",
+ "footer.built-with-love-at-buidlguidl": "Built with {heartIcon} at BuidlGuidl",
+ "footer.fork-me": "Fork me",
+ "general.Submit": "Submit",
+ "general.accepted": "Accepted",
+ "general.code": "Code",
+ "general.demo": "Demo",
+ "general.error.cant-get-message": "Can't get the message to sign. Please try again",
+ "general.error.signature-cancelled": "The signature was cancelled",
+ "general.error.submission-error": "Submission Error. Please try again.",
+ "general.locked": "Locked",
+ "general.quest": "Quest",
+ "general.rejected": "Rejected",
+ "general.role": "Role",
+ "general.submitted": "Submitted",
+ "general.update": "Update",
+ "header.activity": "Activity",
+ "header.builders": "Builders",
+ "header.portfolio": "Portfolio",
+ "header.review-submissions": "Review Submissions",
+ "index.learn-ethereum": "Learn how to build on Ethereum; the superpowers and the gotchas.",
+ "index.step-1": "Watch this quick video as an Intro to Ethereum Development.",
+ "index.step-2.1": "Then use π Scaffold-ETH to copy/paste each Solidity concept and tinker:",
+ "index.step-2.2": "global units, primitives, mappings, structs, modifiers, events,",
+ "index.step-2.3": "inheritance, sending eth, and payable/fallback functions.",
+ "index.step-3": "Watch this getting started playlist to become a power user and eth scripter.",
+ "index.step-4": "When you are ready to test your knowledge, Speed Run Ethereum:",
+ "joinBg.button.already-joined": "Already joined",
+ "joinBg.missing-socials.description": "In order to join the BuildGuidl you need to set your socials in your portfolio. It's our way to contact you.",
+ "joinBg.missing-socials.title": "Can't join the BuidlGuidl",
+ "joinBg.success.description": "Visit BuidlGuidl and start crafting your Web3 portfolio by submitting your DEX, Multisig or SVG NFT build.",
+ "joinBg.success.title": "Welcome to the BuildGuidl :)",
+ "joinedBuidlGuidlBanner.button": "View their profile on Buidlguidl",
+ "joinedBuidlGuidlBanner.label": "This builder has upgraded to BuidlGuidl",
+ "signatureSignUp.write-icon": "write icon"
+}
\ No newline at end of file
diff --git a/packages/react-app/src/lang/es.json b/packages/react-app/src/lang/es.json
new file mode 100644
index 00000000..69d21ebc
--- /dev/null
+++ b/packages/react-app/src/lang/es.json
@@ -0,0 +1,5 @@
+{
+ "account.connect-wallet": "Conectar Wallet",
+ "index.learn-ethereum": "Aprender a desarrollar en Ethereum; los super poderes y los trucos.",
+ "challenges.simple-nft-example.label": "π© Reto 0: π Ejemplo de NFT Simple"
+}
diff --git a/packages/react-app/src/views/BuilderProfileView.jsx b/packages/react-app/src/views/BuilderProfileView.jsx
index b21f322a..ddc9e5a6 100644
--- a/packages/react-app/src/views/BuilderProfileView.jsx
+++ b/packages/react-app/src/views/BuilderProfileView.jsx
@@ -2,8 +2,9 @@ import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import axios from "axios";
import { useToast, useColorModeValue, Container, SimpleGrid, GridItem, Box } from "@chakra-ui/react";
+import { useIntl } from "react-intl";
import BuilderProfileCard from "../components/builder/BuilderProfileCard";
-import { challengeInfo } from "../data/challenges";
+import { getChallengeInfo } from "../data/challenges";
import { BG_BACKEND_URL as bgBackendUrl } from "../constants";
import { getAcceptedChallenges } from "../helpers/builders";
import { getChallengeEventsForUser } from "../data/api";
@@ -26,6 +27,8 @@ export default function BuilderProfileView({
const [isLoadingBuilder, setIsLoadingBuilder] = useState(false);
const [isBuilderOnBg, setIsBuilderOnBg] = useState(false);
const [isLoadingTimestamps, setIsLoadingTimestamps] = useState(false);
+ const intl = useIntl();
+ const challengeInfo = getChallengeInfo(intl);
const toast = useToast({ position: "top", isClosable: true });
const toastVariant = useColorModeValue("subtle", "solid");
const bgColor = useColorModeValue("sre.cardBackground", "sreDark.cardBackground");
@@ -75,7 +78,10 @@ export default function BuilderProfileView({
setIsLoadingTimestamps(false);
} catch (error) {
toast({
- description: "Can't get challenges metadata. Please try again",
+ description: intl.formatMessage({
+ id: "builderProfileView.error-getting-challenges",
+ defaultMessage: "Can't get challenges metadata. Please try again",
+ }),
status: "error",
variant: toastVariant,
});
diff --git a/packages/react-app/src/views/ChallengeDetailView.jsx b/packages/react-app/src/views/ChallengeDetailView.jsx
index 5619aaf5..db3fe5b9 100644
--- a/packages/react-app/src/views/ChallengeDetailView.jsx
+++ b/packages/react-app/src/views/ChallengeDetailView.jsx
@@ -26,7 +26,8 @@ import ReactMarkdown from "react-markdown";
import ChakraUIRenderer from "chakra-ui-markdown-renderer";
import rehypeRaw from "rehype-raw";
-import { challengeInfo } from "../data/challenges";
+import { FormattedMessage, useIntl } from "react-intl";
+import { getChallengeInfo } from "../data/challenges";
import ChallengeSubmission from "../components/ChallengeSubmission";
import { chakraMarkdownComponents } from "../helpers/chakraMarkdownTheme";
import { USER_ROLES, JS_CHALLENGE_REPO, TS_CHALLENGE_REPO } from "../helpers/constants";
@@ -42,6 +43,8 @@ export default function ChallengeDetailView({ serverUrl, address, userProvider,
const [openModalOnLoad, setOpenModalOnLoad] = useState(false);
const bgColor = useColorModeValue("sre.cardBackground", "sreDark.cardBackground");
+ const intl = useIntl();
+ const challengeInfo = getChallengeInfo(intl);
const challenge = challengeInfo[challengeId];
const isWalletConnected = !!userRole;
const isAnonymous = userRole && USER_ROLES.anonymous === userRole;
@@ -50,14 +53,14 @@ export default function ChallengeDetailView({ serverUrl, address, userProvider,
// In the future, this might be a fetch to the repos/branchs README
// (Ideally fetched at build time)
useEffect(() => {
- getChallengeReadme(challengeId, "js")
+ getChallengeReadme(challengeId, "js", intl)
.then(text => setDescriptionJs(parseGithubReadme(text)))
.catch(() => setDescriptionJs(null));
- getChallengeReadme(challengeId, "ts")
+ getChallengeReadme(challengeId, "ts", intl)
.then(text => setDescriptionTs(parseGithubReadme(text)))
.catch(() => setDescriptionTs(null));
- }, [challengeId, challenge]);
+ }, [challengeId, challenge, intl]);
useEffect(() => {
if (!isWalletConnected || isAnonymous) return;
@@ -98,13 +101,26 @@ export default function ChallengeDetailView({ serverUrl, address, userProvider,
target="_blank"
rel="noopener noreferrer"
>
- View it on Github
+ {" "}
+
-
+
+ ) : (
+
+ )
+ }
+ shouldWrapChildren
+ >
@@ -146,7 +162,9 @@ export default function ChallengeDetailView({ serverUrl, address, userProvider,
- Submit Challenge
+
+
+
(
export default function HomeView({ connectedBuilder, userProvider }) {
const { primaryFontColor, bgColor } = useCustomColorModes();
const cardBgColor = useColorModeValue("sre.cardBackground", "sreDark.cardBackground");
-
+ const intl = useIntl();
+ const challengeInfo = getChallengeInfo(intl);
const builderAttemptedChallenges = useMemo(() => {
if (!connectedBuilder?.challenges) {
return [];
@@ -60,7 +62,13 @@ export default function HomeView({ connectedBuilder, userProvider }) {
}}
textAlign="center"
>
- Learn how to build on Ethereum; the superpowers and the gotchas.
+ {chunks},
+ }}
+ />
- Watch this{" "}
-
- quick video
- {" "}
- as an Intro to Ethereum Development.
+ (
+
+ {chunks}
+
+ ),
+ }}
+ />
@@ -110,19 +124,27 @@ export default function HomeView({ connectedBuilder, userProvider }) {
}}
textAlign="center"
>
- Then use{" "}
-
-
- π
- {" "}
- Scaffold-ETH
- {" "}
- to copy/paste each Solidity concept and tinker:
+ (
+
+ {chunks}
+
+ ),
+ span: chunks => (
+
+ {chunks}
+
+ ),
+ }}
+ />
- {" "}
-
- inheritance
-
- ,{" "}
-
- sending eth
-
- , and{" "}
-
- payable
-
- /
-
- fallback
- {" "}
- functions.
+ global units, primitives, mappings, structs, modifiers, events,
+ `}
+ values={{
+ a_globalUnits: chunks => (
+
+ {chunks}
+
+ ),
+ a_primitives: chunks => (
+
+ {chunks}
+
+ ),
+ a_mappings: chunks => (
+
+ {chunks}
+
+ ),
+ a_structs: chunks => (
+
+ {chunks}
+
+ ),
+ a_modifiers: chunks => (
+
+ {chunks}
+
+ ),
+ a_events: chunks => (
+
+ {chunks}
+
+ ),
+ }}
+ />{" "}
+ inheritance, sending eth, and payable/fallback functions.
+ `}
+ values={{
+ a_inheritance: chunks => (
+
+ {chunks}
+
+ ),
+ a_sendingEth: chunks => (
+
+ {chunks}
+
+ ),
+ a_payable: chunks => (
+
+ {chunks}
+
+ ),
+ a_fallback: chunks => (
+
+ {chunks}
+
+ ),
+ }}
+ />
@@ -194,16 +247,22 @@ export default function HomeView({ connectedBuilder, userProvider }) {
}}
textAlign="center"
>
- Watch this{" "}
-
- getting started playlist
- {" "}
- to become a power user and eth scripter.
+ (
+
+ {chunks}
+
+ ),
+ }}
+ />
@@ -218,7 +277,10 @@ export default function HomeView({ connectedBuilder, userProvider }) {
}}
textAlign="center"
>
- When you are ready to test your knowledge, Speed Run Ethereum:
+
{Object.entries(challengeInfo).map(([challengeId, challenge], index, { length }) => (
|