diff options
Diffstat (limited to 'ui/src')
24 files changed, 378 insertions, 213 deletions
diff --git a/ui/src/components/comment-form.tsx b/ui/src/components/comment-form.tsx index 5181e45e..9f3476a8 100644 --- a/ui/src/components/comment-form.tsx +++ b/ui/src/components/comment-form.tsx @@ -1,7 +1,10 @@ import { Component, linkEvent } from 'inferno'; import { CommentNode as CommentNodeI, CommentForm as CommentFormI } from '../interfaces'; +import { capitalizeFirstLetter } from '../utils'; import { WebSocketService, UserService } from '../services'; import * as autosize from 'autosize'; +import { i18n } from '../i18next'; +import { T } from 'inferno-i18next'; interface CommentFormProps { postId?: number; @@ -25,12 +28,13 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> { post_id: this.props.node ? this.props.node.comment.post_id : this.props.postId, creator_id: UserService.Instance.user ? UserService.Instance.user.id : null, }, - buttonTitle: !this.props.node ? "Post" : this.props.edit ? "Edit" : "Reply", + buttonTitle: !this.props.node ? capitalizeFirstLetter(i18n.t('post')) : this.props.edit ? capitalizeFirstLetter(i18n.t('edit')) : capitalizeFirstLetter(i18n.t('reply')), } constructor(props: any, context: any) { super(props, context); + this.state = this.emptyState; if (this.props.node) { @@ -62,7 +66,7 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> { <div class="row"> <div class="col-sm-12"> <button type="submit" class="btn btn-sm btn-secondary mr-2" disabled={this.props.disabled}>{this.state.buttonTitle}</button> - {this.props.node && <button type="button" class="btn btn-sm btn-secondary" onClick={linkEvent(this, this.handleReplyCancel)}>Cancel</button>} + {this.props.node && <button type="button" class="btn btn-sm btn-secondary" onClick={linkEvent(this, this.handleReplyCancel)}><T i18nKey="cancel">#</T></button>} </div> </div> </form> diff --git a/ui/src/components/comment-node.tsx b/ui/src/components/comment-node.tsx index a201ddd6..a05286ed 100644 --- a/ui/src/components/comment-node.tsx +++ b/ui/src/components/comment-node.tsx @@ -7,6 +7,8 @@ import * as moment from 'moment'; import { MomentTime } from './moment-time'; import { CommentForm } from './comment-form'; import { CommentNodes } from './comment-nodes'; +import { i18n } from '../i18next'; +import { T } from 'inferno-i18next'; enum BanType {Community, Site}; @@ -74,10 +76,10 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { <Link className="text-info" to={`/u/${node.comment.creator_name}`}>{node.comment.creator_name}</Link> </li> {this.isMod && - <li className="list-inline-item badge badge-light">mod</li> + <li className="list-inline-item badge badge-light"><T i18nKey="mod">#</T></li> } {this.isAdmin && - <li className="list-inline-item badge badge-light">admin</li> + <li className="list-inline-item badge badge-light"><T i18nKey="admin">#</T></li> } <li className="list-inline-item"> <span>( @@ -97,24 +99,24 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { {this.state.showEdit && <CommentForm node={node} edit onReplyCancel={this.handleReplyCancel} disabled={this.props.locked} />} {!this.state.showEdit && !this.state.collapsed && <div> - <div className="md-div" dangerouslySetInnerHTML={mdToHtml(node.comment.removed ? '*removed*' : node.comment.deleted ? '*deleted*' : node.comment.content)} /> + <div className="md-div" dangerouslySetInnerHTML={mdToHtml(node.comment.removed ? `*${i18n.t('removed')}*` : node.comment.deleted ? `*${i18n.t('deleted')}*` : node.comment.content)} /> <ul class="list-inline mb-1 text-muted small font-weight-bold"> {UserService.Instance.user && !this.props.viewOnly && <> <li className="list-inline-item"> - <span class="pointer" onClick={linkEvent(this, this.handleReplyClick)}>reply</span> + <span class="pointer" onClick={linkEvent(this, this.handleReplyClick)}><T i18nKey="reply">#</T></span> </li> <li className="list-inline-item mr-2"> - <span class="pointer" onClick={linkEvent(this, this.handleSaveCommentClick)}>{node.comment.saved ? 'unsave' : 'save'}</span> + <span class="pointer" onClick={linkEvent(this, this.handleSaveCommentClick)}>{node.comment.saved ? i18n.t('unsave') : i18n.t('save')}</span> </li> {this.myComment && <> <li className="list-inline-item"> - <span class="pointer" onClick={linkEvent(this, this.handleEditClick)}>edit</span> + <span class="pointer" onClick={linkEvent(this, this.handleEditClick)}><T i18nKey="edit">#</T></span> </li> <li className="list-inline-item"> <span class="pointer" onClick={linkEvent(this, this.handleDeleteClick)}> - {!this.props.node.comment.deleted ? 'delete' : 'restore'} + {!this.props.node.comment.deleted ? i18n.t('delete') : i18n.t('restore')} </span> </li> </> @@ -123,8 +125,8 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { {this.canMod && <li className="list-inline-item"> {!this.props.node.comment.removed ? - <span class="pointer" onClick={linkEvent(this, this.handleModRemoveShow)}>remove</span> : - <span class="pointer" onClick={linkEvent(this, this.handleModRemoveSubmit)}>restore</span> + <span class="pointer" onClick={linkEvent(this, this.handleModRemoveShow)}><T i18nKey="remove">#</T></span> : + <span class="pointer" onClick={linkEvent(this, this.handleModRemoveSubmit)}><T i18nKey="restore">#</T></span> } </li> } @@ -134,14 +136,14 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { {!this.isMod && <li className="list-inline-item"> {!this.props.node.comment.banned_from_community ? - <span class="pointer" onClick={linkEvent(this, this.handleModBanFromCommunityShow)}>ban</span> : - <span class="pointer" onClick={linkEvent(this, this.handleModBanFromCommunitySubmit)}>unban</span> + <span class="pointer" onClick={linkEvent(this, this.handleModBanFromCommunityShow)}><T i18nKey="ban">#</T></span> : + <span class="pointer" onClick={linkEvent(this, this.handleModBanFromCommunitySubmit)}><T i18nKey="unban">#</T></span> } </li> } {!this.props.node.comment.banned_from_community && <li className="list-inline-item"> - <span class="pointer" onClick={linkEvent(this, this.handleAddModToCommunity)}>{`${this.isMod ? 'remove' : 'appoint'} as mod`}</span> + <span class="pointer" onClick={linkEvent(this, this.handleAddModToCommunity)}>{this.isMod ? i18n.t('remove_as_mod') : i18n.t('appoint_as_mod')}</span> </li> } </> @@ -152,14 +154,14 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { {!this.isAdmin && <li className="list-inline-item"> {!this.props.node.comment.banned ? - <span class="pointer" onClick={linkEvent(this, this.handleModBanShow)}>ban from site</span> : - <span class="pointer" onClick={linkEvent(this, this.handleModBanSubmit)}>unban from site</span> + <span class="pointer" onClick={linkEvent(this, this.handleModBanShow)}><T i18nKey="ban_from_site">#</T></span> : + <span class="pointer" onClick={linkEvent(this, this.handleModBanSubmit)}><T i18nKey="unban_from_site">#</T></span> } </li> } {!this.props.node.comment.banned && <li className="list-inline-item"> - <span class="pointer" onClick={linkEvent(this, this.handleAddAdmin)}>{`${this.isAdmin ? 'remove' : 'appoint'} as admin`}</span> + <span class="pointer" onClick={linkEvent(this, this.handleAddAdmin)}>{this.isAdmin ? i18n.t('remove_as_admin') : i18n.t('appoint_as_admin')}</span> </li> } </> @@ -167,11 +169,11 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { </> } <li className="list-inline-item"> - <Link className="text-muted" to={`/post/${node.comment.post_id}/comment/${node.comment.id}`}>link</Link> + <Link className="text-muted" to={`/post/${node.comment.post_id}/comment/${node.comment.id}`}><T i18nKey="link">#</T></Link> </li> {this.props.markable && <li className="list-inline-item"> - <span class="pointer" onClick={linkEvent(this, this.handleMarkRead)}>{`mark as ${node.comment.read ? 'unread' : 'read'}`}</span> + <span class="pointer" onClick={linkEvent(this, this.handleMarkRead)}>{node.comment.read ? i18n.t('mark_as_unread') : i18n.t('mark_as_read')}</span> </li> } </ul> @@ -180,15 +182,15 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { </div> {this.state.showRemoveDialog && <form class="form-inline" onSubmit={linkEvent(this, this.handleModRemoveSubmit)}> - <input type="text" class="form-control mr-2" placeholder="Reason" value={this.state.removeReason} onInput={linkEvent(this, this.handleModRemoveReasonChange)} /> - <button type="submit" class="btn btn-secondary">Remove Comment</button> + <input type="text" class="form-control mr-2" placeholder={i18n.t('reason')} value={this.state.removeReason} onInput={linkEvent(this, this.handleModRemoveReasonChange)} /> + <button type="submit" class="btn btn-secondary"><T i18nKey="remove_comment">#</T></button> </form> } {this.state.showBanDialog && <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}> <div class="form-group row"> - <label class="col-form-label">Reason</label> - <input type="text" class="form-control mr-2" placeholder="Optional" value={this.state.banReason} onInput={linkEvent(this, this.handleModBanReasonChange)} /> + <label class="col-form-label"><T i18nKey="reason">#</T></label> + <input type="text" class="form-control mr-2" placeholder={i18n.t('reason')} value={this.state.banReason} onInput={linkEvent(this, this.handleModBanReasonChange)} /> </div> {/* TODO hold off on expires until later */} {/* <div class="form-group row"> */} diff --git a/ui/src/components/communities.tsx b/ui/src/components/communities.tsx index c4efe1fb..066d524a 100644 --- a/ui/src/components/communities.tsx +++ b/ui/src/components/communities.tsx @@ -5,6 +5,7 @@ import { retryWhen, delay, take } from 'rxjs/operators'; import { UserOperation, Community, ListCommunitiesResponse, CommunityResponse, FollowCommunityForm, ListCommunitiesForm, SortType } from '../interfaces'; import { WebSocketService } from '../services'; import { msgOp } from '../utils'; +import { T } from 'inferno-i18next'; declare const Sortable: any; @@ -64,17 +65,17 @@ export class Communities extends Component<any, CommunitiesState> { {this.state.loading ? <h5 class=""><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h5> : <div> - <h5>List of communities</h5> + <h5><T i18nKey="list_of_communities">#</T></h5> <div class="table-responsive"> <table id="community_table" class="table table-sm table-hover"> <thead class="pointer"> <tr> - <th>Name</th> - <th class="d-none d-lg-table-cell">Title</th> - <th>Category</th> - <th class="text-right">Subscribers</th> - <th class="text-right d-none d-lg-table-cell">Posts</th> - <th class="text-right d-none d-lg-table-cell">Comments</th> + <th><T i18nKey="name">#</T></th> + <th class="d-none d-lg-table-cell"><T i18nKey="title">#</T></th> + <th><T i18nKey="category">#</T></th> + <th class="text-right"><T i18nKey="subscribers">#</T></th> + <th class="text-right d-none d-lg-table-cell"><T i18nKey="posts">#</T></th> + <th class="text-right d-none d-lg-table-cell"><T i18nKey="comments">#</T></th> <th></th> </tr> </thead> @@ -89,8 +90,8 @@ export class Communities extends Component<any, CommunitiesState> { <td class="text-right d-none d-lg-table-cell">{community.number_of_comments}</td> <td class="text-right"> {community.subscribed ? - <span class="pointer btn-link" onClick={linkEvent(community.id, this.handleUnsubscribe)}>Unsubscribe</span> : - <span class="pointer btn-link" onClick={linkEvent(community.id, this.handleSubscribe)}>Subscribe</span> + <span class="pointer btn-link" onClick={linkEvent(community.id, this.handleUnsubscribe)}><T i18nKey="unsubscribe">#</T></span> : + <span class="pointer btn-link" onClick={linkEvent(community.id, this.handleSubscribe)}><T i18nKey="subscribe">#</T></span> } </td> </tr> @@ -109,9 +110,9 @@ export class Communities extends Component<any, CommunitiesState> { return ( <div class="mt-2"> {this.state.page > 1 && - <button class="btn btn-sm btn-secondary mr-1" onClick={linkEvent(this, this.prevPage)}>Prev</button> + <button class="btn btn-sm btn-secondary mr-1" onClick={linkEvent(this, this.prevPage)}><T i18nKey="prev">#</T></button> } - <button class="btn btn-sm btn-secondary" onClick={linkEvent(this, this.nextPage)}>Next</button> + <button class="btn btn-sm btn-secondary" onClick={linkEvent(this, this.nextPage)}><T i18nKey="next">#</T></button> </div> ); } diff --git a/ui/src/components/community-form.tsx b/ui/src/components/community-form.tsx index e295dcbe..f6520fc6 100644 --- a/ui/src/components/community-form.tsx +++ b/ui/src/components/community-form.tsx @@ -3,8 +3,10 @@ import { Subscription } from "rxjs"; import { retryWhen, delay, take } from 'rxjs/operators'; import { CommunityForm as CommunityFormI, UserOperation, Category, ListCategoriesResponse, CommunityResponse } from '../interfaces'; import { WebSocketService } from '../services'; -import { msgOp } from '../utils'; +import { msgOp, capitalizeFirstLetter } from '../utils'; import * as autosize from 'autosize'; +import { i18n } from '../i18next'; +import { T } from 'inferno-i18next'; import { Community } from '../interfaces'; @@ -74,25 +76,25 @@ export class CommunityForm extends Component<CommunityFormProps, CommunityFormSt return ( <form onSubmit={linkEvent(this, this.handleCreateCommunitySubmit)}> <div class="form-group row"> - <label class="col-12 col-form-label">Name</label> + <label class="col-12 col-form-label"><T i18nKey="name">#</T></label> <div class="col-12"> - <input type="text" class="form-control" value={this.state.communityForm.name} onInput={linkEvent(this, this.handleCommunityNameChange)} required minLength={3} maxLength={20} pattern="[a-z0-9_]+" title="lowercase, underscores, and no spaces."/> + <input type="text" class="form-control" value={this.state.communityForm.name} onInput={linkEvent(this, this.handleCommunityNameChange)} required minLength={3} maxLength={20} pattern="[a-z0-9_]+" title={i18n.t('community_reqs')}/> </div> </div> <div class="form-group row"> - <label class="col-12 col-form-label">Title</label> + <label class="col-12 col-form-label"><T i18nKey="title">#</T></label> <div class="col-12"> <input type="text" value={this.state.communityForm.title} onInput={linkEvent(this, this.handleCommunityTitleChange)} class="form-control" required minLength={3} maxLength={100} /> </div> </div> <div class="form-group row"> - <label class="col-12 col-form-label">Sidebar</label> + <label class="col-12 col-form-label"><T i18nKey="sidebar">#</T></label> <div class="col-12"> <textarea value={this.state.communityForm.description} onInput={linkEvent(this, this.handleCommunityDescriptionChange)} class="form-control" rows={3} maxLength={10000} /> </div> </div> <div class="form-group row"> - <label class="col-12 col-form-label">Category</label> + <label class="col-12 col-form-label"><T i18nKey="category">#</T></label> <div class="col-12"> <select class="form-control" value={this.state.communityForm.category_id} onInput={linkEvent(this, this.handleCommunityCategoryChange)}> {this.state.categories.map(category => @@ -106,8 +108,8 @@ export class CommunityForm extends Component<CommunityFormProps, CommunityFormSt <button type="submit" class="btn btn-secondary mr-2"> {this.state.loading ? <svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg> : - this.props.community ? 'Save' : 'Create'}</button> - {this.props.community && <button type="button" class="btn btn-secondary" onClick={linkEvent(this, this.handleCancel)}>Cancel</button>} + this.props.community ? capitalizeFirstLetter(i18n.t('save')) : capitalizeFirstLetter(i18n.t('create'))}</button> + {this.props.community && <button type="button" class="btn btn-secondary" onClick={linkEvent(this, this.handleCancel)}><T i18nKey="cancel">#</T></button>} </div> </div> </form> diff --git a/ui/src/components/community.tsx b/ui/src/components/community.tsx index 6a1f5da2..920b2eae 100644 --- a/ui/src/components/community.tsx +++ b/ui/src/components/community.tsx @@ -6,6 +6,7 @@ import { WebSocketService } from '../services'; import { PostListings } from './post-listings'; import { Sidebar } from './sidebar'; import { msgOp, routeSortTypeToEnum, fetchLimit } from '../utils'; +import { T } from 'inferno-i18next'; interface State { community: CommunityI; @@ -102,7 +103,7 @@ export class Community extends Component<any, State> { <div class="col-12 col-md-8"> <h5>{this.state.community.title} {this.state.community.removed && - <small className="ml-2 text-muted font-italic">removed</small> + <small className="ml-2 text-muted font-italic"><T i18nKey="removed">#</T></small> } </h5> {this.selects()} @@ -126,15 +127,15 @@ export class Community extends Component<any, State> { return ( <div className="mb-2"> <select value={this.state.sort} onChange={linkEvent(this, this.handleSortChange)} class="custom-select custom-select-sm w-auto"> - <option disabled>Sort Type</option> - <option value={SortType.Hot}>Hot</option> - <option value={SortType.New}>New</option> + <option disabled><T i18nKey="sort_type">#</T></option> + <option value={SortType.Hot}><T i18nKey="hot">#</T></option> + <option value={SortType.New}><T i18nKey="new">#</T></option> <option disabled>──────────</option> - <option value={SortType.TopDay}>Top Day</option> - <option value={SortType.TopWeek}>Week</option> - <option value={SortType.TopMonth}>Month</option> - <option value={SortType.TopYear}>Year</option> - <option value={SortType.TopAll}>All</option> + <option value={SortType.TopDay}><T i18nKey="top_day">#</T></option> + <option value={SortType.TopWeek}><T i18nKey="week">#</T></option> + <option value={SortType.TopMonth}><T i18nKey="month">#</T></option> + <option value={SortType.TopYear}><T i18nKey="year">#</T></option> + <option value={SortType.TopAll}><T i18nKey="all">#</T></option> </select> </div> ) @@ -144,9 +145,9 @@ export class Community extends Component<any, State> { return ( <div class="mt-2"> {this.state.page > 1 && - <button class="btn btn-sm btn-secondary mr-1" onClick={linkEvent(this, this.prevPage)}>Prev</button> + <button class="btn btn-sm btn-secondary mr-1" onClick={linkEvent(this, this.prevPage)}><T i18nKey="prev">#</T></button> } - <button class="btn btn-sm btn-secondary" onClick={linkEvent(this, this.nextPage)}>Next</button> + <button class="btn btn-sm btn-secondary" onClick={linkEvent(this, this.nextPage)}><T i18nKey="next">#</T></button> </div> ); } diff --git a/ui/src/components/create-community.tsx b/ui/src/components/create-community.tsx index c2f89eef..61245e73 100644 --- a/ui/src/components/create-community.tsx +++ b/ui/src/components/create-community.tsx @@ -2,6 +2,8 @@ import { Component } from 'inferno'; import { CommunityForm } from './community-form'; import { Community } from '../interfaces'; import { WebSocketService } from '../services'; +import { i18n } from '../i18next'; +import { T } from 'inferno-i18next'; export class CreateCommunity extends Component<any, any> { @@ -11,7 +13,7 @@ export class CreateCommunity extends Component<any, any> { } componentDidMount() { - document.title = `Create Community - ${WebSocketService.Instance.site.name}`; + document.title = `${i18n.t('create_community')} - ${WebSocketService.Instance.site.name}`; } render() { @@ -19,7 +21,7 @@ export class CreateCommunity extends Component<any, any> { <div class="container"> <div class="row"> <div class="col-12 col-lg-6 offset-lg-3 mb-4"> - <h5>Create Community</h5> + <h5><T i18nKey="create_community">#</T></h5> <CommunityForm onCreate={this.handleCommunityCreate}/> </div> </div> diff --git a/ui/src/components/create-post.tsx b/ui/src/components/create-post.tsx index e09bcf70..dd93a3c5 100644 --- a/ui/src/components/create-post.tsx +++ b/ui/src/components/create-post.tsx @@ -1,6 +1,8 @@ import { Component } from 'inferno'; import { PostForm } from './post-form'; import { WebSocketService } from '../services'; +import { i18n } from '../i18next'; +import { T } from 'inferno-i18next'; export class CreatePost extends Component<any, any> { @@ -10,7 +12,7 @@ export class CreatePost extends Component<any, any> { } componentDidMount() { - document.title = `Create Post - ${WebSocketService.Instance.site.name}`; + document.title = `${i18n.t('create_post')} - ${WebSocketService.Instance.site.name}`; } render() { @@ -18,7 +20,7 @@ export class CreatePost extends Component<any, any> { <div class="container"> <div class="row"> <div class="col-12 col-lg-6 offset-lg-3 mb-4"> - <h5>Create a Post</h5> + <h5><T i18nKey="create_post">#</T></h5> <PostForm onCreate={this.handlePostCreate} prevCommunityName={this.prevCommunityName} /> </div> </div> diff --git a/ui/src/components/footer.tsx b/ui/src/components/footer.tsx index 31403d7c..87d7097e 100644 --- a/ui/src/components/footer.tsx +++ b/ui/src/components/footer.tsx @@ -2,6 +2,7 @@ import { Component } from 'inferno'; import { Link } from 'inferno-router'; import { repoUrl } from '../utils'; import { version } from '../version'; +import { T } from 'inferno-i18next'; export class Footer extends Component<any, any> { @@ -19,16 +20,16 @@ export class Footer extends Component<any, any> { <span class="navbar-text">{version}</span> </li> <li class="nav-item"> - <Link class="nav-link" to="/modlog">Modlog</Link> + <Link class="nav-link" to="/modlog"><T i18nKey="modlog">#</T></Link> </li> <li class="nav-item"> - <a class="nav-link" href={`${repoUrl}/blob/master/docs/api.md`}>API</a> + <a class="nav-link" href={`${repoUrl}/blob/master/docs/api.md`}><T i18nKey="api">#</T></a> </li> <li class="nav-item"> - <Link class="nav-link" to="/sponsors">Sponsors</Link> + <Link class="nav-link" to="/sponsors"><T i18nKey="sponsors">#</T></Link> </li> <li class="nav-item"> - <a class="nav-link" href={repoUrl}>Code</a> + <a class="nav-link" href={repoUrl}><T i18nKey="code">#</T></a> </li> </ul> </div> diff --git a/ui/src/components/inbox.tsx b/ui/src/components/inbox.tsx index 5fb7f874..1b5b1b88 100644 --- a/ui/src/components/inbox.tsx +++ b/ui/src/components/inbox.tsx @@ -6,6 +6,8 @@ import { UserOperation, Comment, SortType, GetRepliesForm, GetRepliesResponse, C import { WebSocketService, UserService } from '../services'; import { msgOp } from '../utils'; import { CommentNodes } from './comment-nodes'; +import { i18n } from '../i18next'; +import { T } from 'inferno-i18next'; enum UnreadType { Unread, All @@ -49,7 +51,7 @@ export class Inbox extends Component<any, InboxState> { } componentDidMount() { - document.title = `/u/${UserService.Instance.user.username} Inbox - ${WebSocketService.Instance.site.name}`; + document.title = `/u/${UserService.Instance.user.username} ${i18n.t('inbox')} - ${WebSocketService.Instance.site.name}`; } render() { @@ -59,12 +61,12 @@ export class Inbox extends Component<any, InboxState> { <div class="row"> <div class="col-12"> <h5 class="mb-0"> - <span>Inbox for <Link to={`/u/${user.username}`}>{user.username}</Link></span> + <span><T i18nKey="inbox_for" interpolation={{user: user.username}}>#<Link to={`/u/${user.username}`}>#</Link></T></span> </h5> {this.state.replies.length > 0 && this.state.unreadType == UnreadType.Unread && <ul class="list-inline mb-1 text-muted small font-weight-bold"> <li className="list-inline-item"> - <span class="pointer" onClick={this.markAllAsRead}>mark all as read</span> + <span class="pointer" onClick={this.markAllAsRead}><T i18nKey="mark_all_as_read">#</T></span> </li> </ul> } @@ -81,18 +83,18 @@ export class Inbox extends Component<any, InboxState> { return ( <div className="mb-2"> <select value={this.state.unreadType} onChange={linkEvent(this, this.handleUnreadTypeChange)} class="custom-select custom-select-sm w-auto"> - <option disabled>Type</option> - <option value={UnreadType.Unread}>Unread</option> - <option value={UnreadType.All}>All</option> + <option disabled><T i18nKey="type">#</T></option> + <option value={UnreadType.Unread}><T i18nKey="unread">#</T></option> + <option value={UnreadType.All}><T i18nKey="all">#</T></option> </select> <select value={this.state.sort} onChange={linkEvent(this, this.handleSortChange)} class="custom-select custom-select-sm w-auto ml-2"> - <option disabled>Sort Type</option> - <option value={SortType.New}>New</option> - <option value={SortType.TopDay}>Top Day</option> - <option value={SortType.TopWeek}>Week</option> - <option value={SortType.TopMonth}>Month</option> - <option value={SortType.TopYear}>Year</option> - <option value={SortType.TopAll}>All</option> + <option disabled><T i18nKey="sort_type">#</T></option> + <option value={SortType.New}><T i18nKey="new">#</T></option> + <option value={SortType.TopDay}><T i18nKey="top_day">top_day</T></option> + <option value={SortType.TopWeek}><T i18nKey="week">#</T></option> + <option value={SortType.TopMonth}><T i18nKey="month">#</T></option> + <option value={SortType.TopYear}><T i18nKey="year">#</T></option> + <option value={SortType.TopAll}><T i18nKey="all">#</T></option> </select> </div> ) @@ -113,9 +115,9 @@ export class Inbox extends Component<any, InboxState> { return ( <div class="mt-2"> {this.state.page > 1 && - <button class="btn btn-sm btn-secondary mr-1" onClick={linkEvent(this, this.prevPage)}>Prev</button> + <button class="btn btn-sm btn-secondary mr-1" onClick={linkEvent(this, this.prevPage)}><T i18nKey="prev">#</T></button> } - <button class="btn btn-sm btn-secondary" onClick={linkEvent(this, this.nextPage)}>Next</button> + <button class="btn btn-sm btn-secondary" onClick={linkEvent(this, this.nextPage)}><T i18nKey="next">#</T></button> </div> ); } @@ -196,7 +198,7 @@ export class Inbox extends Component<any, InboxState> { this.setState(this.state); } else if (op == UserOperation.CreateComment) { // let res: CommentResponse = msg; - alert('Reply sent'); + alert(i18n.t('reply_sent')); // this.state.replies.unshift(res.comment); // TODO do this right // this.setState(this.state); } else if (op == UserOperation.SaveComment) { diff --git a/ui/src/components/login.tsx b/ui/src/components/login.tsx index 6eb88438..e5386aa9 100644 --- a/ui/src/components/login.tsx +++ b/ui/src/components/login.tsx @@ -4,6 +4,8 @@ import { retryWhen, delay, take } from 'rxjs/operators'; import { LoginForm, RegisterForm, LoginResponse, UserOperation } from '../interfaces'; import { WebSocketService, UserService } from '../services'; import { msgOp } from '../utils'; +import { i18n } from '../i18next'; +import { T } from 'inferno-i18next'; interface State { loginForm: LoginForm; @@ -74,13 +76,13 @@ export class Login extends Component<any, State> { <form onSubmit={linkEvent(this, this.handleLoginSubmit)}> <h5>Login</h5> <div class="form-group row"> - <label class="col-sm-2 col-form-label">Email or Username</label> + <label class="col-sm-2 col-form-label"><T i18nKey="email_or_username">#</T></label> <div class="col-sm-10"> <input type="text" class="form-control" value={this.state.loginForm.username_or_email} onInput={linkEvent(this, this.handleLoginUsernameChange)} required minLength={3} /> </div> </div> <div class="form-group row"> - <label class="col-sm-2 col-form-label">Password</label> + <label class="col-sm-2 col-form-label"><T i18nKey="password">#</T></label> <div class="col-sm-10"> <input type="password" value={this.state.loginForm.password} onInput={linkEvent(this, this.handleLoginPasswordChange)} class="form-control" required /> </div> @@ -88,38 +90,37 @@ export class Login extends Component<any, State> { <div class="form-group row"> <div class="col-sm-10"> <button type="submit" class="btn btn-secondary">{this.state.loginLoading ? - <svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg> : 'Login'}</button> + <svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg> : i18n.t('login')}</button> </div> </div> </form> - {/* Forgot your password or deleted your account? Reset your password. TODO */} </div> ); } registerForm() { return ( <form onSubmit={linkEvent(this, this.handleRegisterSubmit)}> - <h5>Sign Up</h5> + <h5><T i18nKey="sign_up">#</T></h5> <div class="form-group row"> - <label class="col-sm-2 col-form-label">Username</label> + <label class="col-sm-2 col-form-label"><T i18nKey="username">#</T></label> <div class="col-sm-10"> <input type="text" class="form-control" value={this.state.registerForm.username} onInput={linkEvent(this, this.handleRegisterUsernameChange)} required minLength={3} maxLength={20} pattern="[a-zA-Z0-9_]+" /> </div> </div> <div class="form-group row"> - <label class="col-sm-2 col-form-label">Email</label> + <label class="col-sm-2 col-form-label"><T i18nKey="email">#</T></label> <div class="col-sm-10"> - <input type="email" class="form-control" placeholder="Optional" value={this.state.registerForm.email} onInput={linkEvent(this, this.handleRegisterEmailChange)} minLength={3} /> + <input type="email" class="form-control" placeholder={i18n.t('optional')} value={this.state.registerForm.email} onInput={linkEvent(this, this.handleRegisterEmailChange)} minLength={3} /> </div> </div> <div class="form-group row"> - <label class="col-sm-2 col-form-label">Password</label> + <label class="col-sm-2 col-form-label"><T i18nKey="password">#</T></label> <div class="col-sm-10"> <input type="password" value={this.state.registerForm.password} onInput={linkEvent(this, this.handleRegisterPasswordChange)} class="form-control" required /> </div> </div> <div class="form-group row"> - <label class="col-sm-2 col-form-label">Verify Password</label> + <label class="col-sm-2 col-form-label"><T i18nKey="verify_password">#</T></label> <div class="col-sm-10"> <input type="password" value={this.state.registerForm.password_verify} onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)} class="form-control" required /> </div> @@ -127,7 +128,7 @@ export class Login extends Component<any, State> { <div class="form-group row"> <div class="col-sm-10"> <button type="submit" class="btn btn-secondary">{this.state.registerLoading ? - <svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg> : 'Sign Up'}</button> + <svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg> : i18n.t('sign_up')}</button> </div> </div> diff --git a/ui/src/components/moment-time.tsx b/ui/src/components/moment-time.tsx index c8826695..c6c7c99d 100644 --- a/ui/src/components/moment-time.tsx +++ b/ui/src/components/moment-time.tsx @@ -1,5 +1,6 @@ import { Component } from 'inferno'; import * as moment from 'moment'; +import { i18n } from '../i18next'; interface MomentTimeProps { data: { @@ -18,7 +19,7 @@ export class MomentTime extends Component<MomentTimeProps, any> { render() { if (this.props.data.updated) { return ( - <span title={this.props.data.updated} className="font-italics">modified {moment.utc(this.props.data.updated).fromNow()}</span> + <span title={this.props.data.updated} className="font-italics">{i18n.t('modified')} {moment.utc(this.props.data.updated).fromNow()}</span> ) } else { let str = this.props.data.published || this.props.data.when_; diff --git a/ui/src/components/navbar.tsx b/ui/src/components/navbar.tsx index 68e486c1..ac23e361 100644 --- a/ui/src/components/navbar.tsx +++ b/ui/src/components/navbar.tsx @@ -6,6 +6,8 @@ import { WebSocketService, UserService } from '../services'; import { UserOperation, GetRepliesForm, GetRepliesResponse, SortType, GetSiteResponse, Comment} from '../interfaces'; import { msgOp } from '../utils'; import { version } from '../version'; +import { i18n } from '../i18next'; +import { T } from 'inferno-i18next'; interface NavbarState { isLoggedIn: boolean; @@ -85,16 +87,16 @@ export class Navbar extends Component<any, NavbarState> { <div className={`${!this.state.expanded && 'collapse'} navbar-collapse`}> <ul class="navbar-nav mr-auto"> <li class="nav-item"> - <Link class="nav-link" to="/communities">Communities</Link> + <Link class="nav-link" to="/communities"><T i18nKey="communities">#</T></Link> </li> <li class="nav-item"> - <Link class="nav-link" to="/search">Search</Link> + <Link class="nav-link" to="/search"><T i18nKey="search">#</T></Link> </li> <li class="nav-item"> - <Link class="nav-link" to={{pathname: '/create_post', state: { prevPath: this.currentLocation }}}>Create Post</Link> + <Link class="nav-link" to={{pathname: '/create_post', state: { prevPath: this.currentLocation }}}><T i18nKey="create_post">#</T></Link> </li> <li class="nav-item"> - <Link class="nav-link" to="/create_community">Create Community</Link> + <Link class="nav-link" to="/create_community"><T i18nKey="create_community">#</T></Link> </li> </ul> <ul class="navbar-nav ml-auto mr-2"> @@ -113,13 +115,13 @@ export class Navbar extends Component<any, NavbarState> { {UserService.Instance.user.username} </a> <div className={`dropdown-menu dropdown-menu-right ${this.state.expandUserDropdown && 'show'}`}> - <a role="button" class="dropdown-item pointer" onClick={linkEvent(this, this.handleOverviewClick)}>Overview</a> - <a role="button" class="dropdown-item pointer" onClick={ linkEvent(this, this.handleLogoutClick) }>Logout</a> + <a role="button" class="dropdown-item pointer" onClick={linkEvent(this, this.handleOverviewClick)}><T i18nKey="overview">#</T></a> + <a role="button" class="dropdown-item pointer" onClick={ linkEvent(this, this.handleLogoutClick) }><T i18nKey="logout">#</T></a> </div> </li> </> : - <Link class="nav-link" to="/login">Login / Sign up</Link> + <Link class="nav-link" to="/login"><T i18nKey="login_sign_up">#</T></Link> } </ul> </div> @@ -209,7 +211,7 @@ export class Navbar extends Component<any, NavbarState> { if (UserService.Instance.user) { document.addEventListener('DOMContentLoaded', function () { if (!Notification) { - alert('Desktop notifications not available in your browser. Try Chromium.'); + alert(i18n.t('notifications_error')); return; } @@ -224,7 +226,7 @@ export class Navbar extends Component<any, NavbarState> { if (Notification.permission !== 'granted') Notification.requestPermission(); else { - var notification = new Notification(`${replies.length} Unread Messages`, { + var notification = new Notification(`${replies.length} ${i18n.t('unread_messages')}`, { icon: `${window.location.protocol}//${window.location.host}/static/assets/apple-touch-icon.png`, body: `${recentReply.creator_name}: ${recentReply.content}` }); diff --git a/ui/src/components/post-form.tsx b/ui/src/components/post-form.tsx index 54b3ca44..bfc861bd 100644 --- a/ui/src/components/post-form.tsx +++ b/ui/src/components/post-form.tsx @@ -4,8 +4,10 @@ import { Subscription } from "rxjs"; import { retryWhen, delay, take } from 'rxjs/operators'; import { PostForm as PostFormI, Post, PostResponse, UserOperation, Community, ListCommunitiesResponse, ListCommunitiesForm, SortType, SearchForm, SearchType, SearchResponse } from '../interfaces'; import { WebSocketService, UserService } from '../services'; -import { msgOp, getPageTitle, debounce } from '../utils'; +import { msgOp, getPageTitle, debounce, capitalizeFirstLetter } from '../utils'; import * as autosize from 'autosize'; +import { i18n } from '../i18next'; +import { T } from 'inferno-i18next'; interface PostFormProps { post?: Post; // If a post is given, that means this is an edit @@ -85,28 +87,28 @@ export class PostForm extends Component<PostFormProps, PostFormState> { <div> <form onSubmit={linkEvent(this, this.handlePostSubmit)}> <div class="form-group row"> - <label class="col-sm-2 col-form-label">URL</label> + <label class="col-sm-2 col-form-label"><T i18nKey="url">#</T></label> <div class="col-sm-10"> <input type="url" class="form-control" value={this.state.postForm.url} onInput={linkEvent(this, debounce(this.handlePostUrlChange))} /> {this.state.suggestedTitle && - <div class="mt-1 text-muted small font-weight-bold pointer" onClick={linkEvent(this, this.copySuggestedTitle)}>copy suggested title: {this.state.suggestedTitle}</div> + <div class="mt-1 text-muted small font-weight-bold pointer" onClick={linkEvent(this, this.copySuggestedTitle)}><T i18nKey="copy_suggested_title" interpolation={{title: this.state.suggestedTitle}}>#</T></div> } </div> </div> <div class="form-group row"> - <label class="col-sm-2 col-form-label">Title</label> + <label class="col-sm-2 col-form-label"><T i18nKey="title">#</T></label> <div class="col-sm-10"> <textarea value={this.state.postForm.name} onInput={linkEvent(this, debounce(this.handlePostNameChange))} class="form-control" required rows={2} minLength={3} maxLength={100} /> {this.state.suggestedPosts.length > 0 && <> - <div class="my-1 text-muted small font-weight-bold">These posts might be related</div> + <div class="my-1 text-muted small font-weight-bold"><T i18nKey="related_posts">#</T></div> <PostListings posts={this.state.suggestedPosts} /> </> } </div> </div> <div class="form-group row"> - <label class="col-sm-2 col-form-label">Body</label> + <label class="col-sm-2 col-form-label"><T i18nKey="body">#</T></label> <div class="col-sm-10"> <textarea value={this.state.postForm.body} onInput={linkEvent(this, this.handlePostBodyChange)} class="form-control" rows={4} maxLength={10000} /> </div> @@ -114,7 +116,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> { {/* Cant change a community from an edit */} {!this.props.post && <div class="form-group row"> - <label class="col-sm-2 col-form-label">Community</label> + <label class="col-sm-2 col-form-label"><T i18nKey="community">#</T></label> <div class="col-sm-10"> <select class="form-control" value={this.state.postForm.community_id} onInput={linkEvent(this, this.handlePostCommunityChange)}> {this.state.communities.map(community => @@ -129,8 +131,8 @@ export class PostForm extends Component<PostFormProps, PostFormState> { <button type="submit" class="btn btn-secondary mr-2"> {this.state.loading ? <svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg> : - this.props.post ? 'Save' : 'Create'}</button> - {this.props.post && <button type="button" class="btn btn-secondary" onClick={linkEvent(this, this.handleCancel)}>Cancel</button>} + this.props.post ? capitalizeFirstLetter(i18n.t('save')) : capitalizeFirstLetter(i18n.t('Create'))}</button> + {this.props.post && <button type="button" class="btn btn-secondary" onClick={linkEvent(this, this.handleCancel)}><T i18nKey="cancel">#</T></button>} </div> </div> </form> diff --git a/ui/src/components/post-listing.tsx b/ui/src/components/post-listing.tsx index 6727dd09..dc8f4cfa 100644 --- a/ui/src/components/post-listing.tsx +++ b/ui/src/components/post-listing.tsx @@ -5,6 +5,8 @@ import { Post, CreatePostLikeForm, PostForm as PostFormI, SavePostForm, Communit import { MomentTime } from './moment-time'; import { PostForm } from './post-form'; import { mdToHtml, canMod, isMod, isImage } from '../utils'; +import { i18n } from '../i18next'; +import { T } from 'inferno-i18next'; interface PostListingState { showEdit: boolean; @@ -67,7 +69,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> { </div> </div> {post.url && isImage(post.url) && - <span title="Expand here" class="pointer" onClick={linkEvent(this, this.handleImageExpandClick)}><img class="mx-2 float-left img-fluid thumbnail rounded" src={post.url} /></span> + <span title={i18n.t('expand_here')} class="pointer" onClick={linkEvent(this, this.handleImageExpandClick)}><img class="mx-2 float-left img-fluid thumbnail rounded" src={post.url} /></span> } <div className="ml-4"> <div> @@ -83,18 +85,18 @@ export class PostListing extends Component<PostListingProps, PostListingState> { </small> } {post.removed && - <small className="ml-2 text-muted font-italic">removed</small> + <small className="ml-2 text-muted font-italic"><T i18nKey="removed">#</T></small> } {post.deleted && - <small className="ml-2 text-muted font-italic">deleted</small> + <small className="ml-2 text-muted font-italic"><T i18nKey="deleted">#</T></small> } {post.locked && - <small className="ml-2 text-muted font-italic">locked</small> + <small className="ml-2 text-muted font-italic"><T i18nKey="locked">#</T></small> } { post.url && isImage(post.url) && <> { !this.state.imageExpanded - ? <span class="text-monospace pointer ml-2 text-muted small" title="Expand here" onClick={linkEvent(this, this.handleImageExpandClick)}>[+]</span> + ? <span class="text-monospace pointer ml-2 text-muted small" title={i18n.t('expand_here')} onClick={linkEvent(this, this.handleImageExpandClick)}>[+]</span> : <span> <span class="text-monospace pointer ml-2 text-muted small" onClick={linkEvent(this, this.handleImageExpandClick)}>[-]</span> @@ -113,10 +115,10 @@ export class PostListing extends Component<PostListingProps, PostListingState> { <span>by </span> <Link className="text-info" to={`/u/${post.creator_name}`}>{post.creator_name}</Link> {this.isMod && - <span className="mx-1 badge badge-light">mod</span> + <span className="mx-1 badge badge-light"><T i18nKey="mod">#</T></span> } {this.isAdmin && - <span className="mx-1 badge badge-light">admin</span> + <span className="mx-1 badge badge-light"><T i18nKey="admin">#</T></span> } {this.props.showCommunity && <span> @@ -137,22 +139,22 @@ export class PostListing extends Component<PostListingProps, PostListingState> { </span> </li> <li className="list-inline-item"> - <Link className="text-muted" to={`/post/${post.id}`}>{post.number_of_comments} Comments</Link> + <Link className="text-muted" to={`/post/${post.id}`}><T i18nKey="number_of_comments" interpolation={{count: post.number_of_comments}}>#</T></Link> </li> </ul> {UserService.Instance.user && this.props.editable && <ul class="list-inline mb-1 text-muted small font-weight-bold"> <li className="list-inline-item mr-2"> - <span class="pointer" onClick={linkEvent(this, this.handleSavePostClick)}>{post.saved ? 'unsave' : 'save'}</span> + <span class="pointer" onClick={linkEvent(this, this.handleSavePostClick)}>{post.saved ? i18n.t('unsave') : i18n.t('save')}</span> </li> {this.myPost && <> <li className="list-inline-item"> - <span class="pointer" onClick={linkEvent(this, this.handleEditClick)}>edit</span> + <span class="pointer" onClick={linkEvent(this, this.handleEditClick)}><T i18nKey="edit">#</T></span> </li> <li className="list-inline-item mr-2"> <span class="pointer" onClick={linkEvent(this, this.handleDeleteClick)}> - {!post.deleted ? 'delete' : 'restore'} + {!post.deleted ? i18n.t('delete') : i18n.t('restore')} </span> </li> </> @@ -161,12 +163,12 @@ export class PostListing extends Component<PostListingProps, PostListingState> { <span> <li className="list-inline-item"> {!this.props.post.removed ? - <span class="pointer" onClick={linkEvent(this, this.handleModRemoveShow)}>remove</span> : - <span class="pointer" onClick={linkEvent(this, this.handleModRemoveSubmit)}>restore</span> + <span class="pointer" onClick={linkEvent(this, this.handleModRemoveShow)}><T i18nKey="remove">#</T></span> : + <span class="pointer" onClick={linkEvent(this, this.handleModRemoveSubmit)}><T i18nKey="restore">#</T></span> } </li> <li className="list-inline-item"> - <span class="pointer" onClick={linkEvent(this, this.handleModLock)}>{this.props.post.locked ? 'unlock' : 'lock'}</span> + <span class="pointer" onClick={linkEvent(this, this.handleModLock)}>{this.props.post.locked ? i18n.t('unlock') : i18n.t('lock')}</span> </li> </span> } @@ -175,7 +177,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> { {this.state.showRemoveDialog && <form class="form-inline" onSubmit={linkEvent(this, this.handleModRemoveSubmit)}> <input type="text" class="form-control mr-2" placeholder="Reason" value={this.state.removeReason} onInput={linkEvent(this, this.handleModRemoveReasonChange)} /> - <button type="submit" class="btn btn-secondary">Remove Post</button> + <button type="submit" class="btn btn-secondary"><T i18nKey="remove_post">#</T></button> </form> } {this.props.showBody && this.props.post.body && <div className="md-div" dangerouslySetInnerHTML={mdToHtml(post.body)} />} diff --git a/ui/src/components/post-listings.tsx b/ui/src/components/post-listings.tsx index 93b2f606..f5682a7e 100644 --- a/ui/src/components/post-listings.tsx +++ b/ui/src/components/post-listings.tsx @@ -2,6 +2,7 @@ import { Component } from 'inferno'; import { Link } from 'inferno-router'; import { Post } from '../interfaces'; import { PostListing } from './post-listing'; +import { T } from 'inferno-i18next'; interface PostListingsProps { posts: Array<Post>; @@ -19,8 +20,10 @@ export class PostListings extends Component<PostListingsProps, any> { <div> {this.props.posts.length > 0 ? this.props.posts.map(post => <PostListing post={post} showCommunity={this.props.showCommunity} />) : - <div>No posts. {this.props.showCommunity !== undefined && <span>Subscribe to some <Link to="/communities">communities</Link>.</span>} - </div> + <> + <div><T i18nKey="no_posts">#</T></div> + {this.props.showCommunity !== undefined && <div><T i18nKey="subscribe_to_communities">#<Link to="/communities">#</Link></T></div>} + </> } </div> ) diff --git a/ui/src/components/post.tsx b/ui/src/components/post.tsx index 7152941f..f582f0d7 100644 --- a/ui/src/components/post.tsx +++ b/ui/src/components/post.tsx @@ -9,6 +9,8 @@ import { Sidebar } from './sidebar'; import { CommentForm } from './comment-form'; import { CommentNodes } from './comment-nodes'; import * as autosize from 'autosize'; +import { i18n } from '../i18next'; +import { T } from 'inferno-i18next'; interface PostState { post: PostI; @@ -130,17 +132,17 @@ export class Post extends Component<any, PostState> { sortRadios() { return ( <div class="btn-group btn-group-toggle mb-3"> - <label className={`btn btn-sm btn-secondary pointer ${this.state.commentSort === CommentSortType.Hot && 'active'}`}>Hot + <label className={`btn btn-sm btn-secondary pointer ${this.state.commentSort === CommentSortType.Hot && 'active'}`}>{i18n.t('hot')} <input type="radio" value={CommentSortType.Hot} checked={this.state.commentSort === CommentSortType.Hot} onChange={linkEvent(this, this.handleCommentSortChange)} /> </label> - <label className={`btn btn-sm btn-secondary pointer ${this.state.commentSort === CommentSortType.Top && 'active'}`}>Top + <label className={`btn btn-sm btn-secondary pointer ${this.state.commentSort === CommentSortType.Top && 'active'}`}>{i18n.t('top')} <input type="radio" value={CommentSortType.Top} checked={this.state.commentSort === CommentSortType.Top} onChange={linkEvent(this, this.handleCommentSortChange)} /> </label> - <label className={`btn btn-sm btn-secondary pointer ${this.state.commentSort === CommentSortType.New && 'active'}`}>New + <label className={`btn btn-sm btn-secondary pointer ${this.state.commentSort === CommentSortType.New && 'active'}`}>{i18n.t('new')} <input type="radio" value={CommentSortType.New} checked={this.state.commentSort === CommentSortType.New} onChange={linkEvent(this, this.handleCommentSortChange)} /> @@ -152,7 +154,7 @@ export class Post extends Component<any, PostState> { newComments() { return ( <div class="container-fluid sticky-top new-comments"> - <h5>Chat</h5> + <h5><T i18nKey="chat">#</T></h5> <CommentForm postId={this.state.post.id} disabled={this.state.post.locked} /> {this.state.comments.map(comment => <CommentNodes diff --git a/ui/src/components/search.tsx b/ui/src/components/search.tsx index ec657bb1..67202dba 100644 --- a/ui/src/components/search.tsx +++ b/ui/src/components/search.tsx @@ -6,6 +6,8 @@ import { WebSocketService } from '../services'; import { msgOp, fetchLimit } from '../utils'; import { PostListing } from './post-listing'; import { CommentNodes } from './comment-nodes'; +import { i18n } from '../i18next'; +import { T } from 'inferno-i18next'; interface SearchState { q: string, @@ -87,7 +89,7 @@ export class Search extends Component<any, SearchState> { <button type="submit" class="btn btn-secondary mr-2"> {this.state.loading ? <svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg> : - <span>Search</span> + <span><T i18nKey="search">#</T></span> } </button> </form> @@ -98,19 +100,19 @@ export class Search extends Component<any, SearchState> { return ( <div className="mb-2"> <select value={this.state.type_} onChange={linkEvent(this, this.handleTypeChange)} class="custom-select custom-select-sm w-auto"> - <option disabled>Type</option> - <option value={SearchType.Both}>Both</option> - <option value={SearchType.Comments}>Comments</option> - <option value={SearchType.Posts}>Posts</option> + <option disabled><T i18nKey="type">#</T></option> + <option value={SearchType.Both}><T i18nKey="both">#</T></option> + <option value={SearchType.Comments}><T i18nKey="comments">#</T></option> + <option value={SearchType.Posts}><T i18nKey="posts">#</T></option> </select> <select value={this.state.sort} onChange={linkEvent(this, this.handleSortChange)} class="custom-select custom-select-sm w-auto ml-2"> - <option disabled>Sort Type</option> - <option value={SortType.New}>New</option> - <option value={SortType.TopDay}>Top Day</option> - <option value={SortType.TopWeek}>Week</option> - <option value={SortType.TopMonth}>Month</option> - <option value={SortType.TopYear}>Year</option> - <option value={SortType.TopAll}>All</option> + <option disabled><T i18nKey="sort_type">#</T></option> + <option value={SortType.New}><T i18nKey="new">#</T></option> + <option value={SortType.TopDay}><T i18nKey="top_day">#</T></option> + <option value={SortType.TopWeek}><T i18nKey="week">#</T></option> + <option value={SortType.TopMonth}><T i18nKey="month">#</T></option> + <option value={SortType.TopYear}><T i18nKey="year">#</T></option> + <option value={SortType.TopAll}><T i18nKey="all">#</T></option> </select> </div> ) @@ -171,9 +173,9 @@ export class Search extends Component<any, SearchState> { return ( <div class="mt-2"> {this.state.page > 1 && - <button class="btn btn-sm btn-secondary mr-1" onClick={linkEvent(this, this.prevPage)}>Prev</button> + <button class="btn btn-sm btn-secondary mr-1" onClick={linkEvent(this, this.prevPage)}><T i18nKey="prev">#</T></button> } - <button class="btn btn-sm btn-secondary" onClick={linkEvent(this, this.nextPage)}>Next</button> + <button class="btn btn-sm btn-secondary" onClick={linkEvent(this, this.nextPage)}><T i18nKey="next">#</T></button> </div> ); } @@ -183,7 +185,7 @@ export class Search extends Component<any, SearchState> { return ( <div> {res && res.op && res.posts.length == 0 && res.comments.length == 0 && - <span>No Results</span> + <span><T i18nKey="no_results">#</T></span> } </div> ) @@ -250,7 +252,7 @@ export class Search extends Component<any, SearchState> { let res: SearchResponse = msg; this.state.searchResponse = res; this.state.loading = false; - document.title = `Search - ${this.state.q} - ${WebSocketService.Instance.site.name}`; + document.title = `${i18n.t('search')} - ${this.state.q} - ${WebSocketService.Instance.site.name}`; window.scrollTo(0,0); this.setState(this.state); } diff --git a/ui/src/components/setup.tsx b/ui/src/components/setup.tsx index edb98260..fbfb2e13 100644 --- a/ui/src/components/setup.tsx +++ b/ui/src/components/setup.tsx @@ -5,6 +5,8 @@ import { RegisterForm, LoginResponse, UserOperation } from '../interfaces'; import { WebSocketService, UserService } from '../services'; import { msgOp } from '../utils'; import { SiteForm } from './site-form'; +import { i18n } from '../i18next'; +import { T } from 'inferno-i18next'; interface State { userForm: RegisterForm; @@ -46,7 +48,7 @@ export class Setup extends Component<any, State> { } componentDidMount() { - document.title = "Setup - Lemmy"; + document.title = `${i18n.t('setup')} - Lemmy`; } render() { @@ -54,7 +56,7 @@ export class Setup extends Component<any, State> { <div class="container"> <div class="row"> <div class="col-12 offset-lg-3 col-lg-6"> - <h3>Lemmy Instance Setup</h3> + <h3><T i18nKey="lemmy_instance_setup">#</T></h3> {!this.state.doneRegisteringUser ? this.registerUser() : <SiteForm />} </div> </div> @@ -65,27 +67,27 @@ export class Setup extends Component<any, State> { registerUser() { return ( <form onSubmit={linkEvent(this, this.handleRegisterSubmit)}> - <h5>Set up Site Administrator</h5> + <h5><T i18nKey="setup_admin">#</T></h5> <div class="form-group row"> - <label class="col-sm-2 col-form-label">Username</label> + <label class="col-sm-2 col-form-label"><T i18nKey="username">#</T></label> <div class="col-sm-10"> <input type="text" class="form-control" value={this.state.userForm.username} onInput={linkEvent(this, this.handleRegisterUsernameChange)} required minLength={3} maxLength={20} pattern="[a-zA-Z0-9_]+" /> </div> </div> <div class="form-group row"> - <label class="col-sm-2 col-form-label">Email</label> + <label class="col-sm-2 col-form-label"><T i18nKey="email">#</T></label> <div class="col-sm-10"> - <input type="email" class="form-control" placeholder="Optional" value={this.state.userForm.email} onInput={linkEvent(this, this.handleRegisterEmailChange)} minLength={3} /> + <input type="email" class="form-control" placeholder={i18n.t('optional')} value={this.state.userForm.email} onInput={linkEvent(this, this.handleRegisterEmailChange)} minLength={3} /> </div> </div> <div class="form-group row"> - <label class="col-sm-2 col-form-label">Password</label> + <label class="col-sm-2 col-form-label"><T i18nKey="password">#</T></label> <div class="col-sm-10"> <input type="password" value={this.state.userForm.password} onInput={linkEvent(this, this.handleRegisterPasswordChange)} class="form-control" required /> </div> </div> <div class="form-group row"> - <label class="col-sm-2 col-form-label">Verify Password</label> + <label class="col-sm-2 col-form-label"><T i18nKey="verify_password">#</T></label> <div class="col-sm-10"> <input type="password" value={this.state.userForm.password_verify} onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)} class="form-control" required /> </div> @@ -93,7 +95,7 @@ export class Setup extends Component<any, State> { <div class="form-group row"> <div class="col-sm-10"> <button type="submit" class="btn btn-secondary">{this.state.userLoading ? - <svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg> : 'Sign Up'}</button> + <svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg> : i18n.t('sign_up')}</button> </div> </div> diff --git a/ui/src/components/sidebar.tsx b/ui/src/components/sidebar.tsx index d36d962c..900c5477 100644 --- a/ui/src/components/sidebar.tsx +++ b/ui/src/components/sidebar.tsx @@ -4,6 +4,8 @@ import { Community, CommunityUser, FollowCommunityForm, CommunityForm as Communi import { WebSocketService, UserService } from '../services'; import { mdToHtml, getUnixTime } from '../utils'; import { CommunityForm } from './community-form'; +import { i18n } from '../i18next'; +import { T } from 'inferno-i18next'; interface SidebarProps { community: Community; @@ -54,10 +56,10 @@ export class Sidebar extends Component<SidebarProps, SidebarState> { <div> <h5 className="mb-0">{community.title} {community.removed && - <small className="ml-2 text-muted font-italic">removed</small> + <small className="ml-2 text-muted font-italic"><T i18nKey="removed">#</T></small> } {community.deleted && - <small className="ml-2 text-muted font-italic">deleted</small> + <small className="ml-2 text-muted font-italic"><T i18nKey="deleted">#</T></small> } </h5> <Link className="text-muted" to={`/c/${community.name}`}>/c/{community.name}</Link> @@ -65,12 +67,12 @@ export class Sidebar extends Component<SidebarProps, SidebarState> { {this.canMod && <> <li className="list-inline-item"> - <span class="pointer" onClick={linkEvent(this, this.handleEditClick)}>edit</span> + <span class="pointer" onClick={linkEvent(this, this.handleEditClick)}><T i18nKey="edit">#</T></span> </li> {this.amCreator && <li className="list-inline-item"> <span class="pointer" onClick={linkEvent(this, this.handleDeleteClick)}> - {!community.deleted ? 'delete' : 'restore'} + {!community.deleted ? i18n.t('delete') : i18n.t('restore')} </span> </li> } @@ -79,8 +81,8 @@ export class Sidebar extends Component<SidebarProps, SidebarState> { {this.canAdmin && <li className="list-inline-item"> {!this.props.community.removed ? - <span class="pointer" onClick={linkEvent(this, this.handleModRemoveShow)}>remove</span> : - <span class="pointer" onClick={linkEvent(this, this.handleModRemoveSubmit)}>restore</span> + <span class="pointer" onClick={linkEvent(this, this.handleModRemoveShow)}><T i18nKey="remove">#</T></span> : + <span class="pointer" onClick={linkEvent(this, this.handleModRemoveSubmit)}><T i18nKey="restore">#</T></span> } </li> @@ -89,8 +91,8 @@ export class Sidebar extends Component<SidebarProps, SidebarState> { {this.state.showRemoveDialog && <form onSubmit={linkEvent(this, this.handleModRemoveSubmit)}> <div class="form-group row"> - <label class="col-form-label">Reason</label> - <input type="text" class="form-control mr-2" placeholder="Optional" value={this.state.removeReason} onInput={linkEvent(this, this.handleModRemoveReasonChange)} /> + <label class="col-form-label"><T i18nKey="reason">#</T></label> + <input type="text" class="form-control mr-2" placeholder={i18n.t('optional')} value={this.state.removeReason} onInput={linkEvent(this, this.handleModRemoveReasonChange)} /> </div> {/* TODO hold off on expires for now */} {/* <div class="form-group row"> */} @@ -98,29 +100,29 @@ export class Sidebar extends Component<SidebarProps, SidebarState> { {/* <input type="date" class="form-control mr-2" placeholder="Expires" value={this.state.removeExpires} onInput={linkEvent(this, this.handleModRemoveExpiresChange)} /> */} {/* </div> */} <div class="form-group row"> - <button type="submit" class="btn btn-secondary">Remove Community</button> + <button type="submit" class="btn btn-secondary"><T i18nKey="remove_community">#</T></button> </div> </form> } <ul class="my-1 list-inline"> <li className="list-inline-item"><Link className="badge badge-light" to="/communities">{community.category_name}</Link></li> - <li className="list-inline-item badge badge-light">{community.number_of_subscribers} Subscribers</li> - <li className="list-inline-item badge badge-light">{community.number_of_posts} Posts</li> - <li className="list-inline-item badge badge-light">{community.number_of_comments} Comments</li> - <li className="list-inline-item"><Link className="badge badge-light" to={`/modlog/community/${this.props.community.id}`}>Modlog</Link></li> + <li className="list-inline-item badge badge-light"><T i18nKey="number_of_subscribers" interpolation={{count: community.number_of_subscribers}}>#</T></li> + <li className="list-inline-item badge badge-light"><T i18nKey="number_of_posts" interpolation={{count: community.number_of_posts}}>#</T></li> + <li className="list-inline-item badge badge-light"><T i18nKey="number_of_comments" interpolation={{count: community.number_of_comments}}>#</T></li> + <li className="list-inline-item"><Link className="badge badge-light" to={`/modlog/community/${this.props.community.id}`}><T i18nKey="modlog">#</T></Link></li> </ul> <ul class="list-inline small"> - <li class="list-inline-item">mods: </li> + <li class="list-inline-item">{i18n.t('mods')}: </li> {this.props.moderators.map(mod => <li class="list-inline-item"><Link class="text-info" to={`/u/${mod.user_name}`}>{mod.user_name}</Link></li> )} </ul> <Link class={`btn btn-sm btn-secondary btn-block mb-3 ${(community.deleted || community.removed) && 'no-click'}`} - to={`/create_post/c/${community.name}`}>Create a Post</Link> + to={`/create_post/c/${community.name}`}><T i18nKey="create_a_post">#</T></Link> <div> {community.subscribed - ? <button class="btn btn-sm btn-secondary btn-block mb-3" onClick={linkEvent(community.id, this.handleUnsubscribe)}>Unsubscribe</button> - : <button class="btn btn-sm btn-secondary btn-block mb-3" onClick={linkEvent(community.id, this.handleSubscribe)}>Subscribe</button> + ? <button class="btn btn-sm btn-secondary btn-block mb-3" onClick={linkEvent(community.id, this.handleUnsubscribe)}><T i18nKey="unsubscribe">#</T></button> + : <button class="btn btn-sm btn-secondary btn-block mb-3" onClick={linkEvent(community.id, this.handleSubscribe)}><T i18nKey="subscribe">#</T></button> } </div> {community.description && diff --git a/ui/src/components/site-form.tsx b/ui/src/components/site-form.tsx index 7c51be40..01164215 100644 --- a/ui/src/components/site-form.tsx +++ b/ui/src/components/site-form.tsx @@ -1,7 +1,10 @@ import { Component, linkEvent } from 'inferno'; import { Site, SiteForm as SiteFormI } from '../interfaces'; import { WebSocketService } from '../services'; +import { capitalizeFirstLetter } from '../utils'; import * as autosize from 'autosize'; +import { i18n } from '../i18next'; +import { T } from 'inferno-i18next'; interface SiteFormProps { site?: Site; // If a site is given, that means this is an edit @@ -39,15 +42,15 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { render() { return ( <form onSubmit={linkEvent(this, this.handleCreateSiteSubmit)}> - <h5>{`${this.props.site ? 'Edit' : 'Name'} your Site`}</h5> + <h5>{`${this.props.site ? capitalizeFirstLetter(i18n.t('edit')) : capitalizeFirstLetter(i18n.t('name'))} ${i18n.t('your_site')}`}</h5> <div class="form-group row"> - <label class="col-12 col-form-label">Name</label> + <label class="col-12 col-form-label"><T i18nKey="name">#</T></label> <div class="col-12"> <input type="text" class="form-control" value={this.state.siteForm.name} onInput={linkEvent(this, this.handleSiteNameChange)} required minLength={3} maxLength={20} /> </div> </div> <div class="form-group row"> - <label class="col-12 col-form-label">Sidebar</label> + <label class="col-12 col-form-label"><T i18nKey="sidebar">#</T></label> <div class="col-12"> <textarea value={this.state.siteForm.description} onInput={linkEvent(this, this.handleSiteDescriptionChange)} class="form-control" rows={3} maxLength={10000} /> </div> @@ -57,8 +60,8 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { <button type="submit" class="btn btn-secondary mr-2"> {this.state.loading ? <svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg> : - this.props.site ? 'Save' : 'Create'}</button> - {this.props.site && <button type="button" class="btn btn-secondary" onClick={linkEvent(this, this.handleCancel)}>Cancel</button>} + this.props.site ? capitalizeFirstLetter(i18n.t('save')) : capitalizeFirstLetter(i18n.t('create'))}</button> + {this.props.site && <button type="button" class="btn btn-secondary" onClick={linkEvent(this, this.handleCancel)}><T i18nKey="cancel">#</T></button>} </div> </div> </form> diff --git a/ui/src/components/sponsors.tsx b/ui/src/components/sponsors.tsx index c0b36e4c..898efa2c 100644 --- a/ui/src/components/sponsors.tsx +++ b/ui/src/components/sponsors.tsx @@ -1,5 +1,7 @@ import { Component } from 'inferno'; import { WebSocketService } from '../services'; +import { i18n } from '../i18next'; +import { T } from 'inferno-i18next'; let general = [ @@ -18,7 +20,7 @@ export class Sponsors extends Component<any, any> { } componentDidMount() { - document.title = `Sponsors - ${WebSocketService.Instance.site.name}`; + document.title = `${i18n.t('sponsors')} - ${WebSocketService.Instance.site.name}`; } render() { @@ -36,19 +38,19 @@ export class Sponsors extends Component<any, any> { topMessage() { return ( <div> - <h5>Sponsors of Lemmy</h5> + <h5><T i18nKey="sponsors_of_lemmy">#</T></h5> <p> - Lemmy is free, <a href="https://github.com/dessalines/lemmy">open-source</a> software, meaning no advertising, monetizing, or venture capital, ever. Your donations directly support full-time development of the project. Thank you to the following people: + <T i18nKey="sponsor_message">#<a href="https://github.com/dessalines/lemmy">#</a></T> </p> - <a class="btn btn-secondary" href="https://www.patreon.com/dessalines">Support on Patreon</a> + <a class="btn btn-secondary" href="https://www.patreon.com/dessalines"><T i18nKey="support_on_patreon">#</T></a> </div> ) } sponsors() { return ( <div class="container"> - <h5>Sponsors</h5> - <p>General Sponsors are those that pledged $10 to $39 to Lemmy.</p> + <h5><T i18nKey="sponsors">#</T></h5> + <p><T i18nKey="general_sponsors">#</T></p> <div class="row card-columns"> {general.map(s => <div class="card col-12 col-md-2"> @@ -68,11 +70,11 @@ export class Sponsors extends Component<any, any> { <table class="table table-hover text-center"> <tbody> <tr> - <td>Bitcoin</td> + <td><T i18nKey="bitcoin">#</T></td> <td><code>1Hefs7miXS5ff5Ck5xvmjKjXf5242KzRtK</code></td> </tr> <tr> - <td>Ethereum</td> + <td><T i18nKey="ethereum">#</T></td> <td><code>0x400c96c96acbC6E7B3B43B1dc1BB446540a88A01</code></td> </tr> </tbody> diff --git a/ui/src/components/user.tsx b/ui/src/components/user.tsx index d7c2bf66..4193175c 100644 --- a/ui/src/components/user.tsx +++ b/ui/src/components/user.tsx @@ -8,6 +8,8 @@ import { msgOp, fetchLimit, routeSortTypeToEnum, capitalizeFirstLetter } from '. import { PostListing } from './post-listing'; import { CommentNodes } from './comment-nodes'; import { MomentTime } from './moment-time'; +import { i18n } from '../i18next'; +import { T } from 'inferno-i18next'; enum View { Overview, Comments, Posts, Saved @@ -142,20 +144,20 @@ export class User extends Component<any, UserState> { return ( <div className="mb-2"> <select value={this.state.view} onChange={linkEvent(this, this.handleViewChange)} class="custom-select custom-select-sm w-auto"> - <option disabled>View</option> - <option value={View.Overview}>Overview</option> - <option value={View.Comments}>Comments</option> - <option value={View.Posts}>Posts</option> - <option value={View.Saved}>Saved</option> + <option disabled><T i18nKey="view">#</T></option> + <option value={View.Overview}><T i18nKey="overview">#</T></option> + <option value={View.Comments}><T i18nKey="comments">#</T></option> + <option value={View.Posts}><T i18nKey="posts">#</T></option> + <option value={View.Saved}><T i18nKey="saved">#</T></option> </select> <select value={this.state.sort} onChange={linkEvent(this, this.handleSortChange)} class="custom-select custom-select-sm w-auto ml-2"> - <option disabled>Sort Type</option> - <option value={SortType.New}>New</option> - <option value={SortType.TopDay}>Top Day</option> - <option value={SortType.TopWeek}>Week</option> - <option value={SortType.TopMonth}>Month</option> - <option value={SortType.TopYear}>Year</option> - <option value={SortType.TopAll}>All</option> + <option disabled><T i18nKey="sort_type">#</T></option> + <option value={SortType.New}><T i18nKey="new">#</T></option> + <option value={SortType.TopDay}><T i18nKey="top_day">#</T></option> + <option value={SortType.TopWeek}><T i18nKey="week">#</T></option> + <option value={SortType.TopMonth}><T i18nKey="month">#</T></option> + <option value={SortType.TopYear}><T i18nKey="year">#</T></option> + <option value={SortType.TopAll}><T i18nKey="all">#</T></option> </select> </div> ) @@ -220,12 +222,12 @@ export class User extends Component<any, UserState> { <div>Joined <MomentTime data={user} /></div> <table class="table table-bordered table-sm mt-2"> <tr> - <td>{user.post_score} points</td> - <td>{user.number_of_posts} posts</td> + <td><T i18nKey="number_of_points" interpolation={{count: user.post_score}}>#</T></td> + <td><T i18nKey="number_of_posts" interpolation={{count: user.number_of_posts}}>#</T></td> </tr> <tr> - <td>{user.comment_score} points</td> - <td>{user.number_of_comments} comments</td> + <td><T i18nKey="number_of_points" interpolation={{count: user.comment_score}}>#</T></td> + <td><T i18nKey="number_of_comments" interpolation={{count: user.number_of_comments}}>#</T></td> </tr> </table> <hr /> @@ -238,7 +240,7 @@ export class User extends Component<any, UserState> { <div> {this.state.moderates.length > 0 && <div> - <h5>Moderates</h5> + <h5><T i18nKey="moderates">#</T></h5> <ul class="list-unstyled"> {this.state.moderates.map(community => <li><Link to={`/c/${community.community_name}`}>{community.community_name}</Link></li> @@ -256,7 +258,7 @@ export class User extends Component<any, UserState> { {this.state.follows.length > 0 && <div> <hr /> - <h5>Subscribed</h5> + <h5><T i18nKey="subscribed">#</T></h5> <ul class="list-unstyled"> {this.state.follows.map(community => <li><Link to={`/c/${community.community_name}`}>{community.community_name}</Link></li> @@ -272,9 +274,9 @@ export class User extends Component<any, UserState> { return ( <div class="mt-2"> {this.state.page > 1 && - <button class="btn btn-sm btn-secondary mr-1" onClick={linkEvent(this, this.prevPage)}>Prev</button> + <button class="btn btn-sm btn-secondary mr-1" onClick={linkEvent(this, this.prevPage)}><T i18nKey="prev">#</T></button> } - <button class="btn btn-sm btn-secondary" onClick={linkEvent(this, this.nextPage)}>Next</button> + <button class="btn btn-sm btn-secondary" onClick={linkEvent(this, this.nextPage)}><T i18nKey="next">#</T></button> </div> ); } @@ -359,7 +361,7 @@ export class User extends Component<any, UserState> { this.setState(this.state); } else if (op == UserOperation.CreateComment) { // let res: CommentResponse = msg; - alert('Reply sent'); + alert(i18n.t('reply_sent')); // this.state.comments.unshift(res.comment); // TODO do this right // this.setState(this.state); } else if (op == UserOperation.SaveComment) { diff --git a/ui/src/i18next.ts b/ui/src/i18next.ts index 0af98423..816c4c07 100644 --- a/ui/src/i18next.ts +++ b/ui/src/i18next.ts @@ -1,20 +1,141 @@ -import * as i18next from 'i18next'; +import * as i18n from 'i18next'; // https://github.com/nimbusec-oss/inferno-i18next/blob/master/tests/T.test.js#L66 +// +// TODO don't forget to add moment locales for new languages. const resources = { en: { translation: { + post: 'post', + edit: 'edit', + reply: 'reply', + cancel: 'Cancel', + unlock: 'unlock', + lock: 'lock', + link: 'link', + mod: 'mod', + mods: 'mods', + moderates: 'Moderates', + admin: 'admin', + admins: 'admins', + modlog: 'Modlog', + remove: 'remove', + removed: 'removed', + locked: 'locked', + reason: 'Reason', + remove_as_mod: 'remove as mod', + appoint_as_mod: 'appoint as mod', + remove_as_admin: 'remove as admin', + appoint_as_admin: 'appoint as admin', + mark_as_read: 'mark as read', + mark_as_unread: 'mark as unread', + remove_comment: 'Remove Comment', + remove_community: 'Remove Community', + delete: 'delete', + deleted: 'deleted', + restore: 'restore', + ban: 'ban', + unban: 'unban', + ban_from_site: 'ban from site', + unban_from_site: 'unban from site', + save: 'save', + unsave: 'unsave', + create: 'create', subscribed_to_communities:'Subscribed to <1>communities</1>', create_a_community: 'Create a community', + create_community: 'Create Community', + create_a_post: 'Create a post', + create_post: 'Create Post', trending_communities:'Trending <1>communities</1>', - edit: 'edit', number_of_users:'{{count}} Users', + number_of_subscribers:'{{count}} Subscribers', number_of_posts:'{{count}} Posts', number_of_comments:'{{count}} Comments', - modlog: 'Modlog', - admins: 'admins', + number_of_points:'{{count}} Points', powered_by: 'Powered by', landing_0: 'Lemmy is a <1>link aggregator</1> / reddit alternative, intended to work in the <2>fediverse</2>.<3></3>Its self-hostable, has live-updating comment threads, and is tiny (<4>~80kB</4>). Federation into the ActivityPub network is on the roadmap. <5></5>This is a <6>very early beta version</6>, and a lot of features are currently broken or missing. <7></7>Suggest new features or report bugs <8>here.</8><9></9>Made with <10>Rust</10>, <11>Actix</11>, <12>Inferno</12>, <13>Typescript</13>.', + list_of_communities: 'List of communities', + name: 'Name', + title: 'Title', + category: 'Category', + subscribers: 'Subscribers', + both: 'Both', + posts: 'Posts', + comments: 'Comments', + saved: 'Saved', + unsubscribe: 'Unsubscribe', + subscribe: 'Subscribe', + prev: 'Prev', + next: 'Next', + sidebar: 'Sidebar', + community_reqs: 'lowercase, underscores, and no spaces.', + sort_type: 'Sort type', + hot: 'Hot', + new: 'New', + top_day: 'Top day', + week: 'Week', + month: 'Month', + year: 'Year', + all: 'All', + top: 'Top', + + api: 'API', + sponsors: 'Sponsors', + sponsors_of_lemmy: 'Sponsors of Lemmy', + sponsor_message: 'Lemmy is free, <1>open-source</1> software, meaning no advertising, monetizing, or venture capital, ever. Your donations directly support full-time development of the project. Thank you to the following people:', + support_on_patreon: 'Support on Patreon', + general_sponsors:'General Sponsors are those that pledged $10 to $39 to Lemmy.', + bitcoin: 'Bitcoin', + ethereum: 'Ethereum', + code: 'Code', + + inbox: 'Inbox', + inbox_for: 'Inbox for <1>{{user}}</1>', + mark_all_as_read: 'mark all as read', + type: 'Type', + unread: 'Unread', + reply_sent: 'Reply sent', + + communities: 'Communities', + search: 'Search', + overview: 'Overview', + view: 'View', + logout: 'Logout', + login_sign_up: 'Login / Sign up', + notifications_error: 'Desktop notifications not available in your browser. Try Firefox or Chrome.', + unread_messages: 'Unread Messages', + + email_or_username: 'Email or Username', + password: 'Password', + verify_password: 'Verify Password', + login: 'Login', + sign_up: 'Sign Up', + username: 'Username', + email: 'Email', + optional: 'Optional', + + url: 'URL', + body: 'Body', + copy_suggested_title: 'copy suggested title: {{title}}', + related_posts: 'These posts might be related', + community: 'Community', + + expand_here: 'Expand here', + remove_post: 'Remove Post', + + no_posts: 'No Posts.', + subscribe_to_communities: 'Subscribe to some <1>communities</1>.', + + chat: 'Chat', + + no_results: 'No results.', + + setup: 'Setup', + lemmy_instance_setup: 'Lemmy Instance Setup', + setup_admin: 'Set Up Site Administrator', + + your_site: 'your site', + modified: 'modified', foo: 'foo', @@ -34,12 +155,13 @@ function format(value: any, format: any, lng: any) { return value; } -i18next.init({ - lng: 'en', +i18n +.init({ + fallbackLng: 'en', resources, interpolation: { format: format } }); -export { i18next, resources }; +export { i18n, resources }; diff --git a/ui/src/index.tsx b/ui/src/index.tsx index 2067c06b..41381513 100644 --- a/ui/src/index.tsx +++ b/ui/src/index.tsx @@ -17,7 +17,7 @@ import { Inbox } from './components/inbox'; import { Search } from './components/search'; import { Sponsors } from './components/sponsors'; import { Symbols } from './components/symbols'; -import { i18next } from './i18next'; +import { i18n } from './i18next'; import './css/bootstrap.min.css'; import './css/main.css'; @@ -36,7 +36,7 @@ class Index extends Component<any, any> { render() { return ( - <Provider i18next={i18next}> + <Provider i18next={i18n}> <BrowserRouter> <Navbar /> <div class="mt-1 p-0"> |