possibility to set language for post

This commit is contained in:
Arnas Udovic 2024-01-25 15:13:15 +02:00
parent a7eaec3391
commit 4d576b00fa
19 changed files with 327 additions and 14 deletions

View File

@ -60,5 +60,6 @@ export default [
{ id: 'fa-crosshairs', src: 'src/thirdparty/font-awesome-svg-png/white/svg/crosshairs.svg' }, { id: 'fa-crosshairs', src: 'src/thirdparty/font-awesome-svg-png/white/svg/crosshairs.svg' },
{ id: 'fa-magic', src: 'src/thirdparty/font-awesome-svg-png/white/svg/magic.svg' }, { id: 'fa-magic', src: 'src/thirdparty/font-awesome-svg-png/white/svg/magic.svg' },
{ id: 'fa-hashtag', src: 'src/thirdparty/font-awesome-svg-png/white/svg/hashtag.svg' }, { id: 'fa-hashtag', src: 'src/thirdparty/font-awesome-svg-png/white/svg/hashtag.svg' },
{ id: 'fa-bookmark', src: 'src/thirdparty/font-awesome-svg-png/white/svg/bookmark.svg' } { id: 'fa-bookmark', src: 'src/thirdparty/font-awesome-svg-png/white/svg/bookmark.svg' },
{ id: 'fa-language', src: 'src/thirdparty/font-awesome-svg-png/white/svg/language.svg' }
] ]

View File

@ -330,7 +330,7 @@ export default {
aboutAppDescription: ` aboutAppDescription: `
<p> <p>
Semaphore is Semaphore is
<a rel="noopener" target="_blank" href="https://github.com/NickColley/semaphore">free and open-source software</a> <a rel="noopener" target="_blank" href="https://github.com/NickColley/semaphore">free and open-source software</a>
maintained by <a rel="noopener" target="_blank" href="https://nickcolley.co.uk">Nick Colley</a> maintained by <a rel="noopener" target="_blank" href="https://nickcolley.co.uk">Nick Colley</a>
and distributed under the and distributed under the
<a rel="noopener" target="_blank" <a rel="noopener" target="_blank"
@ -696,5 +696,15 @@ export default {
statusesList: 'Statuses: list', statusesList: 'Statuses: list',
notificationsOnInstance: 'Notifications on {instance}', notificationsOnInstance: 'Notifications on {instance}',
// Details // Details
statusEdited: 'Edited' statusEdited: 'Edited',
// Settings > Languages
languages: 'Languages',
addLanguage: 'Add language',
add: 'Add',
languageCode: 'Language code',
enterLanguageCode: 'Enter language code',
getLanguageCode: 'Language should be code from ISO-639 base. To check the right code for the language visti IANA language sub-tag registry.',
ianaLanguageRegistry: 'IANA registry',
setLanguageLabel: 'Set language (current {label})',
setLanguage: 'Set language'
} }

View File

@ -0,0 +1,16 @@
import { store } from '../_store/store.js'
import { goto } from '../../../__sapper__/client.js'
export function addLanguage () {
const { languages, newLanguage } = store.get()
languages.push(newLanguage.toLowerCase().trim())
store.set({ languages })
store.save()
goto('/settings/languages')
}
export function resetNewLanguage() {
const newLanguage = ''
store.set({ newLanguage })
store.save()
}

View File

@ -27,7 +27,7 @@ export async function insertHandleForReply (statusId) {
export async function postStatus (realm, text, inReplyToId, mediaIds, export async function postStatus (realm, text, inReplyToId, mediaIds,
sensitive, spoilerText, visibility, sensitive, spoilerText, visibility,
mediaDescriptions, inReplyToUuid, poll, mediaFocalPoints) { mediaDescriptions, inReplyToUuid, poll, mediaFocalPoints, language) {
const { currentInstance, accessToken, online } = store.get() const { currentInstance, accessToken, online } = store.get()
if (!online) { if (!online) {
@ -56,7 +56,7 @@ export async function postStatus (realm, text, inReplyToId, mediaIds,
} }
})) }))
const status = await postStatusToServer(currentInstance, accessToken, text, const status = await postStatusToServer(currentInstance, accessToken, text,
inReplyToId, mediaIds, sensitive, spoilerText, visibility, poll, mediaFocalPoints) inReplyToId, mediaIds, sensitive, spoilerText, visibility, poll, language, mediaFocalPoints)
addStatusOrNotification(currentInstance, 'home', status) addStatusOrNotification(currentInstance, 'home', status)
store.clearComposeData(realm) store.clearComposeData(realm)
emit('postedStatus', realm, inReplyToUuid) emit('postedStatus', realm, inReplyToUuid)

