]> Untitled Git - lemmy.git/commitdiff
i18n translations first pass.
authorDessalines <tyhou13@gmx.com>
Fri, 9 Aug 2019 01:58:04 +0000 (18:58 -0700)
committerDessalines <tyhou13@gmx.com>
Fri, 9 Aug 2019 01:58:04 +0000 (18:58 -0700)
24 files changed:
ui/src/components/comment-form.tsx
ui/src/components/comment-node.tsx
ui/src/components/communities.tsx
ui/src/components/community-form.tsx
ui/src/components/community.tsx
ui/src/components/create-community.tsx
ui/src/components/create-post.tsx
ui/src/components/footer.tsx
ui/src/components/inbox.tsx
ui/src/components/login.tsx
ui/src/components/moment-time.tsx
ui/src/components/navbar.tsx
ui/src/components/post-form.tsx
ui/src/components/post-listing.tsx
ui/src/components/post-listings.tsx
ui/src/components/post.tsx
ui/src/components/search.tsx
ui/src/components/setup.tsx
ui/src/components/sidebar.tsx
ui/src/components/site-form.tsx
ui/src/components/sponsors.tsx
ui/src/components/user.tsx
ui/src/i18next.ts
ui/src/index.tsx

index 5181e45e121a6a3c67d5fd8c508f4e83c7eb2fee..9f3476a8b3002ab64a47cc2acd5c64617b1f4b86 100644 (file)
@@ -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>
index a201ddd6bef3de6e83d89efe713b3619085577fc..a05286ed3c7fbe2d46c1bbd58d37ca98d20416d4 100644 (file)
@@ -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"> */}
index c4efe1fbebc12a7f88000e557502877038847929..066d524a2e8a8676d983d007cd76759d5a5f498f 100644 (file)
@@ -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>
     );
   }
index e295dcbedd340a9588107931efaaa529e2204c0a..f6520fc6b53efde17edb1b4fccf70e2f224b856f 100644 (file)
@@ -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>
index 6a1f5da2b456fc3112ce686a6900e0dd37b9993f..920b2eae23340c23dccb30bcf2619e3357872430 100644 (file)
@@ -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>
     );
   }
index c2f89eef3311a5a7215124a8f8487db515cf13a7..61245e739f659fdc2d38ebbe1e74c5d01538eb5f 100644 (file)
@@ -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>
index e09bcf703443f36fbe7a6da18c585d56907931da..dd93a3c53797b002d0e5c6d38390e8022b6728e7 100644 (file)
@@ -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>
index 31403d7cad286dbbf4ab721119f218d60dcf01b1..87d7097e0394688d860556a9441e18b7126f20e7 100644 (file)
@@ -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>
index 5fb7f874b492d13461e0640bd41815edb06d1a43..1b5b1b8888a7a2c15eca326c7461f8b306202936 100644 (file)
@@ -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) {
index 6eb88438df97c959842842e6ac17e4fa07eccf34..e5386aa967769905379657eadd87c2d21dcb2bfb 100644 (file)
@@ -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>
index c882669530e30103b8055e02a911f34bab776bd3..c6c7c99d8cf55c6c1a0dc6939a33419b0f52ca85 100644 (file)
@@ -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_;
index 68e486c1f195a38c46754cac80fbae067be4c325..ac23e361d4838ebf17f4682c1f500b4cbda5f9b0 100644 (file)
@@ -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}`
       });
index 54b3ca44004bcea88904c14d46584043b2dc80d9..bfc861bd3a7d0555e217dfb4c4ed8f06f8ba4247 100644 (file)
@@ -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>
index 6727dd09c2f46351e20708a24c6eff2a64b21d4a..dc8f4cfae3ee5c3826ae5b8748738ac196bd43a7 100644 (file)
@@ -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)} />}
index 93b2f606f9dd1a3cec8e8e473f34a1c406b611e3..f5682a7e96ad88309ee8b0b814ab4d90dcdbead2 100644 (file)
@@ -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>
     )
index 7152941f1f8e5acf39a2594ff7066dd9085f7909..f582f0d75608e2fc2ab7934978937febb4882d6a 100644 (file)
@@ -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 
index ec657bb15fc20f95816e3e886563ad30b1716eb4..67202dba13733bf0687e989cb7cd90f0a5f7f7ea 100644 (file)
@@ -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);
     }
index edb98260c67f891e1795ab7df202b76712bafd70..fbfb2e13b09f24b66ef147540fb12b811db783bc 100644 (file)
@@ -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>
index d36d962c1c4a00466959f69fd7285b933316dd21..900c547788bdcf7c9816e20e00f8420d72da429f 100644 (file)
@@ -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 && 
index 7c51be40358731b8ac0ff35464ed6a383fa990fa..011642158e6cb2bd7ead54f27fb868f881c14109 100644 (file)
@@ -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>
index c0b36e4cc95c4d372f523148465dd65f4c332cde..898efa2cfe2f95e1c1050df8f77ff3f80fda2938 100644 (file)
@@ -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>
index d7c2bf66de6ebd260330ff9a8eeb1c805e091831..4193175c70acdef162fea81801be3c5c01d2843e 100644 (file)
@@ -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) {
index 0af9842355f9ca0c9be0c84ab93a43661e930372..816c4c07d561c9446a230f61f1d96e0dcadddc6e 100644 (file)
-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 };
index 2067c06ba0fa28fedeed8edbf0a2ff21b9f50de9..41381513d05b5f3ae32aa4dd3f412d4a554fce2f 100644 (file)
@@ -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">