Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

i18n to Contract Verification #5591

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
27 changes: 22 additions & 5 deletions apps/contract-verification/src/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { CompilerAbstract } from '@remix-project/remix-solidity'
import { useLocalStorage } from './hooks/useLocalStorage'
import { getVerifier } from './Verifiers'
import { ContractDropdownSelection } from './components/ContractDropdown'
import { IntlProvider } from 'react-intl'

const plugin = new ContractVerificationPluginClient()

Expand All @@ -32,11 +33,25 @@ const App = () => {
const [proxyAddressError, setProxyAddressError] = useState('')
const [abiEncodedConstructorArgs, setAbiEncodedConstructorArgs] = useState<string>('')
const [abiEncodingError, setAbiEncodingError] = useState<string>('')
const [locale, setLocale] = useState<{ code: string; messages: any }>({
code: 'en',
messages: {}
})

const timer = useRef(null)

useEffect(() => {
plugin.internalEvents.on('verification_activated', () => {

// @ts-ignore
plugin.call('locale', 'currentLocale').then((locale: any) => {
setLocale(locale)
})

// @ts-ignore
plugin.on('locale', 'localeChanged', (locale: any) => {
setLocale(locale)
})
// Fetch compiler artefacts initially
plugin.call('compilerArtefacts' as any, 'getAllCompilerAbstracts').then((obj: any) => {
setCompilationOutput(obj)
Expand Down Expand Up @@ -143,11 +158,13 @@ const App = () => {
}, [submittedContracts])

return (
<AppContext.Provider value={{ themeType, setThemeType, clientInstance: plugin, settings, setSettings, chains, compilationOutput, submittedContracts, setSubmittedContracts }}>
<VerifyFormContext.Provider value={{ selectedChain, setSelectedChain, contractAddress, setContractAddress, contractAddressError, setContractAddressError, selectedContract, setSelectedContract, proxyAddress, setProxyAddress, proxyAddressError, setProxyAddressError, abiEncodedConstructorArgs, setAbiEncodedConstructorArgs, abiEncodingError, setAbiEncodingError }}>
<DisplayRoutes />
</VerifyFormContext.Provider>
</AppContext.Provider>
<IntlProvider locale={locale.code} messages={locale.messages}>
<AppContext.Provider value={{ themeType, setThemeType, clientInstance: plugin, settings, setSettings, chains, compilationOutput, submittedContracts, setSubmittedContracts }}>
<VerifyFormContext.Provider value={{ selectedChain, setSelectedChain, contractAddress, setContractAddress, contractAddressError, setContractAddressError, selectedContract, setSelectedContract, proxyAddress, setProxyAddress, proxyAddressError, setProxyAddressError, abiEncodedConstructorArgs, setAbiEncodedConstructorArgs, abiEncodingError, setAbiEncodingError }}>
<DisplayRoutes />
</VerifyFormContext.Provider>
</AppContext.Provider>
</IntlProvider>
)
}

Expand Down
40 changes: 20 additions & 20 deletions apps/contract-verification/src/app/components/AccordionReceipt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,32 +97,32 @@ const ReceiptsBody = ({ receipts }: { receipts: VerificationReceipt[] }) => {
className="list-group-item d-flex flex-row align-items-center"
>
<CustomTooltip
placement="top"
tooltipClasses=" text-break"
tooltipTextClasses="text-capitalize"
tooltipText={`Status: ${receipt.status}${receipt.message ? `, Message: ${receipt.message}` : ''}`}
>
<span className="mr-2">
{['verified', 'partially verified', 'already verified'].includes(receipt.status) ?
<i className="fas fa-check text-success px-1"></i> :
receipt.status === 'fully verified' ?
<i className="fas fa-check-double text-success px-1"></i> :
receipt.status === 'failed' ?
<i className="fas fa-xmark text-warning px-1"></i> :
['pending', 'awaiting implementation verification'].includes(receipt.status) ?
<i className="fas fa-spinner fa-spin"></i> :
<i className="fas fa-question"></i>
}
</span>
</CustomTooltip>
<div className="d-flex flex-row w-100 justify-content-between">
placement="top"
tooltipClasses=" text-break"
tooltipTextClasses="text-capitalize"
tooltipText={`Status: ${receipt.status}${receipt.message ? `, Message: ${receipt.message}` : ''}`}
>
<span className="mr-2">
{['verified', 'partially verified', 'already verified'].includes(receipt.status) ?
<i className="fas fa-check text-success px-1"></i> :
receipt.status === 'fully verified' ?
<i className="fas fa-check-double text-success px-1"></i> :
receipt.status === 'failed' ?
<i className="fas fa-xmark text-warning px-1"></i> :
['pending', 'awaiting implementation verification'].includes(receipt.status) ?
<i className="fas fa-spinner fa-spin"></i> :
<i className="fas fa-question"></i>
}
</span>
</CustomTooltip>
<div className="d-flex flex-row w-100 justify-content-between">
<CustomTooltip placement="top" tooltipClasses=" text-break" tooltipText={`API: ${receipt.verifierInfo.apiUrl}`}>
<span className="font-weight-bold pr-2">{receipt.verifierInfo.name}</span>
</CustomTooltip>
<div className="ml-1">
{!!receipt.lookupUrl && receipt.verifierInfo.name === 'Blockscout' ?
<CopyToClipboard classList="pr-0 py-0" tip="Copy code URL" content={receipt.lookupUrl} direction="top" /> :
!!receipt.lookupUrl && <a href={receipt.lookupUrl} target="_blank" className="fa fas fa-arrow-up-right-from-square"></a>
!!receipt.lookupUrl && <a href={receipt.lookupUrl} target="_blank" className="fa fas fa-arrow-up-right-from-square" rel="noreferrer"></a>
}
</div>
</div>
Expand Down
8 changes: 5 additions & 3 deletions apps/contract-verification/src/app/components/ConfigInput.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react'
import { CustomTooltip } from '@remix-ui/helper'
import { FormattedMessage, useIntl } from 'react-intl'

interface ConfigInputProps {
label: string
Expand All @@ -13,6 +14,7 @@ interface ConfigInputProps {
export const ConfigInput: React.FC<ConfigInputProps> = ({ label, id, secret, initialValue, saveResult }) => {
const [value, setValue] = useState(initialValue)
const [enabled, setEnabled] = useState(false)
const intl = useIntl()

// Reset state when initialValue changes
useEffect(() => {
Expand Down Expand Up @@ -42,7 +44,7 @@ export const ConfigInput: React.FC<ConfigInputProps> = ({ label, id, secret, ini
type={secret ? 'password' : 'text'}
className={`form-control small w-100 ${!enabled ? 'bg-transparent pl-0 border-0' : ''}`}
id={id}
placeholder={`Add ${label}`}
placeholder={intl.formatMessage({ id: "contract-verification.configInputPlaceholder", defaultMessage: "Enter API Key" }, { label })}
value={value}
onChange={(e) => setValue(e.target.value)}
disabled={!enabled}
Expand All @@ -51,10 +53,10 @@ export const ConfigInput: React.FC<ConfigInputProps> = ({ label, id, secret, ini
{ enabled ? (
<>
<button type="button" className="btn btn-primary btn-sm ml-2" onClick={handleSave}>
Save
<FormattedMessage id="contract-verification.configInputSaveButton" defaultMessage="Save" />
</button>
<button type="button" className="btn btn-secondary btn-sm ml-2" onClick={handleCancel}>
Cancel
<FormattedMessage id="contract-verification.configInputCancelButton" defaultMessage="Cancel" />
</button>
</>
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ethers } from 'ethers'

import { AppContext } from '../AppContext'
import { ContractDropdownSelection } from './ContractDropdown'
import { FormattedMessage } from 'react-intl'

interface ConstructorArgumentsProps {
abiEncodedConstructorArgs: string
Expand Down Expand Up @@ -102,7 +103,10 @@ export const ConstructorArguments: React.FC<ConstructorArgumentsProps> = ({ abiE
<div className="d-flex py-1 align-items-center custom-control custom-checkbox">
<input className="form-check-input custom-control-input" type="checkbox" id="toggleRawInputSwitch" checked={toggleRawInput} onChange={() => setToggleRawInput(!toggleRawInput)} />
<label className="m-0 form-check-label custom-control-label" style={{ paddingTop: '2px' }} htmlFor="toggleRawInputSwitch">
Enter raw ABI-encoded constructor arguments
<FormattedMessage
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the default message needed? See Drafish's comment:

// There may have some un-translated content. Always fill in the gaps with EN JSON.
// No need for a defaultMessage prop when render a FormattedMessage component.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noted that when going through the docs, but without the defaultMessage, there are console errors for every FormattedMessage block added. I will look into it and see

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ryestew I noted from the error I get when the FormattedMessage tags are created without a defaultMessage prop that it falls back to using the id prop as the default. See this link which is the documentation page of the package we are using react-intl (scroll to the bottom to see code examples). In the examples shown, defaultMessage prop is used to give a fallback. So we might need to update our docs.

This here also shows the fallback behaviour of the libarary react-intl fallback behaviour

id="contract-verification.constructorArgumentsToggleRawInput"
defaultMessage="Enter raw ABI-encoded constructor arguments"
/>
</label>
</div>
{toggleRawInput ? (
Expand All @@ -122,7 +126,10 @@ export const ConstructorArguments: React.FC<ConstructorArgumentsProps> = ({ abiE
{abiEncodedConstructorArgs && (
<div>
<label className="form-check-label" htmlFor="rawAbiEncodingResult">
ABI-encoded constructor arguments:
<FormattedMessage
id="contract-verification.constructorArgumentsRawAbiEncodingResult"
defaultMessage="ABI-encoded constructor arguments"
/> :
</label>
<textarea className="form-control" rows={5} disabled value={abiEncodedConstructorArgs} id="rawAbiEncodingResult" style={{ opacity: 0.5 }} />
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import React, { useEffect, useState, useContext } from 'react'
import { ethers } from 'ethers/'

interface ContractAddressInputProps {
label: string
label: string | any
id: string
contractAddress: string
setContractAddress: (address: string) => void
contractAddressError: string
contractAddressError: string | any
setContractAddressError: (error: string) => void
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useEffect, useState, useContext, Fragment } from 'react'
import './ContractDropdown.css'
import { AppContext } from '../AppContext'
import { FormattedMessage } from 'react-intl'

export interface ContractDropdownSelection {
triggerFilePath: string
Expand Down Expand Up @@ -42,7 +43,7 @@ export const ContractDropdown: React.FC<ContractDropdownProps> = ({ label, id, s

return (
<div className="form-group">
<label htmlFor={id}>{label}</label>
<label htmlFor={id}><FormattedMessage id="contract-verification.contractDropdownLabel" defaultMessage={label} values={{ label }} /></label>
<select value={selectedContract ? JSON.stringify(selectedContract) : ''}
className={`form-control custom-select pr-4 ${!hasContracts ? 'disabled-cursor text-warning' : ''}`}
id={id}
Expand Down
14 changes: 8 additions & 6 deletions apps/contract-verification/src/app/components/NavMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import React from 'react'
import { NavLink } from 'react-router-dom'
import { useIntl, FormattedMessage } from 'react-intl'

interface NavItemProps {
to: string
icon: JSX.Element
title: string
title: string | any
}

const NavItem: React.FC<NavItemProps> = ({ to, icon, title }) => {
const intl = useIntl()
return (
<NavLink
data-id={`${title}Tab`}
Expand All @@ -24,11 +26,11 @@ const NavItem: React.FC<NavItemProps> = ({ to, icon, title }) => {

export const NavMenu = () => {
return (
<nav className="d-flex medium flex-row w-100" style={{backgroundColor: 'var(--body-bg)!important'}}>
<NavItem to="/" icon={<i className="fas fa-home"></i>} title="Verify" />
<NavItem to="/receipts" icon={<i className="fas fa-receipt"></i>} title="Receipts" />
<NavItem to="/lookup" icon={<i className="fas fa-search"></i>} title="Lookup" />
<NavItem to="/settings" icon={<i className="fas fa-cog"></i>} title="Settings" />
<nav className="d-flex medium flex-row w-100" style={{ backgroundColor: 'var(--body-bg)!important' }}>
<NavItem to="/" icon={<i className="fas fa-home"></i>} title={ <FormattedMessage id="contract-verification.verifyNavTitle" defaultMessage={'Verify'} /> } />
<NavItem to="/receipts" icon={<i className="fas fa-receipt"></i>} title={ <FormattedMessage id="contract-verification.receiptsNavTitle" defaultMessage={'Receipts'} /> } />
<NavItem to="/lookup" icon={<i className="fas fa-search"></i>} title={ <FormattedMessage id="contract-verification.lookupNavTitle" defaultMessage={'Lookup'} /> } />
<NavItem to="/settings" icon={<i className="fas fa-cog"></i>} title={ <FormattedMessage id="contract-verification.settingsNavTitle" defaultMessage={'Settings'} /> } />
</nav>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import React, { useState, useEffect, useRef, useMemo } from 'react'
import Fuse from 'fuse.js'
import type { Chain } from '../types'
import { AppContext } from '../AppContext'
import { useIntl } from 'react-intl'

function getChainDescriptor(chain: Chain): string {
if (!chain) return ''
return `${chain.title || chain.name} (${chain.chainId})`
}

interface DropdownProps {
label: string
label: string | any
id: string
setSelectedChain: (chain: Chain) => void
selectedChain: Chain
Expand All @@ -18,6 +19,7 @@ interface DropdownProps {
export const SearchableChainDropdown: React.FC<DropdownProps> = ({ label, id, setSelectedChain, selectedChain }) => {
const { chains } = React.useContext(AppContext)
const ethereumChainIds = [1, 11155111, 17000]
const intl = useIntl()

// Add Ethereum chains to the head of the chains list. Sort the rest alphabetically
const dropdownChains = useMemo(
Expand Down Expand Up @@ -90,7 +92,7 @@ export const SearchableChainDropdown: React.FC<DropdownProps> = ({ label, id, se
{' '}
{/* Add ref here */}
<label htmlFor={id}>{label}</label>
<input type="text" value={searchTerm} onChange={handleInputChange} onClick={openDropdown} data-id="chainDropdownbox" placeholder="Select a chain" className="form-control" />
<input type="text" value={searchTerm} onChange={handleInputChange} onClick={openDropdown} data-id="chainDropdownbox" placeholder={intl.formatMessage({ id: "contract-verification.searchableChainDropdown", defaultMessage: "Select a chain" })} className="form-control" />
<ul className="dropdown-menu show w-100 bg-light" style={{ maxHeight: '400px', overflowY: 'auto', display: isOpen ? 'initial' : 'none' }}>
{filteredOptions.map((chain) => (
<li key={chain.chainId} onClick={() => handleOptionClick(chain)} data-id={chain.chainId} className={`dropdown-item text-dark ${selectedChain?.chainId === chain.chainId ? 'active' : ''}`} style={{ cursor: 'pointer', whiteSpace: 'normal' }}>
Expand Down
4 changes: 2 additions & 2 deletions apps/contract-verification/src/app/layouts/Default.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { NavMenu } from '../components/NavMenu'

interface Props {
from: string
title?: string
description?: string
title?: string | any
description?: string | any
}

export const DefaultLayout = ({ children, title, description }: PropsWithChildren<Props>) => {
Expand Down
9 changes: 5 additions & 4 deletions apps/contract-verification/src/app/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import { HashRouter as Router, Route, Routes } from 'react-router-dom'

import { VerifyView, ReceiptsView, LookupView, SettingsView } from './views'
import { DefaultLayout } from './layouts'
import { FormattedMessage } from 'react-intl'

const DisplayRoutes = () => (
<Router>
<Routes>
<Route
path="/"
element={
<DefaultLayout from="/" title="Verify" description="Verify compiled contracts on different verification services">
<DefaultLayout from="/" title="Verify" description={<FormattedMessage id="contract-verification.verifyDefaultLayout.description" defaultMessage="Verify compiled contracts on different verification services" />}>
<VerifyView />
</DefaultLayout>
}
Expand All @@ -19,7 +20,7 @@ const DisplayRoutes = () => (
<Route
path="/receipts"
element={
<DefaultLayout from="/" title="Receipts" description="Check the verification statuses of contracts submitted for verification">
<DefaultLayout from="/" title="Receipts" description={<FormattedMessage id="contract-verification.receiptsDefaultLayout.description" defaultMessage="Check the verification statuses of contracts submitted for verification" />}>
<ReceiptsView />
</DefaultLayout>
}
Expand All @@ -28,7 +29,7 @@ const DisplayRoutes = () => (
<Route
path="/lookup"
element={
<DefaultLayout from="/" title="Lookup" description="Search for verified contracts and download them to Remix">
<DefaultLayout from="/" title="Lookup" description={<FormattedMessage id="contract-verification.lookupDefaultLayout.description" defaultMessage="Lookup the verification status of a contract by its address" />}>
<LookupView />
</DefaultLayout>
}
Expand All @@ -37,7 +38,7 @@ const DisplayRoutes = () => (
<Route
path="/settings"
element={
<DefaultLayout from="/" title="Settings" description="Customize settings for each verification service and chain">
<DefaultLayout from="/" title="Settings" description={<FormattedMessage id="contract-verification.settingsDefaultLayout.description" defaultMessage="Configure the settings for the contract verification plugin" />}>
<SettingsView />
</DefaultLayout>
}
Expand Down
7 changes: 4 additions & 3 deletions apps/contract-verification/src/app/views/LookupView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useNavigate } from 'react-router-dom'
import { VerifyFormContext } from '../VerifyFormContext'
import { useSourcifySupported } from '../hooks/useSourcifySupported'
import { CopyToClipboard } from '@remix-ui/clipboard'
import { FormattedMessage } from 'react-intl'

export const LookupView = () => {
const { settings, clientInstance } = useContext(AppContext)
Expand Down Expand Up @@ -74,10 +75,10 @@ export const LookupView = () => {
return (
<>
<form onSubmit={handleLookup}>
<SearchableChainDropdown label="Chain" id="network-dropdown" selectedChain={selectedChain} setSelectedChain={setSelectedChain} />
<ContractAddressInput label="Contract Address" id="contract-address" contractAddress={contractAddress} setContractAddress={setContractAddress} contractAddressError={contractAddressError} setContractAddressError={setContractAddressError} />
<SearchableChainDropdown label={<FormattedMessage id="contract-verification.searchableChainDropdownLabel" defaultMessage="Chain" />} id="network-dropdown" selectedChain={selectedChain} setSelectedChain={setSelectedChain} />
<ContractAddressInput label={<FormattedMessage id="contract-verification.contractAddressInput" defaultMessage="Contract Address" />} id="contract-address" contractAddress={contractAddress} setContractAddress={setContractAddress} contractAddressError={contractAddressError} setContractAddressError={setContractAddressError} />
<button type="submit" className="btn w-100 btn-primary" disabled={submitDisabled}>
Lookup
<FormattedMessage id="contract-verification.lookupButton" defaultMessage="Lookup" />
</button>
</form>
<div className="pt-3">
Expand Down
5 changes: 4 additions & 1 deletion apps/contract-verification/src/app/views/ReceiptsView.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useContext } from 'react'
import { AccordionReceipt } from '../components/AccordionReceipt'
import { AppContext } from '../AppContext'
import { FormattedMessage } from 'react-intl'

export const ReceiptsView = () => {
const { submittedContracts } = useContext(AppContext)
Expand All @@ -10,7 +11,9 @@ export const ReceiptsView = () => {
<div>
{contracts.length > 0 ? contracts.map((contract, index) => (
<AccordionReceipt key={contract.id} contract={contract} index={index} />
)) : <div className="text-center mt-5" data-id="noContractsSubmitted">No contracts submitted for verification</div>}
)) : <div className="text-center mt-5" data-id="noContractsSubmitted">
<FormattedMessage id="contract-verification.receipts.noContractsSubmitted" defaultMessage="No contracts submitted for verification" />
</div>}
</div>
)
}
Loading
Loading