View File

@ -0,0 +1,5 @@
import { store } from '../_store/store.js'
export function setLanguage (realm, language) {
store.setComposeData(realm, { language })
}

View File

@ -3,13 +3,14 @@ import { DEFAULT_TIMEOUT, get, post, put, WRITE_TIMEOUT } from '../_utils/ajax.j
// post is create, put is edit // post is create, put is edit
async function postOrPutStatus (url, accessToken, method, text, inReplyToId, mediaIds, async function postOrPutStatus (url, accessToken, method, text, inReplyToId, mediaIds,
sensitive, spoilerText, visibility, poll) { sensitive, spoilerText, visibility, poll, language) {
const body = { const body = {
status: text, status: text,
media_ids: mediaIds, media_ids: mediaIds,
sensitive, sensitive,
spoiler_text: spoilerText, spoiler_text: spoilerText,
poll, poll,
language,
...(method === 'post' && { ...(method === 'post' && {
// you can't change these properties when editing // you can't change these properties when editing
in_reply_to_id: inReplyToId, in_reply_to_id: inReplyToId,
@ -31,17 +32,17 @@ async function postOrPutStatus (url, accessToken, method, text, inReplyToId, med
} }
export async function postStatus (instanceName, accessToken, text, inReplyToId, mediaIds, export async function postStatus (instanceName, accessToken, text, inReplyToId, mediaIds,
sensitive, spoilerText, visibility, poll) { sensitive, spoilerText, visibility, poll, language) {
const url = `${basename(instanceName)}/api/v1/statuses` const url = `${basename(instanceName)}/api/v1/statuses`
return postOrPutStatus(url, accessToken, 'post', text, inReplyToId, mediaIds, return postOrPutStatus(url, accessToken, 'post', text, inReplyToId, mediaIds,
sensitive, spoilerText, visibility, poll) sensitive, spoilerText, visibility, poll, language)
} }
export async function putStatus (instanceName, accessToken, id, text, inReplyToId, mediaIds, export async function putStatus (instanceName, accessToken, id, text, inReplyToId, mediaIds,
sensitive, spoilerText, visibility, poll) { sensitive, spoilerText, visibility, poll, language) {
const url = `${basename(instanceName)}/api/v1/statuses/${id}` const url = `${basename(instanceName)}/api/v1/statuses/${id}`
return postOrPutStatus(url, accessToken, 'put', text, inReplyToId, mediaIds, return postOrPutStatus(url, accessToken, 'put', text, inReplyToId, mediaIds,
sensitive, spoilerText, visibility, poll) sensitive, spoilerText, visibility, poll, language)
} }
export async function getStatusContext (instanceName, accessToken, statusId) { export async function getStatusContext (instanceName, accessToken, statusId) {

View File

@ -19,7 +19,7 @@
<ComposePoll {realm} {poll} /> <ComposePoll {realm} {poll} />
</div> </div>
{/if} {/if}
<ComposeToolbar {realm} {postPrivacy} {media} {contentWarningShown} {text} {poll} /> <ComposeToolbar {realm} {postPrivacy} {media} {language} {contentWarningShown} {text} {poll} />
<ComposeLengthIndicator {length} {overLimit} /> <ComposeLengthIndicator {length} {overLimit} />
<ComposeMedia {realm} {media} /> <ComposeMedia {realm} {media} />
<ComposeMediaSensitive {realm} {media} {sensitive} {contentWarning} {contentWarningShown} /> <ComposeMediaSensitive {realm} {media} {sensitive} {contentWarning} {contentWarningShown} />
@ -173,6 +173,9 @@
poll: ({ composeData }) => composeData.poll, poll: ({ composeData }) => composeData.poll,
inReplyToId: ({ composeData }) => composeData.inReplyToId, inReplyToId: ({ composeData }) => composeData.inReplyToId,
postPrivacy: ({ postPrivacyKey }) => POST_PRIVACY_OPTIONS.find(_ => _.key === postPrivacyKey), postPrivacy: ({ postPrivacyKey }) => POST_PRIVACY_OPTIONS.find(_ => _.key === postPrivacyKey),
language: ({ composeData, $languages }) => {
return composeData.language ? composeData.language : ($languages.length ? $languages[0] : 'en')
},
defaultPostPrivacyKey: ({ $currentVerifyCredentials }) => ( defaultPostPrivacyKey: ({ $currentVerifyCredentials }) => (
($currentVerifyCredentials && $currentVerifyCredentials.source.privacy) || 'public' ($currentVerifyCredentials && $currentVerifyCredentials.source.privacy) || 'public'
), ),
@ -219,7 +222,8 @@
inReplyToUuid, // typical replies, using Semaphore-specific uuid inReplyToUuid, // typical replies, using Semaphore-specific uuid
inReplyToId, // delete-and-redraft replies, using standard id inReplyToId, // delete-and-redraft replies, using standard id
poll, poll,
sensitive sensitive,
language
} = this.get() } = this.get()
const mediaIds = media.map(_ => _.data.id) const mediaIds = media.map(_ => _.data.id)
const mediaDescriptions = media.map(_ => _.description) const mediaDescriptions = media.map(_ => _.description)
@ -248,7 +252,7 @@
/* no await */ postStatus(realm, text, inReplyTo, mediaIds, /* no await */ postStatus(realm, text, inReplyTo, mediaIds,
sensitive, contentWarning, postPrivacyKey, sensitive, contentWarning, postPrivacyKey,
mediaDescriptions, inReplyToUuid, pollToPost, mediaDescriptions, inReplyToUuid, pollToPost,
mediaFocalPoints) mediaFocalPoints, language)
} }
} }
} }

View File

@ -38,6 +38,12 @@
pressable={true} pressable={true}
pressed={contentWarningShown} pressed={contentWarningShown}
/> />
<IconButton
className="compose-toolbar-button"
label={setLanguageLabel}
href="#fa-language"
on:click="onSetLanguageClick()"
/>
</div> </div>
<input ref:input <input ref:input
on:change="onFileChange(event)" on:change="onFileChange(event)"
@ -74,6 +80,7 @@
import { store } from '../../_store/store.js' import { store } from '../../_store/store.js'
import { importShowEmojiDialog } from '../dialog/asyncDialogs/importShowEmojiDialog.js' import { importShowEmojiDialog } from '../dialog/asyncDialogs/importShowEmojiDialog.js'
import { importShowPostPrivacyDialog } from '../dialog/asyncDialogs/importShowPostPrivacyDialog.js' import { importShowPostPrivacyDialog } from '../dialog/asyncDialogs/importShowPostPrivacyDialog.js'
import { importShowSetLanguageDialog } from '../dialog/asyncDialogs/importShowSetLanguageDialog.js'
import { doMediaUpload } from '../../_actions/media.js' import { doMediaUpload } from '../../_actions/media.js'
import { toggleContentWarningShown } from '../../_actions/contentWarnings.js' import { toggleContentWarningShown } from '../../_actions/contentWarnings.js'
import { mediaAccept } from '../../_static/media.js' import { mediaAccept } from '../../_static/media.js'
@ -91,6 +98,9 @@
computed: { computed: {
postPrivacyLabel: ({ postPrivacy }) => ( postPrivacyLabel: ({ postPrivacy }) => (
formatIntl('intl.postPrivacyLabel', { label: postPrivacy.label }) formatIntl('intl.postPrivacyLabel', { label: postPrivacy.label })
),
setLanguageLabel: ({ language }) => (
formatIntl('intl.setLanguageLabel', { label: language })
) )
}, },
store: () => store, store: () => store,
@ -119,6 +129,11 @@
const showPostPrivacyDialog = await importShowPostPrivacyDialog() const showPostPrivacyDialog = await importShowPostPrivacyDialog()
showPostPrivacyDialog(realm) showPostPrivacyDialog(realm)
}, },
async onSetLanguageClick () {
const { realm } = this.get()
const showSetLanguageDialog = await importShowSetLanguageDialog()
showSetLanguageDialog(realm)
},
onContentWarningClick () { onContentWarningClick () {
const { realm } = this.get() const { realm } = this.get()
toggleContentWarningShown(realm) toggleContentWarningShown(realm)

View File

@ -0,0 +1,3 @@
export const importShowSetLanguageDialog = () => import(
'../creators/showSetLanguageDialog.js'
).then(mod => mod.default)

View File

@ -0,0 +1,50 @@
<ModalDialog
{id}
{label}
{title}
shrinkWidthToFit={true}
background="var(--main-bg)"
>
<GenericDialogList selectable={true} {items} on:click="onClick(event)" />
</ModalDialog>
<script>
import ModalDialog from './ModalDialog.html'
import { store } from '../../../_store/store.js'
import { setLanguage } from '../../../_actions/language.js'
import GenericDialogList from './GenericDialogList.html'
import { show } from '../helpers/showDialog.js'
import { close } from '../helpers/closeDialog.js'
import { oncreate } from '../helpers/onCreateDialog.js'
export default {
oncreate,
components: {
ModalDialog,
GenericDialogList
},
store: () => store,
methods: {
show,
close,
onClick (item) {
const { realm } = this.get()
setLanguage(realm, item.key)
this.close()
}
},
computed: {
composeData: ({ $currentComposeData, realm }) => $currentComposeData[realm] || {},
language: ({ composeData, $languages }) => {
return composeData.language ? composeData.language : ($languages.length ? $languages[0] : 'en')
},
items: ({ language, $languages }) => {
return $languages.map(option => ({
key: option,
label: option.toUpperCase(),
icon: 'fa-language',
selected: language === option
}))
}
}
}
</script>

View File

@ -0,0 +1,10 @@
import SetLanguageDialog from '../components/SetLanguageDialog.html'
import { showDialog } from './showDialog.js'
export default function showSetLanguageDialog (realm) {
return showDialog(SetLanguageDialog, {
label: 'intl.setLanguage',
title: 'intl.setLanguage',
realm
})
}

View File

@ -51,6 +51,8 @@
settings: 'intl.settings', settings: 'intl.settings',
'settings/about': 'intl.aboutApp', 'settings/about': 'intl.aboutApp',
'settings/general': 'intl.general', 'settings/general': 'intl.general',
'settings/languages': 'intl.languages',
'settings/languages/add': 'intl.addLanguage',
'settings/instances': 'intl.instances', 'settings/instances': 'intl.instances',
'settings/instances/add': $isUserLoggedIn ? 'intl.addInstance' : 'intl.logIn' 'settings/instances/add': $isUserLoggedIn ? 'intl.addInstance' : 'intl.logIn'
}), }),

