add localisation support
currently only en_GB (TODO: dynamic language pack imports)
This commit is contained in:
		
							parent
							
								
									970590497f
								
							
						
					
					
						commit
						e326ac858e
					
				
					 17 changed files with 263 additions and 90 deletions
				
			
		
							
								
								
									
										88
									
								
								src/lang/en_GB.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								src/lang/en_GB.json
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,88 @@ | |||
| { | ||||
|     "compose_placeholders": [ | ||||
|         "What's cooking, $1?", | ||||
|         "Speak your mind!", | ||||
|         "Federate something...", | ||||
|         "I sure love posting!", | ||||
|         "Another day, another $1 post!" | ||||
|     ], | ||||
| 
 | ||||
|     "login": { | ||||
|         "welcome": "Welcome, fediverse user!", | ||||
|         "enter_domain": "Please enter your server domain to log in.", | ||||
|         "experimental": "Please note this is\n<strong><em>extremely experimental software</em></strong>;\nthings are likely to break!\n<br>\nIf that's all cool with you, welcome aboard!", | ||||
|         "button": "Log in", | ||||
|         "error": { | ||||
|             "no_domain": "Please enter an server domain.", | ||||
|             "connection_failed": "Failed to connect to the server.\nCheck the browser console for details!", | ||||
|             "create_app": "Failed to create an application for this server." | ||||
|         }, | ||||
|         "made_with_tagline": "made with ❤ by <a href=\"https://bliss.town\">bliss town</a>" | ||||
|     }, | ||||
| 
 | ||||
|     "navigation": { | ||||
|         "timeline": "Timeline", | ||||
|         "notifications": "Notifications", | ||||
|         "explore": "Explore", | ||||
|         "lists": "Lists", | ||||
| 
 | ||||
|         "favourites": "Favourites", | ||||
|         "bookmarks": "Bookmarks", | ||||
|         "hashtags": "Hashtags", | ||||
| 
 | ||||
|         "profile_information": "Profile information", | ||||
|         "settings": "Settings", | ||||
|         "log_out": "Log out" | ||||
|     }, | ||||
| 
 | ||||
|     "timeline": { | ||||
|         "home": "Home", | ||||
|         "local": "Local", | ||||
|         "federated": "Federated", | ||||
|         "fetching": "getting the feed..." | ||||
|     }, | ||||
| 
 | ||||
|     "notification": { | ||||
|         "and_others": "and <strong>%1</strong> others", | ||||
|         "mention": "%1 mentioned you.", | ||||
|         "reblog": "%1 boosted your post.", | ||||
|         "reaction": "%1 reacted to your post.", | ||||
|         "follow": "%1 followed you.", | ||||
|         "follow_request": "%1 requested to follow you.", | ||||
|         "favourite": "%1 favourited your post.", | ||||
|         "poll": "%1's poll as ended.", | ||||
|         "update": "%1 updated their post.", | ||||
|         "default": "%1 poked you!", | ||||
|         "fetching": "fetching notifications..." | ||||
|     }, | ||||
| 
 | ||||
|     "post": { | ||||
|         "time": "%1 ago", | ||||
|         "boosted": "%1 boosted this post.", | ||||
|         "actions": { | ||||
|             "reply": "Reply", | ||||
|             "boost": "Boost", | ||||
|             "favourite": "Favourite", | ||||
|             "quote": "Quote", | ||||
|             "react": "React", | ||||
|             "more": "More", | ||||
|             "delete": "Delete" | ||||
|         }, | ||||
|         "warning": { | ||||
|             "show": "(click to reveal)", | ||||
|             "hide": "(click to hide)" | ||||
|         }, | ||||
|         "visibility": { | ||||
|             "public": "public", | ||||
|             "unlisted": "unlisted", | ||||
|             "private": "private", | ||||
|             "direct": "direct" | ||||
|         } | ||||
|     }, | ||||
| 
 | ||||
|     "compose": "Post", | ||||
|     "search": "Search", | ||||
| 
 | ||||
|     "source": "source", | ||||
|     "issues": "issues" | ||||
| } | ||||
							
								
								
									
										60
									
								
								src/lib/lang.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/lib/lang.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,60 @@ | |||