View File

@ -27,6 +27,11 @@
</span> </span>
{/if} {/if}
{/if} {/if}
{#if language}
<span class="status-application status-application-span language-tag">
{language}
</span>
{/if}
<a class="status-favs-reblogs status-reblogs" <a class="status-favs-reblogs status-reblogs"
rel="prefetch" rel="prefetch"
href="/statuses/{originalStatusId}/reblogs" href="/statuses/{originalStatusId}/reblogs"
@ -131,6 +136,10 @@
} }
.language-tag {
text-transform: uppercase;
}
</style> </style>
<script> <script>
import ExternalLink from '../ExternalLink.html' import ExternalLink from '../ExternalLink.html'
@ -181,6 +190,7 @@
} }
return originalStatus.favourites_count || 0 return originalStatus.favourites_count || 0
}, },
language: ({ originalStatus }) => originalStatus.language,
displayAbsoluteFormattedDate: ({ createdAtDateTS, $isMobileSize }) => ( displayAbsoluteFormattedDate: ({ createdAtDateTS, $isMobileSize }) => (
($isMobileSize ? shortAbsoluteDateFormatter : absoluteDateFormatter)().format(createdAtDateTS) ($isMobileSize ? shortAbsoluteDateFormatter : absoluteDateFormatter)().format(createdAtDateTS)
), ),

View File

@ -8,6 +8,9 @@
<SettingsListRow> <SettingsListRow>
<SettingsListButton href="/settings/instances" label="{intl.instances}"/> <SettingsListButton href="/settings/instances" label="{intl.instances}"/>
</SettingsListRow> </SettingsListRow>
<SettingsListRow>
<SettingsListButton href="/settings/languages" label="{intl.languages}"/>
</SettingsListRow>
<SettingsListRow> <SettingsListRow>
<SettingsListButton href="/settings/wellness" label="{intl.wellness}"/> <SettingsListButton href="/settings/wellness" label="{intl.wellness}"/>
</SettingsListRow> </SettingsListRow>

View File

@ -0,0 +1,99 @@
<SettingsLayout page='settings/languages/add' label={intl.addLanguage}>
<h1 id="add-an-language-h1">{intl.addLanguage}</h1>
<div class="add-new-language">
<form on:submit='onSubmitLanguage(event)' aria-labelledby="add-an-language-h1">
{#if !hasIndexedDB || !hasLocalStorage}
<div class="form-error form-error-user-error" role="alert">
{intl.storageError}
</div>
{/if}
<noscript>
<div class="form-error" role="alert">
{intl.javaScriptError}
</div>
</noscript>
<label for="languageInput">{intl.languageCode}</label>
<input type="text" autocapitalize="none" spellcheck="false" id="languageInput"
bind:value='$newLanguage' placeholder="{intl.enterLanguageCode}" required
>
<button class="primary" type="submit" id="submitButton"
disabled={!$newLanguage}>
{intl.add}
</button>
</form>
</div>
<p>
{intl.getLanguageCode}
<a rel="noopener" target="_blank" href="https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry">{intl.ianaLanguageRegistry}</a>
</p>
</SettingsLayout>
<style>
.add-new-language {
background: var(--form-bg);
padding: 5px 10px 15px;
margin: 20px auto;
border: 1px solid var(--form-border);
border-radius: 4px;
}
.form-error {
border: 2px solid var(--warn-color);
border-radius: 2px;
padding: 10px;
font-size: 1.3em;
margin: 5px;
background-color: var(--main-bg);
}
input {
min-width: 70%;
max-width: 100%;
background-color: var(--input-bg);
}
label, input, button, :global(.add-new-language-aside) {
display: block;
margin: 20px 5px;
}
@media (max-width: 767px) {
input {
min-width: 95%;
}
}
</style>
<script>
import SettingsLayout from '../../../_components/settings/SettingsLayout.html'
import { store } from '../../../_store/store.js'
import { addLanguage, resetNewLanguage } from '../../../_actions/addLanguage.js'
export default {
async oncreate () {
this.set({
hasIndexedDB: await testHasIndexedDB(),
hasLocalStorage: testHasLocalStorage()
})
resetNewLanguage()
},
components: {
SettingsLayout
},
store: () => store,
data: () => ({
hasIndexedDB: true,
hasLocalStorage: true
}),
methods: {
onSubmitLanguage (event) {
event.preventDefault()
event.stopPropagation()
addLanguage()
}
}
}
</script>

View File

@ -0,0 +1,40 @@
<SettingsLayout page='settings/languages' label="{intl.languages}">
<h1>{intl.languages}</h1>
<SettingsList>
{#each $languages as language}
<SettingsListRow>
<span class="language-list-item">
{language}
</span>
</SettingsListRow>
{/each}
</SettingsList>
<p>
<a rel="prefetch" href="/settings/languages/add" id="add-link-1">{intl.addLanguage}</a>
</p>
</SettingsLayout>
<style>
.language-list-item {
display: flex;
flex-grow: 1;
text-transform: uppercase;
}
</style>
<script>
import { store } from '../../../_store/store.js'
import SettingsLayout from '../../../_components/settings/SettingsLayout.html'
import SettingsList from '../../../_components/settings/SettingsList.html'
import SettingsListRow from '../../../_components/settings/SettingsListRow.html'
import { formatIntl } from '../../../_utils/formatIntl.js'
export default {
components: {
SettingsLayout,
SettingsList,
SettingsListRow
},
store: () => store
}
</script>

View File

@ -44,7 +44,9 @@ const persistedState = {
reduceMotion: reduceMotion:
!process.browser || !process.browser ||
matchMedia('(prefers-reduced-motion: reduce)').matches, matchMedia('(prefers-reduced-motion: reduce)').matches,
underlineLinks: false underlineLinks: false,
languages: ['en'],
newLanguage: ''
} }
const nonPersistedState = { const nonPersistedState = {

View File

@ -0,0 +1,21 @@
<Title name="{intl.addLanguage}" settingsPage={true} />
<LazyPage {pageComponent} {params} />
<script>
import Title from '../../_components/Title.html'
import LazyPage from '../../_components/LazyPage.html'
import pageComponent from '../../_pages/settings/languages/add.html'
export default {
components: {
Title,
LazyPage
},
data: () => ({
pageComponent
})
}
</script>

View File

@ -0,0 +1,21 @@
<Title name="{intl.languages}" settingsPage={true} />
<LazyPage {pageComponent} {params} />
<script>
import Title from '../../_components/Title.html'
import LazyPage from '../../_components/LazyPage.html'
import pageComponent from '../../_pages/settings/languages/index.html'
export default {
components: {
Title,
LazyPage
},
data: () => ({
pageComponent
})
}
</script>