| import * as en_GB from '@cf/lang/en_GB.json'; | ||||
| 
 | ||||
| /** | ||||
|  * @param {string} lang IETH language tag (i.e. en_GB) | ||||
|  * @returns Map<string, string | string[]> | ||||
|  */ | ||||
| export default function init(lang) { | ||||
|     let i18n = new Object(); | ||||
|     let language; | ||||
| 
 | ||||
|     // TODO: dynamic imports seem to fail here; it can't find the file.
 | ||||
|     // try {
 | ||||
|     //     language = import(`../lang/${lang}.json`);
 | ||||
|     // } catch (error) {
 | ||||
|     //     throw error;
 | ||||
|     // }
 | ||||
| 
 | ||||
|     language = en_GB; | ||||
| 
 | ||||
|     i18n.lang = language; | ||||
|     i18n.lang_code = lang; | ||||
|     i18n.string = function(/* @type string */ key) { | ||||
|         const tokens = key.split('.'); | ||||
|          | ||||
|         let i = 0; | ||||
|         let token = tokens[i]; | ||||
|         let res = this.lang; | ||||
|         while (true) { | ||||
|             res = res[token]; | ||||
|             if (res === undefined) { | ||||
|                 console.warn(`${key} not found for language ${this.lang_code}`); | ||||
|                 return key; | ||||
|             } | ||||
|             if (typeof res === 'string' || res instanceof String) | ||||
|                 return res; | ||||
|             i++; | ||||
|             token = tokens[i]; | ||||
|         } | ||||
|     } | ||||
|     i18n.stringArray = function(/* @type string */ key) { | ||||
|         const tokens = key.split('.'); | ||||
|          | ||||
|         let i = 0; | ||||
|         let token = tokens[i]; | ||||
|         let res = this.lang; | ||||
|         while (true) { | ||||
|             res = res[token]; | ||||
|             if (res === undefined) { | ||||
|                 console.warn(`${key} not found for language ${this.lang_code}`); | ||||
|                 return key; | ||||
|             } | ||||
|             if (Array.isArray(res)) | ||||
|                 return res; | ||||
|             i++; | ||||
|             token = tokens[i]; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return i18n; | ||||
| } | ||||
|  | @ -1,3 +1,6 @@ | |||
| import Lang from '$lib/lang.js'; | ||||
| const lang = Lang('en_GB'); | ||||
| 
 | ||||
| const denoms = [ | ||||
|     { unit: 's', min: 0 }, | ||||
|     { unit: 'm', min: 60 }, | ||||
|  | @ -18,6 +21,6 @@ export function shorthand(date) { | |||
|         unit = denoms[index].unit; | ||||
|     } | ||||
|     if (value > 0) | ||||
|         return Math.floor(value) + unit + " ago"; | ||||
|         return lang.string('post.time').replaceAll('%1', Math.floor(value) + unit); | ||||
|     return "in " + Math.floor(value) + unit; | ||||
| } | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ | |||
|     import { timeline } from '$lib/timeline.js'; | ||||
|     import { createEventDispatcher } from 'svelte'; | ||||
|     import { playSound } from '$lib/sound'; | ||||
|     import Lang from '$lib/lang.js' | ||||
| 
 | ||||
|     import Button from '@cf/ui/Button.svelte'; | ||||
|     import PostIcon from '@cf/icons/post.svg'; | ||||
|  | @ -19,6 +20,8 @@ | |||
|     import FollowersVisIcon from '@cf/icons/followers.svg'; | ||||
|     import PrivateVisIcon from '@cf/icons/dm.svg'; | ||||
| 
 | ||||
|     const lang = Lang('en_GB'); | ||||
| 
 | ||||
|     export let reply_id; | ||||
| 
 | ||||
|     let content_warning = "" | ||||
|  | @ -27,15 +30,9 @@ | |||
|     let show_cw = false; | ||||
|     let visibility = "Public"; | ||||
| 
 | ||||
|     const placeholders = [ | ||||
|         "What's cooking, $1?", | ||||
|         "Speak your mind!", | ||||
|         "Federate something...", | ||||
|         "I sure love posting!", | ||||
|         "Another day, another $1 post!", | ||||
|     ]; | ||||
|     let placeholder = placeholders[Math.floor(placeholders.length * Math.random())] | ||||
|         .replaceAll("$1", $account.username); | ||||
|     const placeholders = lang.stringArray('compose_placeholders'); | ||||
|     let placeholder = Array.isArray(placeholders) ? placeholders[Math.floor(placeholders.length * Math.random())] | ||||
|         .replaceAll("$1", $account.username) : placeholders; | ||||
| 
 | ||||
|     const dispatch = createEventDispatcher(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,9 +3,12 @@ | |||
|     import { server, createServer } from '$lib/client/server.js'; | ||||
|     import { app } from '$lib/client/app.js'; | ||||
|     import { get } from 'svelte/store'; | ||||
|     import Lang from '$lib/lang.js'; | ||||
| 
 | ||||
|     import Logo from '$lib/../img/campfire-logo.svg'; | ||||
| 
 | ||||
|     const lang = Lang('en_GB'); | ||||
| 
 | ||||
|     let display_error = false; | ||||
|     let logging_in = false; | ||||
| 
 | ||||
|  | @ -17,21 +20,21 @@ | |||
|         const host = event.target.host.value; | ||||
| 
 | ||||
|         if (!host || host === "") { | ||||
|             display_error = "Please enter an server domain."; | ||||
|             display_error = lang.string('login.error.no_domain'); | ||||
|             logging_in = false; | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         server.set(await createServer(host)); | ||||
|         if (!get(server)) { | ||||
|             display_error = "Failed to connect to the server.\nCheck the browser console for details!" | ||||
|             display_error = lang.string('login.error.connection_failed'); | ||||
|             logging_in = false; | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         app.set(await api.createApp(get(server).host)); | ||||
|         if (!get(app)) { | ||||
|             display_error = "Failed to create an application for this server." | ||||
|             display_error = lang.string('login.error.create_app'); | ||||
|             logging_in = false; | ||||
|             return; | ||||
|         } | ||||
|  | @ -44,8 +47,8 @@ | |||
|     <div class="app-logo"> | ||||
|         <Logo /> | ||||
|     </div> | ||||
|     <p>Welcome, fediverse user!</p> | ||||
|     <p>Please enter your server domain to log in.</p> | ||||
|     <p>{lang.string('login.welcome')}</p> | ||||
|     <p>{lang.string('login.enter_domain')}</p> | ||||
|     <div class="input-wrapper"> | ||||
|         <input type="text" id="host" aria-label="server domain" class={logging_in ? "throb" : ""}> | ||||
|         {#if display_error} | ||||
|  | @ -53,16 +56,10 @@ | |||
|         {/if} | ||||
|     </div> | ||||
|     <br> | ||||
|     <button type="submit" id="login" class={logging_in ? "disabled" : ""}>Log in</button> | ||||
|     <p><small> | ||||
|         Please note this is | ||||
|         <strong><em>extremely experimental software</em></strong>; | ||||
|         things are likely to break! | ||||
|         <br> | ||||
|         If that's all cool with you, welcome aboard! | ||||
|     </small></p> | ||||
|     <button type="submit" id="login" class={logging_in ? "disabled" : ""}>{lang.string('login.button')}</button> | ||||
|     <p><small>{@html lang.string('login.experimental')}</small></p> | ||||
| 
 | ||||
|     <p class="form-footer">made with ❤ by <a href="https://bliss.town">bliss town</a>, 2024</p> | ||||
|     <p class="form-footer">{@html lang.string('login.made_with_tagline')}</p> | ||||
| </form> | ||||
| 
 | ||||
| <style> | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ | |||
|     import { page } from '$app/stores'; | ||||
|     import { createEventDispatcher } from 'svelte'; | ||||
|     import { notifications, unread_notif_count } from '$lib/notifications.js'; | ||||
|     import Lang from '$lib/lang.js'; | ||||
| 
 | ||||
|     import Logo from '$lib/../img/campfire-logo.svg'; | ||||
|     import Button from './Button.svelte'; | ||||
|  | @ -27,6 +28,7 @@ | |||
|     import LogoutIcon from '../../img/icons/logout.svg'; | ||||
| 
 | ||||
|     const VERSION = APP_VERSION; | ||||
|     const lang = Lang('en_GB'); | ||||
| 
 | ||||
|     const dispatch = createEventDispatcher(); | ||||
| 
 | ||||
|  | @ -95,7 +97,7 @@ | |||
|             <svelte:fragment slot="icon"> | ||||
|                 <TimelineIcon/> | ||||
|             </svelte:fragment> | ||||
|             Timeline | ||||
|             {lang.string('navigation.timeline')} | ||||
|         </Button> | ||||
|         <Button label="Notifications" | ||||
|                 on:click={() => handle_btn("notifications")} | ||||
|  | @ -103,7 +105,7 @@ | |||
|             <svelte:fragment slot="icon"> | ||||
|                 <NotificationsIcon/> | ||||
|             </svelte:fragment> | ||||
|             Notifications | ||||
|             {lang.string('navigation.notifications')} | ||||
|             {#if $unread_notif_count} | ||||
|                 <span class="notification-count"> | ||||
|                     {$unread_notif_count <= 99 ? $unread_notif_count : "99+"} | ||||
|  | @ -114,54 +116,54 @@ | |||
|             <svelte:fragment slot="icon"> | ||||
|                 <ExploreIcon height="auto"/> | ||||
|             </svelte:fragment> | ||||
|             Explore | ||||
|             {lang.string('navigation.explore')} | ||||
|         </Button> | ||||
|         <Button label="Lists" disabled> | ||||
|             <svelte:fragment slot="icon"> | ||||
|                 <ListIcon/> | ||||
|             </svelte:fragment> | ||||
|             Lists | ||||
|             {lang.string('navigation.lists')} | ||||
|         </Button> | ||||
| 
 | ||||
|         <div class="flex-row"> | ||||
|             <Button centered label="Favourites" disabled> | ||||
|             <Button centered label="{lang.string('navigation.favourites')}" disabled> | ||||
|                 <svelte:fragment slot="icon"> | ||||
|                     <FavouritesIcon/> | ||||
|                 </svelte:fragment> | ||||
|             </Button> | ||||
|             <Button centered label="Bookmarks" disabled> | ||||
|             <Button centered label="{lang.string('navigation.bookmarks')}" disabled> | ||||
|                 <svelte:fragment slot="icon"> | ||||
|                     <BookmarkIcon/> | ||||
|                 </svelte:fragment> | ||||
|             </Button> | ||||
|             <Button centered label="Hashtags" disabled> | ||||
|             <Button centered label="{lang.string('navigation.hashtags')}" disabled> | ||||
|                 <svelte:fragment slot="icon"> | ||||
|                     <HashtagIcon/> | ||||
|                 </svelte:fragment> | ||||
|             </Button> | ||||
|         </div> | ||||
| 
 | ||||
|         <Button filled label="Post" on:click={() => dispatch("compose")}> | ||||
|         <Button filled label="{lang.string('compose')}" on:click={() => dispatch("compose")}> | ||||
|             <svelte:fragment slot="icon"> | ||||
|                 <PostIcon/> | ||||
|             </svelte:fragment> | ||||
|             Post | ||||
|             {lang.string('compose')} | ||||
|         </Button> | ||||
|     </div> | ||||
| 
 | ||||
|     <div id="account-items"> | ||||
|         <div class="flex-row"> | ||||
|             <Button centered label="Profile information" disabled> | ||||
|             <Button centered label="{lang.string('navigation.profile_information')}" disabled> | ||||
|                 <svelte:fragment slot="icon"> | ||||
|                     <InfoIcon/> | ||||
|                 </svelte:fragment> | ||||
|             </Button> | ||||
|             <Button centered label="Settings" disabled> | ||||
|             <Button centered label="{lang.string('navigation.settings')}" disabled> | ||||
|                 <svelte:fragment slot="icon"> | ||||
|                     <SettingsIcon/> | ||||
|                 </svelte:fragment> | ||||
|             </Button> | ||||
|             <Button centered label="Log out" on:click={() => log_out()}> | ||||
|             <Button centered label="{lang.string('navigation.log_out')}" on:click={() => log_out()}> | ||||
|                 <svelte:fragment slot="icon"> | ||||
|                     <LogoutIcon/> | ||||
|                 </svelte:fragment> | ||||
|  | @ -184,8 +186,8 @@ | |||
|         campfire v{VERSION} | ||||
|         <br> | ||||
|         <ul> | ||||
|             <li><a href="https://git.arimelody.me/blisstown/campfire">source</a></li> | ||||
|             <li><a href="https://codeberg.org/arimelody/campfire/issues">issues</a></li> | ||||
|             <li><a href="https://git.arimelody.me/blisstown/campfire">{lang.string('source')}</a></li> | ||||
|             <li><a href="https://codeberg.org/arimelody/campfire/issues">{lang.string('issues')}</a></li> | ||||
|         </ul> | ||||
|     </span> | ||||
| </div> | ||||
|  |  | |||
|  | @ -9,10 +9,13 @@ | |||
|     // import QuoteIcon from '$lib/../img/icons/quote.svg'; | ||||
|     import ReactionBar from '$lib/ui/post/ReactionBar.svelte'; | ||||
|     import ActionBar from '$lib/ui/post/ActionBar.svelte'; | ||||
|     import Lang from '$lib/lang.js'; | ||||
| 
 | ||||
|     const lang = Lang('en_GB'); | ||||
| 
 | ||||
|     let mention = (accounts) => { | ||||
|         let res = `<a href=${account.url}>${account.rich_name}</a>`; | ||||
|         if (accounts.length > 1) res += ` and <strong>${accounts.length - 1}</strong> others`; | ||||
|         if (accounts.length > 1) res += ' ' + lang.string('notification.and_others').replaceAll('%1', accounts.length - 1); | ||||
|         return res; | ||||
|     }; | ||||
| 
 | ||||
|  | @ -20,23 +23,23 @@ | |||
|     let activity_text = function (type) { | ||||
|         switch (type) { | ||||
|             case "mention": | ||||
|                 return `%1 mentioned you.`; | ||||
|                 return lang.string('notification.mention'); | ||||
|             case "reblog": | ||||
|                 return `%1 boosted your post.`; | ||||
|                 return lang.string('notification.reblog'); | ||||
|             case "reaction": | ||||
|                 return `%1 reacted to your post.`; | ||||
|                 return lang.string('notification.reaction'); | ||||
|             case "follow": | ||||
|                 return `%1 followed you.`; | ||||
|                 return lang.string('notification.follow'); | ||||
|             case "follow_request": | ||||
|                 return `%1 requested to follow you.`; | ||||
|                 return lang.string('notification.follow.request'); | ||||
|             case "favourite": | ||||
|                 return `%1 favourited your post.`; | ||||
|                 return lang.string('notification.favourite'); | ||||
|             case "poll": | ||||
|                 return `%1's poll as ended.`; | ||||
|                 return lang.string('notification.poll'); | ||||
|             case "update": | ||||
|                 return `%1 updated their post.`; | ||||
|                 return lang.string('notification.update'); | ||||
|             default: | ||||
|                 return `%1 poked you!`; | ||||
|                 return lang.string('notification.default'); | ||||
|         } | ||||
|     }(data.type); | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,5 +1,11 @@ | |||
| <script> | ||||
|     import Lang from '$lib/lang.js'; | ||||
| 
 | ||||
|     const lang = Lang('en_GB'); | ||||
| </script> | ||||
| 
 | ||||
| <div id="widgets"> | ||||
|     <input type="text" id="search" placeholder="Search"> | ||||
|     <input type="text" id="search" placeholder="{lang.string('search')}"> | ||||
| </div> | ||||
| 
 | ||||
| <style> | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ | |||
|     import { timeline } from '$lib/timeline'; | ||||
|     import { parseReactions } from '$lib/post'; | ||||
|     import { playSound } from '$lib/sound'; | ||||
|     import Lang from '$lib/lang'; | ||||
| 
 | ||||
|     import ActionButton from './ActionButton.svelte'; | ||||
| 
 | ||||
|  | @ -19,6 +20,8 @@ | |||
| 
 | ||||
|     export let post; | ||||
| 
 | ||||
|     const lang = Lang('en_GB'); | ||||
| 
 | ||||
|     async function toggleBoost() { | ||||
|         if (!$app || !$app.token) return; | ||||
| 
 | ||||
|  | @ -74,29 +77,29 @@ | |||
| </script> | ||||
| 
 | ||||
| <div class="post-actions" aria-label="Post actions" role="toolbar" tabindex="0" on:mouseup|stopPropagation on:keydown|stopPropagation> | ||||
|     <ActionButton type="reply" label="Reply" bind:count={post.reply_count} sound="post" disabled> | ||||
|     <ActionButton type="reply" label="{lang.string('post.actions.reply')}" bind:count={post.reply_count} sound="post" disabled> | ||||
|         <ReplyIcon/> | ||||
|     </ActionButton> | ||||
|     <ActionButton type="boost" label="Boost" on:click={toggleBoost} bind:active={post.boosted} bind:count={post.boost_count} disabled={!$account}> | ||||
|     <ActionButton type="boost" label="{lang.string('post.actions.boost')}" on:click={toggleBoost} bind:active={post.boosted} bind:count={post.boost_count} disabled={!$account}> | ||||
|         <RepostIcon/> | ||||
|         <svelte:fragment slot="activeIcon"> | ||||
|             <RepostIcon/> | ||||
|         </svelte:fragment> | ||||
|     </ActionButton> | ||||
|     <ActionButton type="favourite" label="Favourite" on:click={toggleFavourite} bind:active={post.favourited} bind:count={post.favourite_count} disabled={!$account}> | ||||
|     <ActionButton type="favourite" label="{lang.string('post.actions.favourite')}" on:click={toggleFavourite} bind:active={post.favourited} bind:count={post.favourite_count} disabled={!$account}> | ||||
|         <FavouriteIcon/> | ||||
|         <svelte:fragment slot="activeIcon"> | ||||
|             <FavouriteIconFill/> | ||||
|         </svelte:fragment> | ||||
|     </ActionButton> | ||||
|     <ActionButton type="quote" label="Quote" disabled> | ||||
|     <ActionButton type="quote" label="{lang.string('post.actions.quote')}" disabled> | ||||
|         <QuoteIcon/> | ||||
|     </ActionButton> | ||||
|     <ActionButton type="more" label="More" disabled> | ||||
|     <ActionButton type="more" label="{lang.string('post.actions.more')}" disabled> | ||||
|         <MoreIcon/> | ||||
|     </ActionButton> | ||||
|     {#if $account && post.account.id === $account.id} | ||||
|         <ActionButton type="delete" label="Delete" on:click={deletePost}> | ||||
|         <ActionButton type="delete" label="{lang.string('post.actions.delete')}" on:click={deletePost}> | ||||
|             <DeleteIcon/> | ||||
|         </ActionButton> | ||||
|     {/if} | ||||
|  |  | |||
|  | @ -1,25 +1,29 @@ | |||
| <script> | ||||
|     import Lang from '$lib/lang'; | ||||
| 
 | ||||
|     export let post; | ||||
| 
 | ||||
|     let open_warned = false; | ||||
|     const lang = Lang('en_GB'); | ||||
| 
 | ||||
|     let open = false; | ||||
| </script> | ||||
| 
 | ||||
| <div class="post-body"> | ||||
|     {#if post.warning} | ||||
|         <button class="post-warning" on:click|stopPropagation={() => { open_warned = !open_warned }} on:mouseup|stopPropagation> | ||||
|         <button class="post-warning" on:click|stopPropagation={() => { open = !open }} on:mouseup|stopPropagation> | ||||
|         <strong> | ||||
|             {post.warning} | ||||
|             <span class="warning-instructions"> | ||||
|                 {#if !open_warned} | ||||
|                     (click to reveal) | ||||
|             <span class="instructions"> | ||||
|                 {#if !open} | ||||
|                     {lang.string('post.warning.show')} | ||||
|                 {:else} | ||||
|                     (click to hide) | ||||
|                     {lang.string('post.warning.hide')} | ||||
|                 {/if} | ||||
|             </span> | ||||
|         </strong> | ||||
|         </button> | ||||
|     {/if} | ||||
|     {#if !post.warning || open_warned} | ||||
|     {#if !post.warning || open} | ||||
|         {#if post.rich_text} | ||||
|             <span class="post-text">{@html post.rich_text}</span> | ||||
|         {:else if post.html} | ||||
|  | @ -78,7 +82,7 @@ | |||
|         box-shadow: 0 0 8px var(--warn-bg); | ||||
|     } | ||||
| 
 | ||||
|     .post-warning .warning-instructions { | ||||
|     .post-warning .instructions { | ||||
|         font-weight: normal; | ||||
|         opacity: .5; | ||||
|     } | ||||
|  |  | |||
|  | @ -1,5 +1,8 @@ | |||
| <script> | ||||
|     import { shorthand as short_time } from '$lib/time.js'; | ||||
|     import Lang from '$lib/lang.js'; | ||||
| 
 | ||||
|     const lang = Lang('en_GB'); | ||||
| 
 | ||||
|     export let post; | ||||
| 
 | ||||
|  | @ -9,10 +12,10 @@ | |||
| <div class="post-context"> | ||||
|     <span class="post-context-icon">🔁</span> | ||||
|     <span class="post-context-action"> | ||||
|         <a href={post.account.url} target="_blank"><span class="name"> | ||||
|                 {@html post.account.rich_name}</span> | ||||
|         </a> | ||||
|         boosted this post. | ||||
|         { @html | ||||
|         lang.string('post.boosted').replaceAll('%1', | ||||
|         `<a href={${post.account.url}} target="_blank"><span class="name">${post.account.rich_name}</span></a>`) | ||||
|         } | ||||
|     </span> | ||||
|     <span class="post-context-time"> | ||||
|         <time title="{time_string}">{short_time(post.created_at)}</time> | ||||
|  | @ -41,8 +44,8 @@ | |||
|         margin-right: 4px; | ||||
|     } | ||||
| 
 | ||||
|     .post-context a, | ||||
|     .post-context a:visited { | ||||
|     :global(.post-context a), | ||||
|     :global(.post-context a:visited) { | ||||
|         color: inherit; | ||||
|         text-decoration: none; | ||||
|     } | ||||
|  |  | |||
|  | @ -1,5 +1,8 @@ | |||
| <script> | ||||
|     import { shorthand as short_time } from '$lib/time.js'; | ||||
|     import Lang from '$lib/lang.js'; | ||||
| 
 | ||||
|     const lang = Lang('en_GB'); | ||||
| 
 | ||||
|     export let post; | ||||
|     export let reply = undefined; | ||||
|  | @ -19,10 +22,8 @@ | |||
|         <div class="post-info" on:mouseup|stopPropagation> | ||||
|             <a href={post.url} target="_blank" class="created-at"> | ||||
|                 <time title={time_string}>{short_time(post.created_at)}</time> | ||||
|                 {#if post.visibility !== "public"} | ||||
|                     <br> | ||||
|                     <span class="post-visibility">{post.visibility}</span> | ||||
|                 {/if} | ||||
|                 <br> | ||||
|                 <span class="post-visibility">{lang.string('post.visibility.' + post.visibility)}</span> | ||||
|             </a> | ||||
|         </div> | ||||
|     </header> | ||||
|  |  | |||
|  | @ -51,11 +51,7 @@ | |||
|                 {/if} | ||||
|         </ReactionButton> | ||||
|     {/each} | ||||
|     <ReactionButton | ||||
|             type="reaction" | ||||
|             title="react" | ||||
|             label="React" | ||||
|             disabled> | ||||
|     <ReactionButton disabled> | ||||
|     <ReactIcon/> | ||||
|     </ReactionButton> | ||||
| </div> | ||||
|  |  | |||
|  | @ -1,11 +1,14 @@ | |||
| <script> | ||||
|     import { playSound } from '../../sound.js'; | ||||
|     import { createEventDispatcher } from 'svelte'; | ||||
|     import Lang from '$lib/lang'; | ||||
|     const dispatch = createEventDispatcher(); | ||||
| 
 | ||||
|     const lang = Lang('en_GB'); | ||||
| 
 | ||||
|     export let type = "react"; | ||||
|     export let label = "React"; | ||||
|     export let title = label; | ||||
|     export let label = lang.string('post.actions.react'); | ||||
|     export let title = lang.string('post.actions.react'); | ||||
|     export let count = 0; | ||||
|     export let active = false; | ||||
|     export let disabled = false; | ||||
|  |  | |||
|  | @ -2,11 +2,14 @@ | |||
|     import { page } from '$app/stores'; | ||||
|     import { account } from '$lib/stores/account.js'; | ||||
|     import { timeline, getTimeline } from '$lib/timeline.js'; | ||||
|     import Lang from '$lib/lang.js'; | ||||
| 
 | ||||
|     import LoginForm from '$lib/ui/LoginForm.svelte'; | ||||
|     import Button from '$lib/ui/Button.svelte'; | ||||
|     import Post from '$lib/ui/post/Post.svelte'; | ||||
| 
 | ||||
|     const lang = Lang('en_GB'); | ||||
| 
 | ||||
|     account.subscribe(account => { | ||||
|         if (account) getTimeline(); | ||||
|     }); | ||||
|  | @ -21,18 +24,18 @@ | |||
| 
 | ||||
| {#if $account} | ||||
|     <header> | ||||
|         <h1>Home</h1> | ||||
|         <h1>{lang.string('timeline.home')}</h1> | ||||
|         <nav> | ||||
|             <Button centered active>Home</Button> | ||||
|             <Button centered disabled>Local</Button> | ||||
|             <Button centered disabled>Federated</Button> | ||||
|             <Button centered active>{lang.string('timeline.home')}</Button> | ||||
|             <Button centered disabled>{lang.string('timeline.local')}</Button> | ||||
|             <Button centered disabled>{lang.string('timeline.federated')}</Button> | ||||
|         </nav> | ||||
|     </header> | ||||
| 
 | ||||
|     <div id="feed" role="feed"> | ||||
|         {#if $timeline.length <= 0} | ||||
|             <div class="loading throb"> | ||||
|                 <span>getting the feed...</span> | ||||
|                 <span>{lang.string('timeline.fetching')}</span> | ||||
|             </div> | ||||
|         {/if} | ||||
|         {#each $timeline as post} | ||||
|  |  | |||
|  | @ -4,6 +4,9 @@ | |||
|     import { goto } from '$app/navigation'; | ||||
|     import { page } from '$app/stores'; | ||||
|     import Notification from '$lib/ui/Notification.svelte'; | ||||
|     import Lang from '$lib/lang.js'; | ||||
| 
 | ||||
|     const lang = Lang('en_GB'); | ||||
| 
 | ||||
|     if (!$account) goto("/"); | ||||
| 
 | ||||
|  | @ -31,13 +34,13 @@ | |||
| </script> | ||||
| 
 | ||||
| <header> | ||||
|     <h1>Notifications</h1> | ||||
|     <h1>{lang.string('navigation.notifications')}</h1> | ||||
| </header> | ||||
| 
 | ||||
| <div class="notifications"> | ||||
|     {#if $notifications.length === 0} | ||||
|         <div class="loading throb"> | ||||
|             <span>fetching notifications...</span> | ||||
|             <span>{lang.string('notification.fetching')}</span> | ||||
|         </div> | ||||
|     {:else} | ||||
|         {#each $notifications as notif} | ||||
|  |  | |||
|  | @ -19,9 +19,10 @@ const config = { | |||
|             name: child_process.execSync('git rev-parse HEAD').toString().trim() | ||||
|         }, | ||||
|         alias: { | ||||
|             '@cf/ui/*': "./src/lib/ui", | ||||
|             '@cf/icons/*': "./src/img/icons", | ||||
|             '@cf/store/*': "./src/lib/stores" | ||||
|             '@cf/ui/*': './src/lib/ui', | ||||
|             '@cf/icons/*': './src/img/icons', | ||||
|             '@cf/store/*': './src/lib/stores', | ||||
|             '@cf/lang/*': './src/lang' | ||||
|         } | ||||
|     }, | ||||
| }; | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue