forked from Mirrors/elk
Merge branch 'main' into userquin/feat-track-scroll-position
This commit is contained in:
commit
369501efca
33 changed files with 667 additions and 116 deletions
45
CODE_OF_CONDUCT.md
Normal file
45
CODE_OF_CONDUCT.md
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
# Code Of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, political party, or sexual identity and orientation. Note, however, that religion, political party, or other ideological affiliation provide no exemptions for the behavior we outline as unacceptable in this Code of Conduct.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to creating a positive environment include:
|
||||||
|
|
||||||
|
- Using welcoming and inclusive language
|
||||||
|
- Being respectful of differing viewpoints and experiences
|
||||||
|
- Gracefully accepting constructive criticism
|
||||||
|
- Focusing on what is best for the community
|
||||||
|
- Showing empathy towards other community members
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
- The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||||
|
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
|
- Public or private harassment
|
||||||
|
- Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||||
|
- Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||||
|
|
||||||
|
## Our Responsibilities
|
||||||
|
|
||||||
|
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||||
|
|
||||||
|
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team by DM at [the Elk Discord](https://chat.elk.zone). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||||
|
|
||||||
|
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
|
@ -117,6 +117,10 @@ Elk uses [Vitest](https://vitest.dev). You can run the test suite with:
|
||||||
nr test
|
nr test
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 📲 PWA
|
||||||
|
|
||||||
|
You can consult the [PWA documentation](https://docs.elk.zone/docs/pwa) to learn more about the PWA capabilities on Elk, how to install Elk PWA in your desktop or mobile device and some hints about PWA stuff on Elk.
|
||||||
|
|
||||||
## 🦄 Stack
|
## 🦄 Stack
|
||||||
|
|
||||||
- [Vite](https://vitejs.dev/) - Next Generation Frontend Tooling
|
- [Vite](https://vitejs.dev/) - Next Generation Frontend Tooling
|
||||||
|
@ -129,7 +133,7 @@ nr test
|
||||||
- [Iconify](https://github.com/iconify/icon-sets#iconify-icon-sets-in-json-format) - Iconify icon sets in JSON format
|
- [Iconify](https://github.com/iconify/icon-sets#iconify-icon-sets-in-json-format) - Iconify icon sets in JSON format
|
||||||
- [Masto.js](https://neet.github.io/masto.js) - Mastodon API client in TypeScript
|
- [Masto.js](https://neet.github.io/masto.js) - Mastodon API client in TypeScript
|
||||||
- [shiki](https://shiki.matsu.io/) - A beautiful Syntax Highlighter
|
- [shiki](https://shiki.matsu.io/) - A beautiful Syntax Highlighter
|
||||||
- [vite-plugin-pwa](https://github.com/vite-pwa/vite-plugin-pwa) - Prompt for update and push notifications
|
- [vite-plugin-pwa](https://github.com/vite-pwa/vite-plugin-pwa) - Prompt for update, Web Push Notifications and Web Share Target API
|
||||||
|
|
||||||
## 👨💻 Contributors
|
## 👨💻 Contributors
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ defineProps<{
|
||||||
text-secondary-light
|
text-secondary-light
|
||||||
>
|
>
|
||||||
<slot name="prepend" />
|
<slot name="prepend" />
|
||||||
<CommonTooltip :content="$t('account.bot')" :disabled="showLabel">
|
<CommonTooltip no-auto-focus :content="$t('account.bot')" :disabled="showLabel">
|
||||||
<div i-mdi:robot-outline />
|
<div i-mdi:robot-outline />
|
||||||
</CommonTooltip>
|
</CommonTooltip>
|
||||||
<div v-if="showLabel">
|
<div v-if="showLabel">
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { decode } from 'blurhash'
|
import { decode } from 'blurhash'
|
||||||
|
|
||||||
const { blurhash, src, srcset } = defineProps<{
|
const { blurhash, src, srcset, shouldLoadImage = true } = defineProps<{
|
||||||
blurhash?: string | null | undefined
|
blurhash?: string | null | undefined
|
||||||
src: string
|
src: string
|
||||||
srcset?: string
|
srcset?: string
|
||||||
|
shouldLoadImage?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
|
@ -19,7 +20,7 @@ const placeholderSrc = $computed(() => {
|
||||||
return getDataUrlFromArr(pixels, 32, 32)
|
return getDataUrlFromArr(pixels, 32, 32)
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
function loadImage() {
|
||||||
const img = document.createElement('img')
|
const img = document.createElement('img')
|
||||||
|
|
||||||
img.onload = () => {
|
img.onload = () => {
|
||||||
|
@ -34,6 +35,16 @@ onMounted(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
isLoaded.value = true
|
isLoaded.value = true
|
||||||
}, 3_000)
|
}, 3_000)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (shouldLoadImage)
|
||||||
|
loadImage()
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => shouldLoadImage, () => {
|
||||||
|
if (shouldLoadImage)
|
||||||
|
loadImage()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// only one icon can be lit up at the same time
|
// only one icon can be lit up at the same time
|
||||||
const moreMenuVisible = ref(false)
|
const moreMenuVisible = ref(false)
|
||||||
|
|
||||||
|
const { notifications } = useNotifications()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -18,7 +20,12 @@ const moreMenuVisible = ref(false)
|
||||||
<div i-ri:search-line />
|
<div i-ri:search-line />
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<NuxtLink to="/notifications" :active-class="moreMenuVisible ? '' : 'text-primary'" flex flex-row items-center place-content-center h-full flex-1 @click="$scrollToTop">
|
<NuxtLink to="/notifications" :active-class="moreMenuVisible ? '' : 'text-primary'" flex flex-row items-center place-content-center h-full flex-1 @click="$scrollToTop">
|
||||||
<div i-ri:notification-4-line />
|
<div flex relative>
|
||||||
|
<div class="i-ri:notification-4-line" text-xl />
|
||||||
|
<div v-if="notifications" class="top-[-0.3rem] right-[-0.3rem]" absolute font-bold rounded-full h-4 w-4 text-xs bg-primary text-inverted flex items-center justify-center>
|
||||||
|
{{ notifications < 10 ? notifications : '•' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<NuxtLink to="/conversations" :active-class="moreMenuVisible ? '' : 'text-primary'" flex flex-row items-center place-content-center h-full flex-1 @click="$scrollToTop">
|
<NuxtLink to="/conversations" :active-class="moreMenuVisible ? '' : 'text-primary'" flex flex-row items-center place-content-center h-full flex-1 @click="$scrollToTop">
|
||||||
<div i-ri:at-line />
|
<div i-ri:at-line />
|
||||||
|
|
|
@ -25,7 +25,7 @@ const toggleApply = () => {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div relative group>
|
<div relative group>
|
||||||
<StatusAttachment :attachment="attachment" w-full />
|
<StatusAttachment :attachment="attachment" w-full is-preview />
|
||||||
<div absolute right-2 top-2>
|
<div absolute right-2 top-2>
|
||||||
<div
|
<div
|
||||||
v-if="removable"
|
v-if="removable"
|
||||||
|
@ -63,7 +63,7 @@ const toggleApply = () => {
|
||||||
{{ $t('action.close') }}
|
{{ $t('action.close') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<StatusAttachment :attachment="attachment" w-full />
|
<StatusAttachment :attachment="attachment" w-full is-preview />
|
||||||
</div>
|
</div>
|
||||||
</ModalDialog>
|
</ModalDialog>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -27,7 +27,7 @@ const emit = defineEmits<{
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
const draftState = useDraft(draftKey, initial)
|
const draftState = useDraft(draftKey, initial)
|
||||||
const { draft, isEmpty } = $(draftState)
|
const { draft } = $(draftState)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isExceedingAttachmentLimit, isUploading, failedAttachments, isOverDropZone,
|
isExceedingAttachmentLimit, isUploading, failedAttachments, isOverDropZone,
|
||||||
|
@ -48,8 +48,6 @@ const { editor } = useTiptap({
|
||||||
set: (newVal) => {
|
set: (newVal) => {
|
||||||
draft.params.status = newVal
|
draft.params.status = newVal
|
||||||
draft.lastUpdated = Date.now()
|
draft.lastUpdated = Date.now()
|
||||||
if (isEmpty)
|
|
||||||
clearEmptyDrafts()
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
placeholder: computed(() => placeholder ?? draft.params.inReplyToId ? t('placeholder.replying') : t('placeholder.default_1')),
|
placeholder: computed(() => placeholder ?? draft.params.inReplyToId ? t('placeholder.replying') : t('placeholder.default_1')),
|
||||||
|
@ -154,7 +152,7 @@ defineExpose({
|
||||||
<div id="state-editing" text-secondary self-center>
|
<div id="state-editing" text-secondary self-center>
|
||||||
{{ $t('state.editing') }}
|
{{ $t('state.editing') }}
|
||||||
</div>
|
</div>
|
||||||
<StatusCard :status="draft.editingStatus" :actions="false" :hover="false" px-0 />
|
<StatusCard :status="draft.editingStatus" :actions="false" :hover="false" is-preview px-0 />
|
||||||
</div>
|
</div>
|
||||||
<div border="b dashed gray/40" />
|
<div border="b dashed gray/40" />
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -66,12 +66,14 @@ const chooseIcon = (i: number, text: string) => {
|
||||||
</CommonDropdown>
|
</CommonDropdown>
|
||||||
<input
|
<input
|
||||||
v-model="form.fieldsAttributes[i - 1].name"
|
v-model="form.fieldsAttributes[i - 1].name"
|
||||||
type="text" placeholder="Label"
|
type="text" placeholder-text-secondary
|
||||||
|
:placeholder="$t('settings.profile.appearance.profile_metadata_label')"
|
||||||
input-base
|
input-base
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
v-model="form.fieldsAttributes[i - 1].value"
|
v-model="form.fieldsAttributes[i - 1].value"
|
||||||
type="text" placeholder="Content"
|
type="text" placeholder-text-secondary
|
||||||
|
:placeholder="$t('settings.profile.appearance.profile_metadata_value')"
|
||||||
input-base
|
input-base
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
defineProps<{
|
const { disabled = false } = defineProps<{
|
||||||
icon?: string
|
icon?: string
|
||||||
text?: string
|
text?: string
|
||||||
checked: boolean
|
checked: boolean
|
||||||
|
disabled?: boolean
|
||||||
}>()
|
}>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -10,10 +11,13 @@ defineProps<{
|
||||||
<button
|
<button
|
||||||
exact-active-class="text-primary"
|
exact-active-class="text-primary"
|
||||||
block w-full group focus:outline-none text-start
|
block w-full group focus:outline-none text-start
|
||||||
|
:disabled="disabled"
|
||||||
|
:class="disabled ? 'opacity-50 cursor-not-allowed' : ''"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
w-full flex w-fit px5 py3 md:gap2 gap4 items-center
|
w-full flex w-fit px5 py3 md:gap2 gap4 items-center
|
||||||
transition-250 group-hover:bg-active
|
transition-250
|
||||||
|
:class="disabled ? '' : 'group-hover:bg-active'"
|
||||||
group-focus-visible:ring="2 current"
|
group-focus-visible:ring="2 current"
|
||||||
>
|
>
|
||||||
<div flex-1 flex items-center md:gap2 gap4>
|
<div flex-1 flex items-center md:gap2 gap4>
|
||||||
|
|
|
@ -53,7 +53,7 @@ function removeDisabledTranslation(code: string) {
|
||||||
{{ availableOption.nativeName }}
|
{{ availableOption.nativeName }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<button class="btn-text" @click="addDisabledTranslation">
|
<button class="btn-text shrink-0" @click="addDisabledTranslation">
|
||||||
{{ $t('settings.language.translations.add') }}
|
{{ $t('settings.language.translations.add') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { clamp } from '@vueuse/core'
|
import { clamp } from '@vueuse/core'
|
||||||
import type { mastodon } from 'masto'
|
import type { mastodon } from 'masto'
|
||||||
|
import { decode } from 'blurhash'
|
||||||
|
|
||||||
const {
|
const {
|
||||||
attachment,
|
attachment,
|
||||||
fullSize = false,
|
fullSize = false,
|
||||||
|
isPreview = false,
|
||||||
} = defineProps<{
|
} = defineProps<{
|
||||||
attachment: mastodon.v1.MediaAttachment
|
attachment: mastodon.v1.MediaAttachment
|
||||||
attachments?: mastodon.v1.MediaAttachment[]
|
attachments?: mastodon.v1.MediaAttachment[]
|
||||||
fullSize?: boolean
|
fullSize?: boolean
|
||||||
|
isPreview?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const src = $computed(() => attachment.previewUrl || attachment.url || attachment.remoteUrl!)
|
const src = $computed(() => attachment.previewUrl || attachment.url || attachment.remoteUrl!)
|
||||||
|
@ -89,19 +92,47 @@ useIntersectionObserver(video, (entries) => {
|
||||||
}, { threshold: 0.75 })
|
}, { threshold: 0.75 })
|
||||||
|
|
||||||
const userSettings = useUserSettings()
|
const userSettings = useUserSettings()
|
||||||
|
|
||||||
|
const shouldLoadAttachment = ref(isPreview || !getPreferences(userSettings.value, 'enableDataSaving'))
|
||||||
|
|
||||||
|
function loadAttachment() {
|
||||||
|
shouldLoadAttachment.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const blurHashSrc = $computed(() => {
|
||||||
|
if (!attachment.blurhash)
|
||||||
|
return ''
|
||||||
|
const pixels = decode(attachment.blurhash, 32, 32)
|
||||||
|
return getDataUrlFromArr(pixels, 32, 32)
|
||||||
|
})
|
||||||
|
|
||||||
|
let videoThumbnail = shouldLoadAttachment.value
|
||||||
|
? attachment.previewUrl
|
||||||
|
: blurHashSrc
|
||||||
|
|
||||||
|
watch(shouldLoadAttachment, () => {
|
||||||
|
videoThumbnail = shouldLoadAttachment
|
||||||
|
? attachment.previewUrl
|
||||||
|
: blurHashSrc
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div relative ma flex :gap="isAudio ? '2' : ''">
|
<div relative ma flex :gap="isAudio ? '2' : ''">
|
||||||
<template v-if="type === 'video'">
|
<template v-if="type === 'video'">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
relative
|
||||||
|
@click="!shouldLoadAttachment ? loadAttachment() : null"
|
||||||
|
>
|
||||||
<video
|
<video
|
||||||
ref="video"
|
ref="video"
|
||||||
preload="none"
|
preload="none"
|
||||||
:poster="attachment.previewUrl"
|
:poster="videoThumbnail"
|
||||||
muted
|
muted
|
||||||
loop
|
loop
|
||||||
playsinline
|
playsinline
|
||||||
controls
|
:controls="shouldLoadAttachment"
|
||||||
rounded-lg
|
rounded-lg
|
||||||
object-cover
|
object-cover
|
||||||
fullscreen:object-contain
|
fullscreen:object-contain
|
||||||
|
@ -111,15 +142,33 @@ const userSettings = useUserSettings()
|
||||||
aspectRatio,
|
aspectRatio,
|
||||||
objectPosition,
|
objectPosition,
|
||||||
}"
|
}"
|
||||||
|
:class="!shouldLoadAttachment ? 'brightness-60 hover:brightness-70 transition-filter' : ''"
|
||||||
>
|
>
|
||||||
<source :src="attachment.url || attachment.previewUrl" type="video/mp4">
|
<source :src="attachment.url || attachment.previewUrl" type="video/mp4">
|
||||||
</video>
|
</video>
|
||||||
|
<span
|
||||||
|
v-if="!shouldLoadAttachment"
|
||||||
|
class="status-attachment-load"
|
||||||
|
absolute
|
||||||
|
text-sm
|
||||||
|
text-white
|
||||||
|
flex flex-col justify-center items-center
|
||||||
|
gap-3 w-6 h-6
|
||||||
|
pointer-events-none
|
||||||
|
i-ri:video-download-line
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="type === 'gifv'">
|
<template v-else-if="type === 'gifv'">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
relative
|
||||||
|
@click="!shouldLoadAttachment ? loadAttachment() : null"
|
||||||
|
>
|
||||||
<video
|
<video
|
||||||
ref="video"
|
ref="video"
|
||||||
preload="none"
|
preload="none"
|
||||||
:poster="attachment.previewUrl"
|
:poster="videoThumbnail"
|
||||||
muted
|
muted
|
||||||
loop
|
loop
|
||||||
playsinline
|
playsinline
|
||||||
|
@ -134,6 +183,18 @@ const userSettings = useUserSettings()
|
||||||
>
|
>
|
||||||
<source :src="attachment.url || attachment.previewUrl" type="video/mp4">
|
<source :src="attachment.url || attachment.previewUrl" type="video/mp4">
|
||||||
</video>
|
</video>
|
||||||
|
<span
|
||||||
|
v-if="!shouldLoadAttachment"
|
||||||
|
class="status-attachment-load"
|
||||||
|
absolute
|
||||||
|
text-sm
|
||||||
|
text-white
|
||||||
|
flex flex-col justify-center items-center
|
||||||
|
gap-3 w-6 h-6
|
||||||
|
pointer-events-none
|
||||||
|
i-ri:video-download-line
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="type === 'audio'">
|
<template v-else-if="type === 'audio'">
|
||||||
<audio controls h-15>
|
<audio controls h-15>
|
||||||
|
@ -149,7 +210,8 @@ const userSettings = useUserSettings()
|
||||||
h-full
|
h-full
|
||||||
w-full
|
w-full
|
||||||
aria-label="Open image preview dialog"
|
aria-label="Open image preview dialog"
|
||||||
@click="openMediaPreview(attachments ? attachments : [attachment], attachments?.indexOf(attachment) || 0)"
|
relative
|
||||||
|
@click="!shouldLoadAttachment ? loadAttachment() : openMediaPreview(attachments ? attachments : [attachment], attachments?.indexOf(attachment) || 0)"
|
||||||
>
|
>
|
||||||
<CommonBlurhash
|
<CommonBlurhash
|
||||||
:blurhash="attachment.blurhash"
|
:blurhash="attachment.blurhash"
|
||||||
|
@ -163,10 +225,24 @@ const userSettings = useUserSettings()
|
||||||
aspectRatio,
|
aspectRatio,
|
||||||
objectPosition,
|
objectPosition,
|
||||||
}"
|
}"
|
||||||
|
:should-load-image="shouldLoadAttachment"
|
||||||
rounded-lg
|
rounded-lg
|
||||||
h-full
|
h-full
|
||||||
w-full
|
w-full
|
||||||
object-cover
|
object-cover
|
||||||
|
:draggable="shouldLoadAttachment"
|
||||||
|
:class="!shouldLoadAttachment ? 'brightness-60 hover:brightness-70 transition-filter' : ''"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
v-if="!shouldLoadAttachment"
|
||||||
|
class="status-attachment-load"
|
||||||
|
absolute
|
||||||
|
text-sm
|
||||||
|
text-white
|
||||||
|
flex flex-col justify-center items-center
|
||||||
|
gap-3 w-6 h-6
|
||||||
|
pointer-events-none
|
||||||
|
i-ri:file-download-line
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
@ -202,3 +278,11 @@ const userSettings = useUserSettings()
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang="postcss">
|
||||||
|
.status-attachment-load {
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
translate: -50% -50%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -8,6 +8,7 @@ const props = withDefaults(
|
||||||
context?: mastodon.v2.FilterContext
|
context?: mastodon.v2.FilterContext
|
||||||
hover?: boolean
|
hover?: boolean
|
||||||
faded?: boolean
|
faded?: boolean
|
||||||
|
isPreview?: boolean
|
||||||
|
|
||||||
// If we know the prev and next status in the timeline, we can simplify the card
|
// If we know the prev and next status in the timeline, we can simplify the card
|
||||||
older?: mastodon.v1.Status
|
older?: mastodon.v1.Status
|
||||||
|
@ -177,7 +178,7 @@ const showReplyTo = $computed(() => !replyToMain && !directReply)
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Content -->
|
<!-- Content -->
|
||||||
<StatusContent :status="status" :newer="newer" :context="context" mb2 :class="{ 'mt-2 mb1': isDM }" />
|
<StatusContent :status="status" :newer="newer" :context="context" :is-preview="isPreview" mb2 :class="{ 'mt-2 mb1': isDM }" />
|
||||||
<StatusActions v-if="actions !== false" v-show="!userSettings.zenMode" :status="status" />
|
<StatusActions v-if="actions !== false" v-show="!userSettings.zenMode" :status="status" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -5,6 +5,7 @@ const { status, context } = defineProps<{
|
||||||
status: mastodon.v1.Status
|
status: mastodon.v1.Status
|
||||||
newer?: mastodon.v1.Status
|
newer?: mastodon.v1.Status
|
||||||
context?: mastodon.v2.FilterContext | 'details'
|
context?: mastodon.v2.FilterContext | 'details'
|
||||||
|
isPreview?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const isDM = $computed(() => status.visibility === 'direct')
|
const isDM = $computed(() => status.visibility === 'direct')
|
||||||
|
@ -44,6 +45,7 @@ const hasSensitiveSpoilerOrMedia = $computed(() => status.sensitive && (!!status
|
||||||
<StatusMedia
|
<StatusMedia
|
||||||
v-if="status.mediaAttachments?.length"
|
v-if="status.mediaAttachments?.length"
|
||||||
:status="status"
|
:status="status"
|
||||||
|
:is-preview="isPreview"
|
||||||
/>
|
/>
|
||||||
<StatusPreviewCard
|
<StatusPreviewCard
|
||||||
v-if="status.card"
|
v-if="status.card"
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { mastodon } from 'masto'
|
import type { mastodon } from 'masto'
|
||||||
|
|
||||||
const { status } = defineProps<{
|
const { status, isPreview = false } = defineProps<{
|
||||||
status: mastodon.v1.Status | mastodon.v1.StatusEdit
|
status: mastodon.v1.Status | mastodon.v1.StatusEdit
|
||||||
fullSize?: boolean
|
fullSize?: boolean
|
||||||
|
isPreview?: boolean
|
||||||
}>()
|
}>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -16,6 +17,7 @@ const { status } = defineProps<{
|
||||||
:full-size="fullSize"
|
:full-size="fullSize"
|
||||||
w-full
|
w-full
|
||||||
h-full
|
h-full
|
||||||
|
:is-preview="isPreview"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -28,6 +28,13 @@ const cardTypeIconMap: Record<mastodon.v1.PreviewCardType, string> = {
|
||||||
video: 'i-ri:play-line',
|
video: 'i-ri:play-line',
|
||||||
rich: 'i-ri:profile-line',
|
rich: 'i-ri:profile-line',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const userSettings = useUserSettings()
|
||||||
|
const shouldLoadAttachment = ref(!getPreferences(userSettings.value, 'enableDataSaving'))
|
||||||
|
|
||||||
|
function loadAttachment() {
|
||||||
|
shouldLoadAttachment.value = true
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -54,6 +61,7 @@ const cardTypeIconMap: Record<mastodon.v1.PreviewCardType, string> = {
|
||||||
'w-full aspect-[1.91]': !isSquare,
|
'w-full aspect-[1.91]': !isSquare,
|
||||||
'rounded-lg': root,
|
'rounded-lg': root,
|
||||||
}"
|
}"
|
||||||
|
relative
|
||||||
>
|
>
|
||||||
<CommonBlurhash
|
<CommonBlurhash
|
||||||
:blurhash="card.blurhash"
|
:blurhash="card.blurhash"
|
||||||
|
@ -61,8 +69,30 @@ const cardTypeIconMap: Record<mastodon.v1.PreviewCardType, string> = {
|
||||||
:width="card.width"
|
:width="card.width"
|
||||||
:height="card.height"
|
:height="card.height"
|
||||||
:alt="alt"
|
:alt="alt"
|
||||||
|
:should-load-image="shouldLoadAttachment"
|
||||||
w-full h-full object-cover
|
w-full h-full object-cover
|
||||||
|
:class="!shouldLoadAttachment ? 'brightness-60' : ''"
|
||||||
/>
|
/>
|
||||||
|
<button
|
||||||
|
v-if="!shouldLoadAttachment"
|
||||||
|
type="button"
|
||||||
|
absolute
|
||||||
|
class="status-preview-card-load bg-black/64"
|
||||||
|
p-2
|
||||||
|
transition
|
||||||
|
rounded
|
||||||
|
hover:bg-black
|
||||||
|
cursor-pointer
|
||||||
|
@click.stop.prevent="!shouldLoadAttachment ? loadAttachment() : null"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
text-sm
|
||||||
|
text-white
|
||||||
|
flex flex-col justify-center items-center
|
||||||
|
gap-3 w-6 h-6
|
||||||
|
i-ri:file-download-line
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
|
@ -76,3 +106,11 @@ const cardTypeIconMap: Record<mastodon.v1.PreviewCardType, string> = {
|
||||||
<StatusPreviewCardInfo :p="isSquare ? 'x-4' : '4'" :root="root" :card="card" :provider="providerName" />
|
<StatusPreviewCardInfo :p="isSquare ? 'x-4' : '4'" :root="root" :card="card" :provider="providerName" />
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang="postcss">
|
||||||
|
.status-preview-card-load {
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
translate: -50% -50%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -30,7 +30,7 @@ export function getDefaultDraft(options: Partial<Mutable<mastodon.v1.CreateStatu
|
||||||
params: {
|
params: {
|
||||||
status: status || '',
|
status: status || '',
|
||||||
inReplyToId,
|
inReplyToId,
|
||||||
visibility: currentUser.value?.account.source.privacy || visibility || 'public',
|
visibility: visibility || 'public',
|
||||||
sensitive: sensitive ?? false,
|
sensitive: sensitive ?? false,
|
||||||
spoilerText: spoilerText || '',
|
spoilerText: spoilerText || '',
|
||||||
language: language || '', // auto inferred from current language on posting
|
language: language || '', // auto inferred from current language on posting
|
||||||
|
@ -141,7 +141,7 @@ export function directMessageUser(account: mastodon.v1.Account) {
|
||||||
|
|
||||||
export function clearEmptyDrafts() {
|
export function clearEmptyDrafts() {
|
||||||
for (const key in currentUserDrafts.value) {
|
for (const key in currentUserDrafts.value) {
|
||||||
if (builtinDraftKeys.includes(key) && !isEmptyDraft(currentUserDrafts.value[key]))
|
if (builtinDraftKeys.includes(key))
|
||||||
continue
|
continue
|
||||||
if (!currentUserDrafts.value[key].params || isEmptyDraft(currentUserDrafts.value[key]))
|
if (!currentUserDrafts.value[key].params || isEmptyDraft(currentUserDrafts.value[key]))
|
||||||
delete currentUserDrafts.value[key]
|
delete currentUserDrafts.value[key]
|
||||||
|
|
|
@ -18,6 +18,7 @@ export interface PreferencesSettings {
|
||||||
hideAccountHoverCard: boolean
|
hideAccountHoverCard: boolean
|
||||||
grayscaleMode: boolean
|
grayscaleMode: boolean
|
||||||
enableAutoplay: boolean
|
enableAutoplay: boolean
|
||||||
|
enableDataSaving: boolean
|
||||||
enablePinchToZoom: boolean
|
enablePinchToZoom: boolean
|
||||||
experimentalVirtualScroller: boolean
|
experimentalVirtualScroller: boolean
|
||||||
experimentalGitHubCards: boolean
|
experimentalGitHubCards: boolean
|
||||||
|
@ -77,6 +78,7 @@ export const DEFAULT__PREFERENCES_SETTINGS: PreferencesSettings = {
|
||||||
hideAccountHoverCard: false,
|
hideAccountHoverCard: false,
|
||||||
grayscaleMode: false,
|
grayscaleMode: false,
|
||||||
enableAutoplay: true,
|
enableAutoplay: true,
|
||||||
|
enableDataSaving: false,
|
||||||
enablePinchToZoom: false,
|
enablePinchToZoom: false,
|
||||||
experimentalVirtualScroller: true,
|
experimentalVirtualScroller: true,
|
||||||
experimentalGitHubCards: true,
|
experimentalGitHubCards: true,
|
||||||
|
|
|
@ -33,7 +33,12 @@ export function usePreferences<T extends keyof PreferencesSettings>(name: T): Re
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPreferences<T extends keyof PreferencesSettings>(userSettings: UserSettings, name: T): PreferencesSettings[T] {
|
export function getPreferences<T extends keyof PreferencesSettings>(userSettings: UserSettings, name: T): PreferencesSettings[T] {
|
||||||
return userSettings?.preferences?.[name] ?? DEFAULT__PREFERENCES_SETTINGS[name]
|
const preference = userSettings?.preferences?.[name] ?? DEFAULT__PREFERENCES_SETTINGS[name]
|
||||||
|
|
||||||
|
if (name === 'enableAutoplay')
|
||||||
|
return getPreferences(userSettings, 'enableDataSaving') ? false : preference
|
||||||
|
|
||||||
|
return preference
|
||||||
}
|
}
|
||||||
|
|
||||||
export function togglePreferences(key: keyof PreferencesSettings) {
|
export function togglePreferences(key: keyof PreferencesSettings) {
|
||||||
|
|
|
@ -43,7 +43,10 @@ export function setupPageHeader() {
|
||||||
titleTemplate = `${titleTemplate.slice(0, 60)}...${titleTemplate.endsWith('"') ? '"' : ''}`
|
titleTemplate = `${titleTemplate.slice(0, 60)}...${titleTemplate.endsWith('"') ? '"' : ''}`
|
||||||
}
|
}
|
||||||
|
|
||||||
titleTemplate += ` | ${t('app_name')}`
|
if (titleTemplate.length)
|
||||||
|
titleTemplate += ' | '
|
||||||
|
|
||||||
|
titleTemplate += t('app_name')
|
||||||
if (buildInfo.env !== 'release')
|
if (buildInfo.env !== 'release')
|
||||||
titleTemplate += ` (${buildInfo.env})`
|
titleTemplate += ` (${buildInfo.env})`
|
||||||
|
|
||||||
|
|
|
@ -50,4 +50,4 @@ nr test
|
||||||
- [Iconify](https://github.com/iconify/icon-sets#iconify-icon-sets-in-json-format) - Iconify icon sets in JSON format
|
- [Iconify](https://github.com/iconify/icon-sets#iconify-icon-sets-in-json-format) - Iconify icon sets in JSON format
|
||||||
- [Masto.js](https://neet.github.io/masto.js) - Mastodon API client in TypeScript
|
- [Masto.js](https://neet.github.io/masto.js) - Mastodon API client in TypeScript
|
||||||
- [shiki](https://shiki.matsu.io/) - A beautiful Syntax Highlighter
|
- [shiki](https://shiki.matsu.io/) - A beautiful Syntax Highlighter
|
||||||
- [vite-plugin-pwa](https://github.com/vite-pwa/vite-plugin-pwa) - Prompt for update and push notifications
|
- [vite-plugin-pwa](https://github.com/vite-pwa/vite-plugin-pwa) - Prompt for update, Web Push Notifications and Web Share Target API
|
||||||
|
|
131
docs/content/80.pwa.md
Normal file
131
docs/content/80.pwa.md
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
# PWA
|
||||||
|
|
||||||
|
Elk provides a PWA (Progressive Web App) that can be installed on your desktop/device. This allows you to use Elk as a native app on your device, and it will work offline.
|
||||||
|
|
||||||
|
The main goal of the PWA is to provide a native app experience on mobile devices with Web Push Notifications and Web Share Target API capabilities.
|
||||||
|
|
||||||
|
Web Share Target will allow you to share content from other apps to Elk in mobile browsers.
|
||||||
|
|
||||||
|
## Mobile Support
|
||||||
|
|
||||||
|
Some mobile browsers will allow you to install the PWA as a native app, and some others will only allow you to add the PWA to your home screen.
|
||||||
|
|
||||||
|
If you're using a mobile browser that supports the PWA installation, you will see a banner at the bottom of the screen with the option to install the PWA.
|
||||||
|
|
||||||
|
If the browser also supports [beforeinstallprompt](https://web.dev/customize-install/) event, ElK will show a prompt (on the right aside on wide screens, or at top on small screens) to allow you to install the PWA directly.
|
||||||
|
|
||||||
|
If the browser doesn't support the PWA installation, check following entries:
|
||||||
|
|
||||||
|
### Safari iOS
|
||||||
|
|
||||||
|
Right now, you cannot use Web Push Notifications on Safari mobile browsers, since it doesn't support the Web Push API yet at operating system level.
|
||||||
|
|
||||||
|
You can check the status of the Push API on Safari mobile browsers on [this page](https://caniuse.com/notifications).
|
||||||
|
|
||||||
|
Visit also [this site](https://firt.dev/notes/pwa-ios/) to check the status of PWA Capabilities on Safari mobile browsers.
|
||||||
|
|
||||||
|
To install a Progressive Web App (PWA) on Safari, follow these steps:
|
||||||
|
- Tap the "Share" icon at the bottom of the screen.
|
||||||
|
- Scroll down and tap "Add to Home Screen".
|
||||||
|
- Customize the name of the app (if desired) and tap "Add".
|
||||||
|
- The PWA should now appear on your home screen.
|
||||||
|
|
||||||
|
### Firefox
|
||||||
|
|
||||||
|
To install a Progressive Web App (PWA) on Firefox, follow these steps:
|
||||||
|
- Look for the install button or icon, usually located in the address bar or in a prompt that appears on the screen.
|
||||||
|
- Click on the install button or icon and follow the prompts to complete the installation.
|
||||||
|
- If you don't see an install button or icon, you can look for the PWA in the Firefox menu. Click on the three horizontal lines in the top/bottom right corner of the browser window to open the menu, then click on "Install" option.
|
||||||
|
|
||||||
|
### Chromium based browsers
|
||||||
|
|
||||||
|
To install a Progressive Web App (PWA) on Chromium based browsers, such as Google Chrome, Microsoft Edge, or Brave, follow these steps:
|
||||||
|
- Click on the menu button (three vertical dots) at the top right corner of the screen.
|
||||||
|
- Select "Install Elk as app" or "Install Elk to Home screen".
|
||||||
|
- Customize the name of the app (if desired) and click "Install" or "Add".
|
||||||
|
- The PWA should now appear on your device's home screen or in your app drawer.
|
||||||
|
|
||||||
|
## PWA Configuration in Elk project
|
||||||
|
|
||||||
|
By default, Elk will enable the PWA integration, but can be disabled by setting `VITE_DEV_PWA` to `false` in your `.env` file.
|
||||||
|
|
||||||
|
You can find the configuration options for the PWA in the [config/pwa.ts](https://github.com/elk-zone/elk/blob/main/config/pwa.ts) module.
|
||||||
|
|
||||||
|
### PWA Web Manifest
|
||||||
|
|
||||||
|
Right now, there is no support for web manifest internationalization and theme color in any browser, we're using a custom module to generate web manifests for theme color and language variants.
|
||||||
|
|
||||||
|
Elk will generate 2 web manifests per locale, one for light theme and one for dark theme.
|
||||||
|
|
||||||
|
You can check web manifest generation on [modules/pwa/i18n.ts](https://github.com/elk-zone/elk/blob/main/modules/pwa/i18n.ts) module.
|
||||||
|
|
||||||
|
### PWA UI Components
|
||||||
|
|
||||||
|
Elk will provide a set of UI components to allow you to customize the PWA installation prompt on browsers with [beforeinstallprompt](https://web.dev/customize-install/) support.
|
||||||
|
|
||||||
|
You can find the PWA installation prompt in the [PwaInstallPrompt](https://github.com/elk-zone/elk/blob/main/components/pwa/PwaInstallPrompt.client.vue) component: will be shown on the right aside on wide screens, or at top on small screens.
|
||||||
|
|
||||||
|
Elk is using prompt for update strategy, so the PWA prompt for update will be shown only if there is a new version of Elk available.
|
||||||
|
|
||||||
|
On small screens, the PWA prompt for update will be shown as a button on the head, you can find it in [PwaBadge](https://github.com/elk-zone/elk/blob/main/components/pwa/PwaBadge.client.vue) component.
|
||||||
|
|
||||||
|
On wide screens, the PWA prompt for update will be shown as a prompt on the right aside, you can find it in [PwaPrompt](https://github.com/elk-zone/elk/blob/main/components/pwa/PwaPrompt.client.vue) component.
|
||||||
|
|
||||||
|
PWA prompt for update will take preference over PWA installation prompt, so if there is a new version of Elk available, the PWA prompt for update will be shown instead of the PWA installation prompt.
|
||||||
|
|
||||||
|
All previous UI components don't have any logic, all them will use the logic provided by the [PWA plugin](https://github.com/elk-zone/elk/blob/main/plugins/pwa.client.ts).
|
||||||
|
|
||||||
|
### Service Worker
|
||||||
|
|
||||||
|
You can find Elk custom service worker in [service-worker folder](https://github.com/elk-zone/elk/blob/main/service-worker), it provides:
|
||||||
|
- [Prompt for update strategy](https://github.com/elk-zone/elk/blob/main/service-worker/sw.ts#L14).
|
||||||
|
- [Web Push Notifications](https://github.com/elk-zone/elk/blob/main/service-worker/web-push-notifications.ts): [push notifications](https://github.com/elk-zone/elk/blob/main/service-worker/sw.ts#L105) and [push notification click](https://github.com/elk-zone/elk/blob/main/service-worker/sw.ts#L106).
|
||||||
|
- [Web Share Target API](https://github.com/elk-zone/elk/blob/main/service-worker/share-target.ts): [share target registration](https://github.com/elk-zone/elk/blob/main/service-worker/sw.ts#L107).
|
||||||
|
|
||||||
|
### Push Notifications Subscription Logic
|
||||||
|
|
||||||
|
Elk will allow you to send push notifications to your users, you can find the logic for Web Push Notifications subscription [composables/push-notifications folder](https://github.com/elk-zone/elk/blob/main/composables/push-notifications).
|
||||||
|
|
||||||
|
There is a limitation on browsers about registering multiple Web Push Notifications: you can only subscribe to one push service per browser per application: trying to register multiple push subscriptions will be treated as spam by the browser.
|
||||||
|
|
||||||
|
If you try to register multiple push subscriptions, the browser will throw an error, and you will need to unregister the previous push subscription before registering a new one.
|
||||||
|
|
||||||
|
### Debugging PWA in development
|
||||||
|
|
||||||
|
To debug the PWA in development, you will need to run `dev:pwa` or `dev:mocked:pwa` script.
|
||||||
|
|
||||||
|
Running one of previous scripts will start a development server with the PWA enabled: you can review the web manifests and debug the service worker in your browser, use any Chromium based browser, since we're registering the service worker using `type: 'module'`.
|
||||||
|
|
||||||
|
You can debug Web Push Notifications in desktop and mobile browser and Web Share Target in your mobile device, using the same URL as the development server.
|
||||||
|
|
||||||
|
Right now, we can only debug on Android Chrome mobile browsers:
|
||||||
|
- Web Push Notifications: you don't need to install de PWA, just enable the notifications in the browser on notifications page or web push notifications settings page.
|
||||||
|
- Web Share Target: you need to install the PWA, and then you can share content from other apps to Elk.
|
||||||
|
|
||||||
|
### Debugging PWA in mobile browsers
|
||||||
|
|
||||||
|
To debug the PWA service worker in your mobile browser, you will need to:
|
||||||
|
1) Enable development options in your Android device:
|
||||||
|
- Go to "Settings" on your device.
|
||||||
|
- Scroll down to "About phone" and tap it.
|
||||||
|
- Locate the "Build number" and tap it repeatedly (usually 7 times) until you see a message saying that you have enabled developer options.
|
||||||
|
- Go back to "Settings" and you should now see "Developer options" listed.
|
||||||
|
- Tap on "Developer options" and you can enable various settings such as USB debugging, mock locations, and more.
|
||||||
|
2) Connect your Android device to your computer using a USB cable.
|
||||||
|
3) Enable USB debugging in your Android device:
|
||||||
|
- Go to "Settings" on your device.
|
||||||
|
- Scroll down to "Developer options" and tap it.
|
||||||
|
- Enable "USB debugging".
|
||||||
|
- Confirm the prompt on your device to allow USB debugging.
|
||||||
|
- Open Chrome/Edge browser in your device.
|
||||||
|
4) Open Chrome on your computer and go to `chrome://inspect/#devices`.
|
||||||
|
- Elk application should be listed in the "Remote Target" section after a few seconds (navigate to any page).
|
||||||
|
- Enter `http://localhost:5314` in the open in a new tab input and click Open button.
|
||||||
|
- Click on the "Inspect" button to open the DevTools.
|
||||||
|
5) Remember to remove the service worker from your device browser using dev tools once you finish testing the service worker:
|
||||||
|
- Go to `Application > Storage`, you should check following checkboxes:
|
||||||
|
- Application: `[x]` Unregister service worker
|
||||||
|
- Storage: `[x]` Local and session storage `[x]` IndexedDB
|
||||||
|
- Cache: `[x]` Cache storage and `[x]` Application cache
|
||||||
|
- Click on Clear site data button
|
||||||
|
- Go to `Application > Service Workers` and check the current service worker is missing or has the status deleted or reduntant.
|
|
@ -402,6 +402,8 @@
|
||||||
"notifications_settings": "Notifications",
|
"notifications_settings": "Notifications",
|
||||||
"preferences": {
|
"preferences": {
|
||||||
"enable_autoplay": "Enable Autoplay",
|
"enable_autoplay": "Enable Autoplay",
|
||||||
|
"enable_data_saving": "Enable data saving",
|
||||||
|
"enable_data_saving_description": "Save data by preventing attachments from automatically loading.",
|
||||||
"enable_pinch_to_zoom": "Enable pinch to zoom",
|
"enable_pinch_to_zoom": "Enable pinch to zoom",
|
||||||
"github_cards": "GitHub Cards",
|
"github_cards": "GitHub Cards",
|
||||||
"grayscale_mode": "Grayscale mode",
|
"grayscale_mode": "Grayscale mode",
|
||||||
|
@ -428,6 +430,8 @@
|
||||||
"label": "Appearance",
|
"label": "Appearance",
|
||||||
"profile_metadata": "Profile metadata",
|
"profile_metadata": "Profile metadata",
|
||||||
"profile_metadata_desc": "You can have up to {0} items displayed as a table on your profile",
|
"profile_metadata_desc": "You can have up to {0} items displayed as a table on your profile",
|
||||||
|
"profile_metadata_label": "Label",
|
||||||
|
"profile_metadata_value": "Content",
|
||||||
"title": "Edit profile"
|
"title": "Edit profile"
|
||||||
},
|
},
|
||||||
"featured_tags": {
|
"featured_tags": {
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
"follow_requested": "Solicitado",
|
"follow_requested": "Solicitado",
|
||||||
"followers": "Seguidoras",
|
"followers": "Seguidoras",
|
||||||
"followers_count": "{0} Seguidoras|{0} Seguidora|{0} Seguidoras",
|
"followers_count": "{0} Seguidoras|{0} Seguidora|{0} Seguidoras",
|
||||||
"following": "Seguimentos",
|
"following": "Seguindo",
|
||||||
"following_count": "Seguindo a {0}",
|
"following_count": "Seguindo a {0}",
|
||||||
"follows_you": "Séguete",
|
"follows_you": "Séguete",
|
||||||
"go_to_profile": "Ir ao perfil",
|
"go_to_profile": "Ir ao perfil",
|
||||||
|
@ -35,6 +35,7 @@
|
||||||
"posts_count": "{0} Publicacións|{0} Publicación|{0} Publicacións",
|
"posts_count": "{0} Publicacións|{0} Publicación|{0} Publicacións",
|
||||||
"profile_description": "Cabeceira do perfil de {0}",
|
"profile_description": "Cabeceira do perfil de {0}",
|
||||||
"profile_unavailable": "Perfil non dispoñible",
|
"profile_unavailable": "Perfil non dispoñible",
|
||||||
|
"request_follow": "Solicitar seguimento",
|
||||||
"unblock": "Desbloquear",
|
"unblock": "Desbloquear",
|
||||||
"unfollow": "Retirar seguimento",
|
"unfollow": "Retirar seguimento",
|
||||||
"unmute": "Reactivar",
|
"unmute": "Reactivar",
|
||||||
|
@ -68,6 +69,7 @@
|
||||||
"save": "Gardar",
|
"save": "Gardar",
|
||||||
"save_changes": "Gardar cambios",
|
"save_changes": "Gardar cambios",
|
||||||
"sign_in": "Acceder",
|
"sign_in": "Acceder",
|
||||||
|
"sign_in_to": "Iniciar sesión en {0}",
|
||||||
"switch_account": "Cambiar de conta",
|
"switch_account": "Cambiar de conta",
|
||||||
"vote": "Votar"
|
"vote": "Votar"
|
||||||
},
|
},
|
||||||
|
@ -116,6 +118,11 @@
|
||||||
"cancel": "Non",
|
"cancel": "Non",
|
||||||
"confirm": "Si"
|
"confirm": "Si"
|
||||||
},
|
},
|
||||||
|
"delete_list": {
|
||||||
|
"cancel": "Cancelar",
|
||||||
|
"confirm": "Eliminar",
|
||||||
|
"title": "Tes a certeza de querer eliminar a lista \"{0}\"?"
|
||||||
|
},
|
||||||
"delete_posts": {
|
"delete_posts": {
|
||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
"confirm": "Eliminar",
|
"confirm": "Eliminar",
|
||||||
|
@ -169,11 +176,28 @@
|
||||||
"desc_para4": "Elk é Código Aberto. Se queres axudar probándoo, aportando a túa opinión, ou colaborando,",
|
"desc_para4": "Elk é Código Aberto. Se queres axudar probándoo, aportando a túa opinión, ou colaborando,",
|
||||||
"desc_para5": "contacta con nós en GitHub",
|
"desc_para5": "contacta con nós en GitHub",
|
||||||
"desc_para6": "e involúcrate.",
|
"desc_para6": "e involúcrate.",
|
||||||
|
"footer_team": "O Equipo Elk",
|
||||||
"title": "Elk está de pre-estrea!"
|
"title": "Elk está de pre-estrea!"
|
||||||
},
|
},
|
||||||
"language": {
|
"language": {
|
||||||
"search": "Buscar"
|
"search": "Buscar"
|
||||||
},
|
},
|
||||||
|
"list": {
|
||||||
|
"add_account": "Engadir conta á lista",
|
||||||
|
"cancel_edit": "Cancelar a edición",
|
||||||
|
"clear_error": "Limpar erro",
|
||||||
|
"create": "Crear",
|
||||||
|
"delete": "Elimina esta lista",
|
||||||
|
"delete_error": "Algo fallou ao querer eliminar a lista",
|
||||||
|
"edit": "Edita esta lista",
|
||||||
|
"edit_error": "Algo fallou ao querer actualizar a lista",
|
||||||
|
"error": "Houbo un fallo ao crear a lista",
|
||||||
|
"error_prefix": "Erro: ",
|
||||||
|
"list_title_placeholder": "Título da lista",
|
||||||
|
"modify_account": "Modificar listas coa conta",
|
||||||
|
"remove_account": "Retirar conta da lista",
|
||||||
|
"save": "Gardar cambios"
|
||||||
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
"block_account": "Bloquear {0}",
|
"block_account": "Bloquear {0}",
|
||||||
"block_domain": "Bloquear o dominio {0}",
|
"block_domain": "Bloquear o dominio {0}",
|
||||||
|
@ -209,16 +233,19 @@
|
||||||
"blocked_domains": "Dominios bloqueados",
|
"blocked_domains": "Dominios bloqueados",
|
||||||
"blocked_users": "Usuarias bloqueadas",
|
"blocked_users": "Usuarias bloqueadas",
|
||||||
"bookmarks": "Marcadores",
|
"bookmarks": "Marcadores",
|
||||||
"built_at": "Versión {0}",
|
"built_at": "Publicada en {0}",
|
||||||
"compose": "Redactar",
|
"compose": "Redactar",
|
||||||
"conversations": "Conversas",
|
"conversations": "Conversas",
|
||||||
"explore": "Explorar",
|
"explore": "Explorar",
|
||||||
"favourites": "Favoritas",
|
"favourites": "Favoritas",
|
||||||
"federated": "Federada",
|
"federated": "Federada",
|
||||||
"home": "Inicio",
|
"home": "Inicio",
|
||||||
|
"list": "Lista",
|
||||||
|
"lists": "Listas",
|
||||||
"local": "Local",
|
"local": "Local",
|
||||||
"muted_users": "Usuarias acaladas",
|
"muted_users": "Usuarias acaladas",
|
||||||
"notifications": "Notificacións",
|
"notifications": "Notificacións",
|
||||||
|
"privacy": "Privacidade",
|
||||||
"profile": "Perfil",
|
"profile": "Perfil",
|
||||||
"search": "Buscar",
|
"search": "Buscar",
|
||||||
"select_feature_flags": "Toggle Feature Flags",
|
"select_feature_flags": "Toggle Feature Flags",
|
||||||
|
@ -243,11 +270,13 @@
|
||||||
"content_warning": "Escribe aquí o aviso",
|
"content_warning": "Escribe aquí o aviso",
|
||||||
"default_1": "En que estás a pensar?",
|
"default_1": "En que estás a pensar?",
|
||||||
"reply_to_account": "Responder a {0}",
|
"reply_to_account": "Responder a {0}",
|
||||||
"replying": "Respondendo",
|
"replying": "Responde aquí",
|
||||||
"the_thread": "a conversa"
|
"the_thread": "a conversa"
|
||||||
},
|
},
|
||||||
"pwa": {
|
"pwa": {
|
||||||
"dismiss": "Desbotar",
|
"dismiss": "Desbotar",
|
||||||
|
"install": "Instalar",
|
||||||
|
"install_title": "Instalar Elk",
|
||||||
"title": "Dispoñible nova versión de Elk!",
|
"title": "Dispoñible nova versión de Elk!",
|
||||||
"update": "Actualizar",
|
"update": "Actualizar",
|
||||||
"update_available_short": "Actualizar Elk",
|
"update_available_short": "Actualizar Elk",
|
||||||
|
@ -280,6 +309,7 @@
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"about": {
|
"about": {
|
||||||
|
"built_at": "Data versión",
|
||||||
"label": "Acerca de",
|
"label": "Acerca de",
|
||||||
"meet_the_team": "Coñece ao equipo",
|
"meet_the_team": "Coñece ao equipo",
|
||||||
"sponsor_action": "Patrocínanos",
|
"sponsor_action": "Patrocínanos",
|
||||||
|
@ -306,7 +336,15 @@
|
||||||
},
|
},
|
||||||
"language": {
|
"language": {
|
||||||
"display_language": "Idioma da interface",
|
"display_language": "Idioma da interface",
|
||||||
"label": "Idioma"
|
"label": "Idioma",
|
||||||
|
"status": "Estado da tradución: {0}/{1} ({2}%)",
|
||||||
|
"translations": {
|
||||||
|
"add": "Engadir",
|
||||||
|
"choose_language": "Elixe idioma",
|
||||||
|
"heading": "Traducións",
|
||||||
|
"hide_specific": "Agochar determinadas traducións",
|
||||||
|
"remove": "Retirar"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"label": "Notificacións",
|
"label": "Notificacións",
|
||||||
|
@ -335,10 +373,13 @@
|
||||||
"save_settings": "Gardar axustes",
|
"save_settings": "Gardar axustes",
|
||||||
"subscription_error": {
|
"subscription_error": {
|
||||||
"clear_error": "Limpar erro",
|
"clear_error": "Limpar erro",
|
||||||
|
"invalid_vapid_key": "A chave pública VAPID non semella válida.",
|
||||||
"permission_denied": "Permiso non concedido: activa as notificacións no navegador.",
|
"permission_denied": "Permiso non concedido: activa as notificacións no navegador.",
|
||||||
|
"repo_link": "Repositorio de Elk en GitHub",
|
||||||
"request_error": "Algo fallou ao solicitar a subscrición, inténtao outra vez e se o erro continúa, informa do problema no repositorio de Elk.",
|
"request_error": "Algo fallou ao solicitar a subscrición, inténtao outra vez e se o erro continúa, informa do problema no repositorio de Elk.",
|
||||||
"title": "Non se puido activar a subscrición a notificacións push",
|
"title": "Non se puido activar a subscrición a notificacións push",
|
||||||
"too_many_registrations": "Debido a limitacións do navegador, Elk non pode usar o servizo de notificacións push para múltiples contas en diferentes servidores. Podes subscribirte ás notificacións push con outra conta e intentalo outra vez."
|
"too_many_registrations": "Debido a limitacións do navegador, Elk non pode usar o servizo de notificacións push para múltiples contas en diferentes servidores. Podes subscribirte ás notificacións push con outra conta e intentalo outra vez.",
|
||||||
|
"vapid_not_supported": "O teu navegador ten soporte para Notificacións Web Push, pero non semella ter incluído o protocolo VAPID."
|
||||||
},
|
},
|
||||||
"title": "Axustes das Notificacións Push",
|
"title": "Axustes das Notificacións Push",
|
||||||
"undo_settings": "Desfacer cambios",
|
"undo_settings": "Desfacer cambios",
|
||||||
|
@ -355,22 +396,29 @@
|
||||||
"re_auth": "Semella que o teu servidor non ten soporte para notificacións Push. Inténtao pechando a sesión e volvendo a acceder, se esta mensaxe continúa aparecendo contacta coa administración do teu servidor."
|
"re_auth": "Semella que o teu servidor non ten soporte para notificacións Push. Inténtao pechando a sesión e volvendo a acceder, se esta mensaxe continúa aparecendo contacta coa administración do teu servidor."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"show_btn": "Ir aos axustes de notificacións"
|
"show_btn": "Ir aos axustes de notificacións",
|
||||||
|
"under_construction": "En desenvolvemento"
|
||||||
},
|
},
|
||||||
"notifications_settings": "Notificacións",
|
"notifications_settings": "Notificacións",
|
||||||
"preferences": {
|
"preferences": {
|
||||||
"enable_autoplay": "Activa reprodución auto.",
|
"enable_autoplay": "Activar reprodución auto.",
|
||||||
|
"enable_pinch_to_zoom": "Activar belisco para aumentar",
|
||||||
"github_cards": "GitHub Cards",
|
"github_cards": "GitHub Cards",
|
||||||
"grayscale_mode": "Modo en escala de grises",
|
"grayscale_mode": "Modo en escala de grises",
|
||||||
"hide_account_hover_card": "Non mostrar tarxetas emerxentes",
|
"hide_account_hover_card": "Non mostrar tarxetas emerxentes",
|
||||||
|
"hide_alt_indi_on_posts": "Agochar indicador ALT nas publicacións",
|
||||||
"hide_boost_count": "Agochar conta de promocións",
|
"hide_boost_count": "Agochar conta de promocións",
|
||||||
"hide_favorite_count": "Agochar conta de favoritos",
|
"hide_favorite_count": "Agochar conta de favoritos",
|
||||||
"hide_follower_count": "Agochar conta de seguimentos",
|
"hide_follower_count": "Agochar conta de seguimentos",
|
||||||
|
"hide_reply_count": "Agochar conta de respostas",
|
||||||
"hide_translation": "Agochar tradución",
|
"hide_translation": "Agochar tradución",
|
||||||
|
"hide_username_emojis": "Agochar emojis nos nomes",
|
||||||
|
"hide_username_emojis_description": "Agocha nas cronoloxías os emojis nos nomes de usuaria. Emojis seguirán visibles na páxina de perfil da usuaria.",
|
||||||
"label": "Preferencias",
|
"label": "Preferencias",
|
||||||
"title": "Características experimentais",
|
"title": "Características experimentais",
|
||||||
"user_picker": "Selector de usuarias",
|
"user_picker": "Selector de conta",
|
||||||
"virtual_scroll": "Desprazamento virtual"
|
"virtual_scroll": "Desprazamento virtual",
|
||||||
|
"wellbeing": "Benestar"
|
||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"appearance": {
|
"appearance": {
|
||||||
|
@ -419,8 +467,10 @@
|
||||||
"filter_removed_phrase": "Eliminada polo filtro",
|
"filter_removed_phrase": "Eliminada polo filtro",
|
||||||
"filter_show_anyway": "Mostrar igualmente",
|
"filter_show_anyway": "Mostrar igualmente",
|
||||||
"img_alt": {
|
"img_alt": {
|
||||||
|
"ALT": "Texto Alt",
|
||||||
"desc": "Descrición",
|
"desc": "Descrición",
|
||||||
"dismiss": "Desbotar"
|
"dismiss": "Desbotar",
|
||||||
|
"read": "Ler a descrición de {0}"
|
||||||
},
|
},
|
||||||
"poll": {
|
"poll": {
|
||||||
"count": "{0} votos|{0} voto|{0} votos",
|
"count": "{0} votos|{0} voto|{0} votos",
|
||||||
|
@ -441,8 +491,10 @@
|
||||||
"edited": "editada o {0}"
|
"edited": "editada o {0}"
|
||||||
},
|
},
|
||||||
"tab": {
|
"tab": {
|
||||||
|
"accounts": "Contas",
|
||||||
"for_you": "Para ti",
|
"for_you": "Para ti",
|
||||||
"hashtags": "Cancelos",
|
"hashtags": "Cancelos",
|
||||||
|
"list": "Lista",
|
||||||
"media": "Multimedia",
|
"media": "Multimedia",
|
||||||
"news": "Novas",
|
"news": "Novas",
|
||||||
"notifications_all": "Todo",
|
"notifications_all": "Todo",
|
||||||
|
@ -506,8 +558,11 @@
|
||||||
"explore_links_intro": "Estes son os temas sobre os que están a conversar agora mesmo as persoas deste servidor e as dos outros servidores da rede descentralizada.",
|
"explore_links_intro": "Estes son os temas sobre os que están a conversar agora mesmo as persoas deste servidor e as dos outros servidores da rede descentralizada.",
|
||||||
"explore_posts_intro": "Estas publicacións deste e outros servidores da rede descentralizada están aumentando a súa popularidade.",
|
"explore_posts_intro": "Estas publicacións deste e outros servidores da rede descentralizada están aumentando a súa popularidade.",
|
||||||
"explore_tags_intro": "Está aumentando a popularidade destes cancelos entre as persoas deste e outros servidores da rede descentralizada.",
|
"explore_tags_intro": "Está aumentando a popularidade destes cancelos entre as persoas deste e outros servidores da rede descentralizada.",
|
||||||
|
"open_editor_tools": "Ferramentas de edición",
|
||||||
"publish_failed": "Close failed messages at the top of editor to republish posts",
|
"publish_failed": "Close failed messages at the top of editor to republish posts",
|
||||||
"toggle_code_block": "Activar bloque de código"
|
"toggle_bold": "Activar grosa",
|
||||||
|
"toggle_code_block": "Activar bloque de código",
|
||||||
|
"toggle_italic": "Activar cursiva"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"add_existing": "Engadir unha conta existente.",
|
"add_existing": "Engadir unha conta existente.",
|
||||||
|
@ -515,6 +570,7 @@
|
||||||
"sign_in_desc": "Inicia sesión para seguir perfís e cancelos, favorecer, compartir ou responder a mensaxes, ou interactuar coa túa conta noutro servidor.",
|
"sign_in_desc": "Inicia sesión para seguir perfís e cancelos, favorecer, compartir ou responder a mensaxes, ou interactuar coa túa conta noutro servidor.",
|
||||||
"sign_in_notice_title": "Vendo {0} datos públicos",
|
"sign_in_notice_title": "Vendo {0} datos públicos",
|
||||||
"sign_out_account": "Pechar sesión {0}",
|
"sign_out_account": "Pechar sesión {0}",
|
||||||
|
"single_instance_sign_in_desc": "Inicia sesión para seguir perfís e cancelos, favorecer, compartir ou responder a mensaxes.",
|
||||||
"tip_no_account": "Se aínda non tes unha conta Mastodon, {0}.",
|
"tip_no_account": "Se aínda non tes unha conta Mastodon, {0}.",
|
||||||
"tip_register_account": "elixe un servidor para crear unha"
|
"tip_register_account": "elixe un servidor para crear unha"
|
||||||
},
|
},
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
"favourites": "Ulubione",
|
"favourites": "Ulubione",
|
||||||
"follow": "Obserwuj",
|
"follow": "Obserwuj",
|
||||||
"follow_back": "Przestań obserwować",
|
"follow_back": "Przestań obserwować",
|
||||||
"follow_requested": "Wniosek",
|
"follow_requested": "Prośba",
|
||||||
"followers": "Obserwujący",
|
"followers": "Obserwujący",
|
||||||
"followers_count": "{0} Obserwujących|{0} Obserwujący|{0} Obserwujących|{0} Obserwujących",
|
"followers_count": "{0} Obserwujących|{0} Obserwujący|{0} Obserwujących|{0} Obserwujących",
|
||||||
"following": "Obserwujesz",
|
"following": "Obserwujesz",
|
||||||
|
@ -35,6 +35,7 @@
|
||||||
"posts_count": "{0} Wpisów|{0} Wpis|{0} Wpisy|{0} Wpisów",
|
"posts_count": "{0} Wpisów|{0} Wpis|{0} Wpisy|{0} Wpisów",
|
||||||
"profile_description": "nagłówek profilu {0}",
|
"profile_description": "nagłówek profilu {0}",
|
||||||
"profile_unavailable": "Profil niedostępny",
|
"profile_unavailable": "Profil niedostępny",
|
||||||
|
"request_follow": "Prośba o śledzenie",
|
||||||
"unblock": "Odblokuj",
|
"unblock": "Odblokuj",
|
||||||
"unfollow": "Przestań obserwować",
|
"unfollow": "Przestań obserwować",
|
||||||
"unmute": "Wyłącz wyciszenie",
|
"unmute": "Wyłącz wyciszenie",
|
||||||
|
@ -68,6 +69,7 @@
|
||||||
"save": "Zapisz",
|
"save": "Zapisz",
|
||||||
"save_changes": "Zapisz zmiany",
|
"save_changes": "Zapisz zmiany",
|
||||||
"sign_in": "Zaloguj się",
|
"sign_in": "Zaloguj się",
|
||||||
|
"sign_in_to": "Zaloguj się do {0}",
|
||||||
"switch_account": "Przełącz konto",
|
"switch_account": "Przełącz konto",
|
||||||
"vote": "Zagłosuj"
|
"vote": "Zagłosuj"
|
||||||
},
|
},
|
||||||
|
@ -116,6 +118,11 @@
|
||||||
"cancel": "Nie",
|
"cancel": "Nie",
|
||||||
"confirm": "Tak"
|
"confirm": "Tak"
|
||||||
},
|
},
|
||||||
|
"delete_list": {
|
||||||
|
"cancel": "Anuluj",
|
||||||
|
"confirm": "Usuń",
|
||||||
|
"title": "Czy na pewno chcesz usunąć listę „{0}”?"
|
||||||
|
},
|
||||||
"delete_posts": {
|
"delete_posts": {
|
||||||
"cancel": "Anuluj",
|
"cancel": "Anuluj",
|
||||||
"confirm": "Usuń",
|
"confirm": "Usuń",
|
||||||
|
@ -140,6 +147,13 @@
|
||||||
"conversation": {
|
"conversation": {
|
||||||
"with": " "
|
"with": " "
|
||||||
},
|
},
|
||||||
|
"custom_cards": {
|
||||||
|
"stackblitz": {
|
||||||
|
"lines": "Lines {0}",
|
||||||
|
"open": "Otwórz",
|
||||||
|
"snippet_from": "Snippet from {0}"
|
||||||
|
}
|
||||||
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"account_not_found": "Nie znaleziono konta {0}",
|
"account_not_found": "Nie znaleziono konta {0}",
|
||||||
"explore-list-empty": "Nic nie jest w tej chwili popularne. Sprawdź później!",
|
"explore-list-empty": "Nic nie jest w tej chwili popularne. Sprawdź później!",
|
||||||
|
@ -162,6 +176,7 @@
|
||||||
"desc_para4": "Elk jest Open Source. Jeśli chcesz pomóc w testowaniu, przekazać opinię lub wnieść swój wkład,",
|
"desc_para4": "Elk jest Open Source. Jeśli chcesz pomóc w testowaniu, przekazać opinię lub wnieść swój wkład,",
|
||||||
"desc_para5": "skontaktuj się z nami na GitHub",
|
"desc_para5": "skontaktuj się z nami na GitHub",
|
||||||
"desc_para6": "i zaangażuj się.",
|
"desc_para6": "i zaangażuj się.",
|
||||||
|
"footer_team": "Zespół Elk",
|
||||||
"title": "Elk w wersji Preview!"
|
"title": "Elk w wersji Preview!"
|
||||||
},
|
},
|
||||||
"language": {
|
"language": {
|
||||||
|
@ -169,8 +184,19 @@
|
||||||
},
|
},
|
||||||
"list": {
|
"list": {
|
||||||
"add_account": "Dodaj konto do listy",
|
"add_account": "Dodaj konto do listy",
|
||||||
|
"cancel_edit": "Anuluj edycję",
|
||||||
|
"clear_error": "Usuń błąd",
|
||||||
|
"create": "Utwórz",
|
||||||
|
"delete": "Usuń listę",
|
||||||
|
"delete_error": "Podczas usuwania listy wystąpił błąd",
|
||||||
|
"edit": "Edytuj listę",
|
||||||
|
"edit_error": "Podczas aktualizowania listy wystąpił błąd",
|
||||||
|
"error": "Podczas tworzenia listy wystąpił błąd",
|
||||||
|
"error_prefix": "Błąd:",
|
||||||
|
"list_title_placeholder": "Tytuł listy",
|
||||||
"modify_account": "Modyfikacja listy",
|
"modify_account": "Modyfikacja listy",
|
||||||
"remove_account": "Usuń konto z listy"
|
"remove_account": "Usuń konto z listy",
|
||||||
|
"save": "Zapisz zmiany"
|
||||||
},
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
"block_account": "Zablokuj {0}",
|
"block_account": "Zablokuj {0}",
|
||||||
|
@ -283,6 +309,7 @@
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"about": {
|
"about": {
|
||||||
|
"built_at": "Kompilacja",
|
||||||
"label": "Informacje",
|
"label": "Informacje",
|
||||||
"meet_the_team": "Poznaj zespół",
|
"meet_the_team": "Poznaj zespół",
|
||||||
"sponsor_action": "Wspomóż nas",
|
"sponsor_action": "Wspomóż nas",
|
||||||
|
@ -310,6 +337,7 @@
|
||||||
"language": {
|
"language": {
|
||||||
"display_language": "Język aplikacji",
|
"display_language": "Język aplikacji",
|
||||||
"label": "Język",
|
"label": "Język",
|
||||||
|
"status": "Stan tłumaczenia: {0}/{1} ({2}%)",
|
||||||
"translations": {
|
"translations": {
|
||||||
"add": "Dodaj",
|
"add": "Dodaj",
|
||||||
"choose_language": "Wybierz język",
|
"choose_language": "Wybierz język",
|
||||||
|
@ -345,10 +373,13 @@
|
||||||
"save_settings": "Zapisz ustawienia",
|
"save_settings": "Zapisz ustawienia",
|
||||||
"subscription_error": {
|
"subscription_error": {
|
||||||
"clear_error": "Usuń błąd",
|
"clear_error": "Usuń błąd",
|
||||||
|
"invalid_vapid_key": "Wydaje się, że klucz publiczny VAPID jest nieprawidłowy.",
|
||||||
"permission_denied": "Brak uprawnień: włącz powiadomienia w przeglądarce.",
|
"permission_denied": "Brak uprawnień: włącz powiadomienia w przeglądarce.",
|
||||||
|
"repo_link": "Repozytorium Elk w Github",
|
||||||
"request_error": "Wystąpił błąd podczas żądania subskrypcji, spróbuj ponownie, a jeśli błąd będzie się powtarzał, zgłoś problem do repozytorium Elk.",
|
"request_error": "Wystąpił błąd podczas żądania subskrypcji, spróbuj ponownie, a jeśli błąd będzie się powtarzał, zgłoś problem do repozytorium Elk.",
|
||||||
"title": "Nie można zasubskrybować powiadomień push",
|
"title": "Nie można zasubskrybować powiadomień push",
|
||||||
"too_many_registrations": "Ze względu na ograniczenia przeglądarki Elk nie może korzystać z usługi powiadomień push dla wielu kont na różnych serwerach. Powinieneś anulować subskrypcję powiadomień push na innym koncie i spróbować ponownie."
|
"too_many_registrations": "Ze względu na ograniczenia przeglądarki Elk nie może korzystać z usługi powiadomień push dla wielu kont na różnych serwerach. Powinieneś anulować subskrypcję powiadomień push na innym koncie i spróbować ponownie.",
|
||||||
|
"vapid_not_supported": "Twoja przeglądarka obsługuje powiadomienia Web Push, ale wydaje się, że nie implementuje protokołu VAPID."
|
||||||
},
|
},
|
||||||
"title": "Ustawienia powiadomień push",
|
"title": "Ustawienia powiadomień push",
|
||||||
"undo_settings": "Cofnij zmiany",
|
"undo_settings": "Cofnij zmiany",
|
||||||
|
@ -365,23 +396,29 @@
|
||||||
"re_auth": "Wygląda na to, że Twój serwer nie obsługuje powiadomień push. Spróbuj wylogować się i zalogować ponownie, jeśli ten komunikat nadal się pojawia, skontaktuj się z administratorem serwera."
|
"re_auth": "Wygląda na to, że Twój serwer nie obsługuje powiadomień push. Spróbuj wylogować się i zalogować ponownie, jeśli ten komunikat nadal się pojawia, skontaktuj się z administratorem serwera."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"show_btn": "Przejdź do ustawień powiadomień"
|
"show_btn": "Przejdź do ustawień powiadomień",
|
||||||
|
"under_construction": "W budowie"
|
||||||
},
|
},
|
||||||
"notifications_settings": "Powiadomienia",
|
"notifications_settings": "Powiadomienia",
|
||||||
"preferences": {
|
"preferences": {
|
||||||
"enable_autoplay": "Włącz autoodtwarzanie",
|
"enable_autoplay": "Włącz autoodtwarzanie",
|
||||||
|
"enable_pinch_to_zoom": "Włącz powiększanie za pomocą gestów",
|
||||||
"github_cards": "GitHub Cards",
|
"github_cards": "GitHub Cards",
|
||||||
"grayscale_mode": "Tryb skali szarości",
|
"grayscale_mode": "Wpisy w odcieniach szarości",
|
||||||
"hide_account_hover_card": "Ukryj wizytówkę konta",
|
"hide_account_hover_card": "Ukryj wizytówkę konta",
|
||||||
|
"hide_alt_indi_on_posts": "Ukryj wskaźnik ALT przy wpisach",
|
||||||
"hide_boost_count": "Ukryj liczbę podbić",
|
"hide_boost_count": "Ukryj liczbę podbić",
|
||||||
"hide_favorite_count": "Ukryj liczbę polubień",
|
"hide_favorite_count": "Ukryj liczbę polubień",
|
||||||
"hide_follower_count": "Ukryj liczbę obserwujących",
|
"hide_follower_count": "Ukryj liczbę obserwujących",
|
||||||
"hide_reply_count": "Ukryj liczbę odpowiedzi",
|
"hide_reply_count": "Ukryj liczbę odpowiedzi",
|
||||||
"hide_translation": "Ukryj funkcję tłumaczenia",
|
"hide_translation": "Ukryj funkcję tłumaczenia",
|
||||||
|
"hide_username_emojis": "Ukryj emotikony w nazwie użytkownika",
|
||||||
|
"hide_username_emojis_description": "Ukrywa emotikony przed nazwami użytkowników na osi czasu. Emotikony będą nadal widoczne w ich profilach.",
|
||||||
"label": "Preferencje",
|
"label": "Preferencje",
|
||||||
"title": "Funkcje eksperymentalne",
|
"title": "Funkcje eksperymentalne",
|
||||||
"user_picker": "User Picker",
|
"user_picker": "User Picker",
|
||||||
"virtual_scroll": "Virtual Scrolling"
|
"virtual_scroll": "Virtual Scrolling",
|
||||||
|
"wellbeing": "Dla dobrego samopoczucia"
|
||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"appearance": {
|
"appearance": {
|
||||||
|
@ -391,6 +428,8 @@
|
||||||
"label": "Wygląd",
|
"label": "Wygląd",
|
||||||
"profile_metadata": "Metadane profilu",
|
"profile_metadata": "Metadane profilu",
|
||||||
"profile_metadata_desc": "Możesz mieć maksymalnie {0} elementy wyświetlane jako tabela w swoim profilu",
|
"profile_metadata_desc": "Możesz mieć maksymalnie {0} elementy wyświetlane jako tabela w swoim profilu",
|
||||||
|
"profile_metadata_label": "Nazwa",
|
||||||
|
"profile_metadata_value": "Zawartość",
|
||||||
"title": "Edytuj profil"
|
"title": "Edytuj profil"
|
||||||
},
|
},
|
||||||
"featured_tags": {
|
"featured_tags": {
|
||||||
|
@ -430,8 +469,10 @@
|
||||||
"filter_removed_phrase": "Usunięto przez filtr",
|
"filter_removed_phrase": "Usunięto przez filtr",
|
||||||
"filter_show_anyway": "Pokaż mimo wszystko",
|
"filter_show_anyway": "Pokaż mimo wszystko",
|
||||||
"img_alt": {
|
"img_alt": {
|
||||||
|
"ALT": "ALT",
|
||||||
"desc": "Opis",
|
"desc": "Opis",
|
||||||
"dismiss": "Zamknij"
|
"dismiss": "Zamknij",
|
||||||
|
"read": "Przeczytaj opis {0}"
|
||||||
},
|
},
|
||||||
"poll": {
|
"poll": {
|
||||||
"count": "{0} głosów|{0} głos|{0} głosy|{0} głosów",
|
"count": "{0} głosów|{0} głos|{0} głosy|{0} głosów",
|
||||||
|
@ -519,8 +560,11 @@
|
||||||
"explore_links_intro": "Te wiadomości obecnie są komentowane przez osoby z tego serwera i pozostałych w zdecentralizowanej sieci.",
|
"explore_links_intro": "Te wiadomości obecnie są komentowane przez osoby z tego serwera i pozostałych w zdecentralizowanej sieci.",
|
||||||
"explore_posts_intro": "Te wpisy z tego i innych serwerów w zdecentralizowanej sieci zyskują teraz popularność na tym serwerze.",
|
"explore_posts_intro": "Te wpisy z tego i innych serwerów w zdecentralizowanej sieci zyskują teraz popularność na tym serwerze.",
|
||||||
"explore_tags_intro": "Te hasztagi zyskują obecnie na popularności wśród osób na tym i innych serwerach zdecentralizowanej sieci.",
|
"explore_tags_intro": "Te hasztagi zyskują obecnie na popularności wśród osób na tym i innych serwerach zdecentralizowanej sieci.",
|
||||||
|
"open_editor_tools": "Narzędzia edycji",
|
||||||
"publish_failed": "Zamknij komunikaty o błędzie u góry edytora, aby ponownie opublikować wpisy",
|
"publish_failed": "Zamknij komunikaty o błędzie u góry edytora, aby ponownie opublikować wpisy",
|
||||||
"toggle_code_block": "Przełączenie do trybu kodowania"
|
"toggle_bold": "Zmień na pogrubienie",
|
||||||
|
"toggle_code_block": "Przełączenie do trybu kodowania",
|
||||||
|
"toggle_italic": "Zmień na kursywę"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"add_existing": "Dodaj istniejące konto",
|
"add_existing": "Dodaj istniejące konto",
|
||||||
|
@ -528,6 +572,7 @@
|
||||||
"sign_in_desc": "Zaloguj się, aby obserwować profile lub hasztagi, dodawać do ulubionych, udostępniać i odpowiadać na wpisy lub wchodzić w interakcje ze swojego konta na innym serwerze.",
|
"sign_in_desc": "Zaloguj się, aby obserwować profile lub hasztagi, dodawać do ulubionych, udostępniać i odpowiadać na wpisy lub wchodzić w interakcje ze swojego konta na innym serwerze.",
|
||||||
"sign_in_notice_title": "Dane publiczne {0}",
|
"sign_in_notice_title": "Dane publiczne {0}",
|
||||||
"sign_out_account": "Wyloguj {0}",
|
"sign_out_account": "Wyloguj {0}",
|
||||||
|
"single_instance_sign_in_desc": "Zaloguj się, aby obserwować profile lub hashtagi, dodawać do ulubionych, udostępniać i odpowiadać na posty.",
|
||||||
"tip_no_account": "Jeśli nie masz jeszcze konta Mastodon, {0}.",
|
"tip_no_account": "Jeśli nie masz jeszcze konta Mastodon, {0}.",
|
||||||
"tip_register_account": "wybierz swój serwer i zarejestruj się"
|
"tip_register_account": "wybierz swój serwer i zarejestruj się"
|
||||||
},
|
},
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
"posts_count": "{0} Publicações|{0} Publicação|{0} Publicações",
|
"posts_count": "{0} Publicações|{0} Publicação|{0} Publicações",
|
||||||
"profile_description": "Descrição de perfil de {0}",
|
"profile_description": "Descrição de perfil de {0}",
|
||||||
"profile_unavailable": "Perfil indisponível",
|
"profile_unavailable": "Perfil indisponível",
|
||||||
|
"request_follow": "Pedir para seguir",
|
||||||
"unblock": "Desbloquear",
|
"unblock": "Desbloquear",
|
||||||
"unfollow": "Deixar de seguir",
|
"unfollow": "Deixar de seguir",
|
||||||
"unmute": "Deixar de silenciar",
|
"unmute": "Deixar de silenciar",
|
||||||
|
@ -68,6 +69,7 @@
|
||||||
"save": "Guardar",
|
"save": "Guardar",
|
||||||
"save_changes": "Guardar alterações",
|
"save_changes": "Guardar alterações",
|
||||||
"sign_in": "Entrar",
|
"sign_in": "Entrar",
|
||||||
|
"sign_in_to": "Entrar em {0}",
|
||||||
"switch_account": "Mudar de conta",
|
"switch_account": "Mudar de conta",
|
||||||
"vote": "Votar"
|
"vote": "Votar"
|
||||||
},
|
},
|
||||||
|
@ -335,6 +337,7 @@
|
||||||
"language": {
|
"language": {
|
||||||
"display_language": "Idioma de Apresentação",
|
"display_language": "Idioma de Apresentação",
|
||||||
"label": "Idioma",
|
"label": "Idioma",
|
||||||
|
"status": "Estado da tradução: {0}/{1} ({2}%)",
|
||||||
"translations": {
|
"translations": {
|
||||||
"add": "Adicionar",
|
"add": "Adicionar",
|
||||||
"choose_language": "Selecionar idioma",
|
"choose_language": "Selecionar idioma",
|
||||||
|
@ -409,7 +412,8 @@
|
||||||
"hide_follower_count": "Esconder contagem de seguidores",
|
"hide_follower_count": "Esconder contagem de seguidores",
|
||||||
"hide_reply_count": "Esconder contagem de respostas",
|
"hide_reply_count": "Esconder contagem de respostas",
|
||||||
"hide_translation": "Esconder botão de tradução",
|
"hide_translation": "Esconder botão de tradução",
|
||||||
"hide_username_emojis": "Esconder emojis no nome de utilizador",
|
"hide_username_emojis": "Esconder emojis do nome de utilizador",
|
||||||
|
"hide_username_emojis_description": "Esconde os emojis do nome de utilizador nas cronologias. Os Emojis continuarão a ser visíveis nos seus perfis.",
|
||||||
"label": "Preferências",
|
"label": "Preferências",
|
||||||
"title": "Funcionalidades Experimentais",
|
"title": "Funcionalidades Experimentais",
|
||||||
"user_picker": "Selecionador de Utilizador",
|
"user_picker": "Selecionador de Utilizador",
|
||||||
|
@ -424,6 +428,8 @@
|
||||||
"label": "Aspeto",
|
"label": "Aspeto",
|
||||||
"profile_metadata": "Metadados de perfil",
|
"profile_metadata": "Metadados de perfil",
|
||||||
"profile_metadata_desc": "Pode ter até {0} itens expostos, em forma de tabela, no seu perfil",
|
"profile_metadata_desc": "Pode ter até {0} itens expostos, em forma de tabela, no seu perfil",
|
||||||
|
"profile_metadata_label": "Rótulo",
|
||||||
|
"profile_metadata_value": "Conteúdo",
|
||||||
"title": "Editar perfil"
|
"title": "Editar perfil"
|
||||||
},
|
},
|
||||||
"featured_tags": {
|
"featured_tags": {
|
||||||
|
@ -554,15 +560,19 @@
|
||||||
"explore_links_intro": "Estas notícias estão, neste momento, a ser faladas por pessoas neste e noutros servidores da rede descentralizada.",
|
"explore_links_intro": "Estas notícias estão, neste momento, a ser faladas por pessoas neste e noutros servidores da rede descentralizada.",
|
||||||
"explore_posts_intro": "Estas publicações deste e de outros servidores na rede descentralizada estão, neste momento, a ganhar popularidade neste servidor.",
|
"explore_posts_intro": "Estas publicações deste e de outros servidores na rede descentralizada estão, neste momento, a ganhar popularidade neste servidor.",
|
||||||
"explore_tags_intro": "Estes hashtags estão, neste momento, a ganhar popularidade entre as pessoas neste e noutros servidores da rede descentralizada.",
|
"explore_tags_intro": "Estes hashtags estão, neste momento, a ganhar popularidade entre as pessoas neste e noutros servidores da rede descentralizada.",
|
||||||
|
"open_editor_tools": "Ferramentas de edição",
|
||||||
"publish_failed": "Fechar mensagens de falha no topo do editor para republicar publicações",
|
"publish_failed": "Fechar mensagens de falha no topo do editor para republicar publicações",
|
||||||
"toggle_code_block": "Alternar bloco de código"
|
"toggle_bold": "Alternar negrito",
|
||||||
|
"toggle_code_block": "Alternar bloco de código",
|
||||||
|
"toggle_italic": "Alternar itálico"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"add_existing": "Adicionar uma conta existente",
|
"add_existing": "Adicionar uma conta existente",
|
||||||
"server_address_label": "Endereço do Servidor Mastodon",
|
"server_address_label": "Endereço do Servidor Mastodon",
|
||||||
"sign_in_desc": "Entre, para seguir pessoas ou hashtags, adicionar aos favoritos, partilhar e responder a publicações, ou interagir a partir da sua conta de outro servidor.",
|
"sign_in_desc": "Inicie sessão, para seguir pessoas ou hashtags, adicionar aos favoritos, partilhar e responder a publicações, ou interagir a partir da sua conta de outro servidor.",
|
||||||
"sign_in_notice_title": "A visualizar os dados públicos de {0}",
|
"sign_in_notice_title": "A visualizar os dados públicos de {0}",
|
||||||
"sign_out_account": "Desconectar {0}",
|
"sign_out_account": "Desconectar {0}",
|
||||||
|
"single_instance_sign_in_desc": "Inicie sessão, para seguir pessoas ou hashtags, adicionar aos favoritos, partilhar e responder a publicações.",
|
||||||
"tip_no_account": "Se ainda não tem uma conta Mastodon, {0}.",
|
"tip_no_account": "Se ainda não tem uma conta Mastodon, {0}.",
|
||||||
"tip_register_account": "escolha um servidor e inscreva-se"
|
"tip_register_account": "escolha um servidor e inscreva-se"
|
||||||
},
|
},
|
||||||
|
|
|
@ -28,12 +28,15 @@
|
||||||
"muted_users": "已靜音的使用者",
|
"muted_users": "已靜音的使用者",
|
||||||
"muting": "已靜音",
|
"muting": "已靜音",
|
||||||
"mutuals": "互相關注",
|
"mutuals": "互相關注",
|
||||||
|
"notifications_on_post_disable": "當 {username} 發布時,停止通知我",
|
||||||
|
"notifications_on_post_enable": "當 {username} 發布時,通知我",
|
||||||
"notify_on_post": "{username} 發文時通知我",
|
"notify_on_post": "{username} 發文時通知我",
|
||||||
"pinned": "置頂的貼文",
|
"pinned": "置頂的貼文",
|
||||||
"posts": "貼文",
|
"posts": "貼文",
|
||||||
"posts_count": "{0} 則貼文",
|
"posts_count": "{0} 則貼文",
|
||||||
"profile_description": "{0} 的個人資料封面",
|
"profile_description": "{0} 的個人資料封面",
|
||||||
"profile_unavailable": "個人資料不可見",
|
"profile_unavailable": "個人資料不可見",
|
||||||
|
"request_follow": "要求追縱",
|
||||||
"unblock": "取消封鎖",
|
"unblock": "取消封鎖",
|
||||||
"unfollow": "取消追蹤",
|
"unfollow": "取消追蹤",
|
||||||
"unmute": "取消靜音",
|
"unmute": "取消靜音",
|
||||||
|
@ -47,6 +50,7 @@
|
||||||
"boost": "轉發",
|
"boost": "轉發",
|
||||||
"boost_count": "{0}",
|
"boost_count": "{0}",
|
||||||
"boosted": "已轉發",
|
"boosted": "已轉發",
|
||||||
|
"clear_publish_failed": "清除發布失敗的訊息",
|
||||||
"clear_upload_failed": "清除上傳失敗",
|
"clear_upload_failed": "清除上傳失敗",
|
||||||
"close": "關閉",
|
"close": "關閉",
|
||||||
"compose": "撰寫",
|
"compose": "撰寫",
|
||||||
|
@ -59,6 +63,7 @@
|
||||||
"more": "更多",
|
"more": "更多",
|
||||||
"next": "下一個",
|
"next": "下一個",
|
||||||
"prev": "上一個",
|
"prev": "上一個",
|
||||||
|
"previous": "之前的",
|
||||||
"publish": "發布",
|
"publish": "發布",
|
||||||
"reply": "回覆",
|
"reply": "回覆",
|
||||||
"reply_count": "{0}",
|
"reply_count": "{0}",
|
||||||
|
@ -89,6 +94,11 @@
|
||||||
"toggle_zen_mode": "切換禪模式"
|
"toggle_zen_mode": "切換禪模式"
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
|
"confirm_dialog": {
|
||||||
|
"cancel": "取消",
|
||||||
|
"confirm": "確認",
|
||||||
|
"title": "你確定嗎?"
|
||||||
|
},
|
||||||
"end_of_list": "清單到底了",
|
"end_of_list": "清單到底了",
|
||||||
"error": "錯誤",
|
"error": "錯誤",
|
||||||
"in": "在",
|
"in": "在",
|
||||||
|
@ -110,9 +120,10 @@
|
||||||
"confirm": "封鎖",
|
"confirm": "封鎖",
|
||||||
"title": "你確定要封鎖 {0} 域名嗎?"
|
"title": "你確定要封鎖 {0} 域名嗎?"
|
||||||
},
|
},
|
||||||
"common": {
|
"delete_list": {
|
||||||
"cancel": "否",
|
"cancel": "取消",
|
||||||
"confirm": "是"
|
"confirm": "確認",
|
||||||
|
"title": "你確定要刪除 \"{0}\" 列表嗎?"
|
||||||
},
|
},
|
||||||
"delete_posts": {
|
"delete_posts": {
|
||||||
"cancel": "取消",
|
"cancel": "取消",
|
||||||
|
@ -138,6 +149,13 @@
|
||||||
"conversation": {
|
"conversation": {
|
||||||
"with": "與"
|
"with": "與"
|
||||||
},
|
},
|
||||||
|
"custom_cards": {
|
||||||
|
"stackblitz": {
|
||||||
|
"lines": "第 {0} 行",
|
||||||
|
"open": "開啟",
|
||||||
|
"snippet_from": "取自 {0} 的片段"
|
||||||
|
}
|
||||||
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"account_not_found": "未找到使用者 {0}",
|
"account_not_found": "未找到使用者 {0}",
|
||||||
"explore-list-empty": "目前沒有熱門話題,稍後再來看看吧!",
|
"explore-list-empty": "目前沒有熱門話題,稍後再來看看吧!",
|
||||||
|
@ -147,6 +165,12 @@
|
||||||
"unsupported_file_format": "不支援的檔案格式"
|
"unsupported_file_format": "不支援的檔案格式"
|
||||||
},
|
},
|
||||||
"help": {
|
"help": {
|
||||||
|
"build_preview": {
|
||||||
|
"desc1": "您當前正在查看來自開源社區、預覽版的鹿鳴 - {0}。",
|
||||||
|
"desc2": "可能包含末經審查或是惡意修改的內容。",
|
||||||
|
"desc3": "請不要使用真實的帳號登入。",
|
||||||
|
"title": "預覽部署"
|
||||||
|
},
|
||||||
"desc_highlight": "可能會在某些地方出現一些 bug 或缺少的功能。",
|
"desc_highlight": "可能會在某些地方出現一些 bug 或缺少的功能。",
|
||||||
"desc_para1": "感謝你有興趣嘗試鹿鳴,一個我們正在積極開發的通用 Mastodon 用戶端。",
|
"desc_para1": "感謝你有興趣嘗試鹿鳴,一個我們正在積極開發的通用 Mastodon 用戶端。",
|
||||||
"desc_para2": "我們正在努力開發中,並隨著時間的推移不斷完善。",
|
"desc_para2": "我們正在努力開發中,並隨著時間的推移不斷完善。",
|
||||||
|
@ -154,15 +178,33 @@
|
||||||
"desc_para4": "鹿鳴是開源的,如果你願意幫助測試、提供回饋或作出貢獻,",
|
"desc_para4": "鹿鳴是開源的,如果你願意幫助測試、提供回饋或作出貢獻,",
|
||||||
"desc_para5": "在 GitHub 上聯繫我們",
|
"desc_para5": "在 GitHub 上聯繫我們",
|
||||||
"desc_para6": "來參與其中。",
|
"desc_para6": "來參與其中。",
|
||||||
|
"footer_team": "鹿鳴開發團隊",
|
||||||
"title": "預覽鹿鳴!"
|
"title": "預覽鹿鳴!"
|
||||||
},
|
},
|
||||||
"language": {
|
"language": {
|
||||||
"search": "搜尋"
|
"search": "搜尋"
|
||||||
},
|
},
|
||||||
|
"list": {
|
||||||
|
"add_account": "在列表中添加帳號",
|
||||||
|
"cancel_edit": "取消編輯",
|
||||||
|
"clear_error": "清除錯誤",
|
||||||
|
"create": "建立",
|
||||||
|
"delete": "刪除這個列表",
|
||||||
|
"delete_error": "刪除列表時發生錯誤",
|
||||||
|
"edit": "編輯這個列表",
|
||||||
|
"edit_error": "更新列表時發生錯誤",
|
||||||
|
"error": "建立列表時發生錯誤",
|
||||||
|
"error_prefix": "錯誤:",
|
||||||
|
"list_title_placeholder": "列表標題",
|
||||||
|
"modify_account": "修改列表中的帳號",
|
||||||
|
"remove_account": "移除列表中的帳號",
|
||||||
|
"save": "保存變更"
|
||||||
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
"block_account": "封鎖 {0}",
|
"block_account": "封鎖 {0}",
|
||||||
"block_domain": "封鎖的域名 {0}",
|
"block_domain": "封鎖的域名 {0}",
|
||||||
"copy_link_to_post": "複製這篇貼文的連結",
|
"copy_link_to_post": "複製這篇貼文的連結",
|
||||||
|
"copy_original_link_to_post": "複製貼文的原網址",
|
||||||
"delete": "刪除",
|
"delete": "刪除",
|
||||||
"delete_and_redraft": "刪除並重新編輯",
|
"delete_and_redraft": "刪除並重新編輯",
|
||||||
"direct_message_account": "私訊 {0}",
|
"direct_message_account": "私訊 {0}",
|
||||||
|
@ -200,9 +242,12 @@
|
||||||
"favourites": "喜歡",
|
"favourites": "喜歡",
|
||||||
"federated": "聯邦",
|
"federated": "聯邦",
|
||||||
"home": "首頁",
|
"home": "首頁",
|
||||||
|
"list": "列表",
|
||||||
|
"lists": "列表",
|
||||||
"local": "本站",
|
"local": "本站",
|
||||||
"muted_users": "已靜音的使用者",
|
"muted_users": "已靜音的使用者",
|
||||||
"notifications": "通知",
|
"notifications": "通知",
|
||||||
|
"privacy": "隱私權政策",
|
||||||
"profile": "個人資料",
|
"profile": "個人資料",
|
||||||
"search": "搜尋",
|
"search": "搜尋",
|
||||||
"select_feature_flags": "功能開關",
|
"select_feature_flags": "功能開關",
|
||||||
|
@ -232,6 +277,8 @@
|
||||||
},
|
},
|
||||||
"pwa": {
|
"pwa": {
|
||||||
"dismiss": "忽略",
|
"dismiss": "忽略",
|
||||||
|
"install": "安裝",
|
||||||
|
"install_title": "安裝鹿鳴",
|
||||||
"title": "鹿鳴存在新的更新",
|
"title": "鹿鳴存在新的更新",
|
||||||
"update": "更新",
|
"update": "更新",
|
||||||
"update_available_short": "更新鹿鳴",
|
"update_available_short": "更新鹿鳴",
|
||||||
|
@ -264,6 +311,7 @@
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"about": {
|
"about": {
|
||||||
|
"built_at": "構建於",
|
||||||
"label": "關於",
|
"label": "關於",
|
||||||
"meet_the_team": "認識我們",
|
"meet_the_team": "認識我們",
|
||||||
"sponsor_action": "贊助我們",
|
"sponsor_action": "贊助我們",
|
||||||
|
@ -271,7 +319,8 @@
|
||||||
"sponsors": "贊助",
|
"sponsors": "贊助",
|
||||||
"sponsors_body_1": "鹿鳴的誕生得感謝以下的慷慨贊助與幫助:",
|
"sponsors_body_1": "鹿鳴的誕生得感謝以下的慷慨贊助與幫助:",
|
||||||
"sponsors_body_2": "以及所有贊助鹿鳴成員的公司和個人,以及隊員們。",
|
"sponsors_body_2": "以及所有贊助鹿鳴成員的公司和個人,以及隊員們。",
|
||||||
"sponsors_body_3": "如果你喜歡鹿鳴,可以考慮贊助我們:"
|
"sponsors_body_3": "如果你喜歡鹿鳴,可以考慮贊助我們:",
|
||||||
|
"version": "版本"
|
||||||
},
|
},
|
||||||
"account_settings": {
|
"account_settings": {
|
||||||
"description": "在 Mastodon UI 中編輯你的帳號設定",
|
"description": "在 Mastodon UI 中編輯你的帳號設定",
|
||||||
|
@ -289,7 +338,14 @@
|
||||||
},
|
},
|
||||||
"language": {
|
"language": {
|
||||||
"display_language": "顯示語言",
|
"display_language": "顯示語言",
|
||||||
"label": "語言"
|
"label": "語言",
|
||||||
|
"translations": {
|
||||||
|
"add": "添加",
|
||||||
|
"choose_language": "選擇語言",
|
||||||
|
"heading": "翻譯",
|
||||||
|
"hide_specific": "隱藏特定的翻譯",
|
||||||
|
"remove": "刪除"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"label": "通知",
|
"label": "通知",
|
||||||
|
@ -318,10 +374,13 @@
|
||||||
"save_settings": "儲存設定變更",
|
"save_settings": "儲存設定變更",
|
||||||
"subscription_error": {
|
"subscription_error": {
|
||||||
"clear_error": "清除錯誤",
|
"clear_error": "清除錯誤",
|
||||||
|
"invalid_vapid_key": "VAPID 公鑰似乎無效。",
|
||||||
"permission_denied": "權限不足:請在你的瀏覽器中打開通知權限。",
|
"permission_denied": "權限不足:請在你的瀏覽器中打開通知權限。",
|
||||||
|
"repo_link": "鹿鳴的 GitHub 儲存庫",
|
||||||
"request_error": "請求訂閱時發生了一個錯誤,請再次嘗試。如錯誤仍然存在,請到鹿鳴儲存庫中報告這一問題。",
|
"request_error": "請求訂閱時發生了一個錯誤,請再次嘗試。如錯誤仍然存在,請到鹿鳴儲存庫中報告這一問題。",
|
||||||
"title": "無法訂閱推播通知。",
|
"title": "無法訂閱推播通知。",
|
||||||
"too_many_registrations": "由於瀏覽器限制,鹿鳴無法為不同伺服器上的多個帳號使用推播通知服務。 你應該取消訂閱其他帳號的推送通知,然後重試。"
|
"too_many_registrations": "由於瀏覽器限制,鹿鳴無法為不同伺服器上的多個帳號使用推播通知服務。 你應該取消訂閱其他帳號的推送通知,然後重試。",
|
||||||
|
"vapid_not_supported": "您的瀏覽器支持 Web Push Notifications,但似乎沒有實現 VAPIO 的協議。"
|
||||||
},
|
},
|
||||||
"title": "推播通知設定",
|
"title": "推播通知設定",
|
||||||
"undo_settings": "撤銷設定變更",
|
"undo_settings": "撤銷設定變更",
|
||||||
|
@ -338,18 +397,28 @@
|
||||||
"re_auth": "您的伺服器似乎不支援推播通知。嘗試退出使用者並重新登入。如果此消息仍然出現,請聯繫您伺服器的管理員。"
|
"re_auth": "您的伺服器似乎不支援推播通知。嘗試退出使用者並重新登入。如果此消息仍然出現,請聯繫您伺服器的管理員。"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"show_btn": "前往通知設定"
|
"show_btn": "前往通知設定",
|
||||||
|
"under_construction": "建立中"
|
||||||
},
|
},
|
||||||
"notifications_settings": "通知",
|
"notifications_settings": "通知",
|
||||||
"preferences": {
|
"preferences": {
|
||||||
|
"enable_autoplay": "啟用自動播放功能",
|
||||||
|
"enable_pinch_to_zoom": "啟用雙指縮放功能",
|
||||||
"github_cards": "GitHub 卡片",
|
"github_cards": "GitHub 卡片",
|
||||||
|
"grayscale_mode": "深色模式",
|
||||||
|
"hide_account_hover_card": "隱藏帳號浮動卡片",
|
||||||
|
"hide_alt_indi_on_posts": "隱藏貼文上的 alt 指示",
|
||||||
"hide_boost_count": "隱藏轉發數",
|
"hide_boost_count": "隱藏轉發數",
|
||||||
"hide_favorite_count": "隱藏收藏數",
|
"hide_favorite_count": "隱藏收藏數",
|
||||||
"hide_follower_count": "隱藏粉絲數",
|
"hide_follower_count": "隱藏粉絲數",
|
||||||
|
"hide_reply_count": "隱藏回覆數",
|
||||||
|
"hide_translation": "隱藏翻譯",
|
||||||
|
"hide_username_emojis": "隱藏使用者名稱上的表情符號",
|
||||||
"label": "偏好設定",
|
"label": "偏好設定",
|
||||||
"title": "實驗功能",
|
"title": "實驗功能",
|
||||||
"user_picker": "使用者選擇器",
|
"user_picker": "使用者選擇器",
|
||||||
"virtual_scroll": "虛擬滾動"
|
"virtual_scroll": "虛擬滾動",
|
||||||
|
"wellbeing": "福利"
|
||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"appearance": {
|
"appearance": {
|
||||||
|
@ -385,6 +454,7 @@
|
||||||
"edited": "(已編輯)",
|
"edited": "(已編輯)",
|
||||||
"editing": "編輯中",
|
"editing": "編輯中",
|
||||||
"loading": "載入中...",
|
"loading": "載入中...",
|
||||||
|
"publish_failed": "發布失敗",
|
||||||
"publishing": "發布中",
|
"publishing": "發布中",
|
||||||
"upload_failed": "上傳失敗",
|
"upload_failed": "上傳失敗",
|
||||||
"uploading": "上傳中..."
|
"uploading": "上傳中..."
|
||||||
|
@ -394,10 +464,13 @@
|
||||||
"edited": "在 {0} 編輯了",
|
"edited": "在 {0} 編輯了",
|
||||||
"favourited_by": "被喜歡",
|
"favourited_by": "被喜歡",
|
||||||
"filter_hidden_phrase": "篩選依據",
|
"filter_hidden_phrase": "篩選依據",
|
||||||
|
"filter_removed_phrase": "從篩選中移除",
|
||||||
"filter_show_anyway": "仍然顯示",
|
"filter_show_anyway": "仍然顯示",
|
||||||
"img_alt": {
|
"img_alt": {
|
||||||
|
"ALT": "ALT",
|
||||||
"desc": "描述",
|
"desc": "描述",
|
||||||
"dismiss": "關閉"
|
"dismiss": "關閉",
|
||||||
|
"read": "閱讀 {0} 的說明"
|
||||||
},
|
},
|
||||||
"poll": {
|
"poll": {
|
||||||
"count": "{0} 次投票",
|
"count": "{0} 次投票",
|
||||||
|
@ -418,8 +491,10 @@
|
||||||
"edited": "在 {0} 編輯了"
|
"edited": "在 {0} 編輯了"
|
||||||
},
|
},
|
||||||
"tab": {
|
"tab": {
|
||||||
|
"accounts": "帳號",
|
||||||
"for_you": "推薦追蹤",
|
"for_you": "推薦追蹤",
|
||||||
"hashtags": "話題標籤",
|
"hashtags": "話題標籤",
|
||||||
|
"list": "列表",
|
||||||
"media": "媒體",
|
"media": "媒體",
|
||||||
"news": "最新消息",
|
"news": "最新消息",
|
||||||
"notifications_all": "全部",
|
"notifications_all": "全部",
|
||||||
|
@ -483,6 +558,7 @@
|
||||||
"explore_links_intro": "這些新聞故事正被本站和分散式網路上其他站點的使用者談論。",
|
"explore_links_intro": "這些新聞故事正被本站和分散式網路上其他站點的使用者談論。",
|
||||||
"explore_posts_intro": "來自本站和分散式網路上其他站點的這些嘟文正在本站引起關注。",
|
"explore_posts_intro": "來自本站和分散式網路上其他站點的這些嘟文正在本站引起關注。",
|
||||||
"explore_tags_intro": "這些標籤正在本站和分散式網路上其他站點的使用者中引起關注。",
|
"explore_tags_intro": "這些標籤正在本站和分散式網路上其他站點的使用者中引起關注。",
|
||||||
|
"publish_failed": "請關閉編輯器上方的失敗訊息以重新發布貼文。",
|
||||||
"toggle_code_block": "切換程式碼區塊"
|
"toggle_code_block": "切換程式碼區塊"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
|
import { mkdir, writeFile } from 'node:fs/promises'
|
||||||
import { defineNuxtModule } from '@nuxt/kit'
|
import { defineNuxtModule } from '@nuxt/kit'
|
||||||
import type { VitePluginPWAAPI } from 'vite-plugin-pwa'
|
import type { VitePluginPWAAPI } from 'vite-plugin-pwa'
|
||||||
import { VitePWA } from 'vite-plugin-pwa'
|
import { VitePWA } from 'vite-plugin-pwa'
|
||||||
import type { Plugin } from 'vite'
|
import type { Plugin } from 'vite'
|
||||||
|
import { join } from 'pathe'
|
||||||
import type { VitePWANuxtOptions } from './types'
|
import type { VitePWANuxtOptions } from './types'
|
||||||
import { configurePWAOptions } from './config'
|
import { configurePWAOptions } from './config'
|
||||||
import { type LocalizedWebManifest, createI18n, pwaLocales } from './i18n'
|
import { type LocalizedWebManifest, createI18n, pwaLocales } from './i18n'
|
||||||
|
@ -26,6 +28,13 @@ export default defineNuxtModule<VitePWANuxtOptions>({
|
||||||
nuxt.options.appConfig = nuxt.options.appConfig || {}
|
nuxt.options.appConfig = nuxt.options.appConfig || {}
|
||||||
nuxt.options.appConfig.pwaEnabled = !options.disable
|
nuxt.options.appConfig.pwaEnabled = !options.disable
|
||||||
|
|
||||||
|
nuxt.options.nitro.publicAssets = nuxt.options.nitro.publicAssets || []
|
||||||
|
const manifestDir = join(nuxt.options.buildDir, 'manifests')
|
||||||
|
nuxt.options.nitro.publicAssets.push({
|
||||||
|
dir: manifestDir,
|
||||||
|
baseURL: '/',
|
||||||
|
maxAge: 0,
|
||||||
|
})
|
||||||
// TODO: combine with configurePWAOptions?
|
// TODO: combine with configurePWAOptions?
|
||||||
nuxt.hook('nitro:init', (nitro) => {
|
nuxt.hook('nitro:init', (nitro) => {
|
||||||
options.outDir = nitro.options.output.publicDir
|
options.outDir = nitro.options.output.publicDir
|
||||||
|
@ -50,24 +59,19 @@ export default defineNuxtModule<VitePWANuxtOptions>({
|
||||||
throw new Error(`No webmanifest found for locale/theme ${entry}`)
|
throw new Error(`No webmanifest found for locale/theme ${entry}`)
|
||||||
return JSON.stringify(manifest)
|
return JSON.stringify(manifest)
|
||||||
}
|
}
|
||||||
|
if (isClient) {
|
||||||
viteInlineConfig.plugins.push({
|
viteInlineConfig.plugins.push({
|
||||||
name: 'elk:pwa:locales:build',
|
name: 'elk:pwa:locales:build',
|
||||||
apply: 'build',
|
apply: 'build',
|
||||||
generateBundle(_, bundle) {
|
async writeBundle(_options, bundle) {
|
||||||
if (options.disable || !bundle)
|
if (options.disable || !bundle)
|
||||||
return
|
return
|
||||||
|
await mkdir(manifestDir, { recursive: true })
|
||||||
Object.keys(webmanifests!).map(wm => [wm, `manifest-${wm}.webmanifest`]).forEach(([wm, fileName]) => {
|
for (const wm in webmanifests)
|
||||||
bundle[fileName] = {
|
await writeFile(join(manifestDir, `manifest-${wm}.webmanifest`), generateManifest(wm))
|
||||||
needsCodeReference: false,
|
|
||||||
type: 'asset',
|
|
||||||
name: undefined,
|
|
||||||
source: generateManifest(wm),
|
|
||||||
fileName,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
}
|
||||||
viteInlineConfig.plugins.push({
|
viteInlineConfig.plugins.push({
|
||||||
name: 'elk:pwa:locales:dev',
|
name: 'elk:pwa:locales:dev',
|
||||||
apply: 'serve',
|
apply: 'serve',
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@elk-zone/elk",
|
"name": "@elk-zone/elk",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.7.3",
|
"version": "0.7.4",
|
||||||
"packageManager": "pnpm@7.9.0",
|
"packageManager": "pnpm@7.9.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://elk.zone/",
|
"homepage": "https://elk.zone/",
|
||||||
|
|
|
@ -27,6 +27,7 @@ onReactivated(() => {
|
||||||
timeline-title-style
|
timeline-title-style
|
||||||
:content="account ? getDisplayName(account) : t('nav.profile')"
|
:content="account ? getDisplayName(account) : t('nav.profile')"
|
||||||
:show-emojis="!getPreferences(userSettings, 'hideUsernameEmojis')"
|
:show-emojis="!getPreferences(userSettings, 'hideUsernameEmojis')"
|
||||||
|
:markdown="false"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -33,10 +33,20 @@ const userSettings = useUserSettings()
|
||||||
</SettingsToggleItem>
|
</SettingsToggleItem>
|
||||||
<SettingsToggleItem
|
<SettingsToggleItem
|
||||||
:checked="getPreferences(userSettings, 'enableAutoplay')"
|
:checked="getPreferences(userSettings, 'enableAutoplay')"
|
||||||
|
:disabled="getPreferences(userSettings, 'enableDataSaving')"
|
||||||
@click="togglePreferences('enableAutoplay')"
|
@click="togglePreferences('enableAutoplay')"
|
||||||
>
|
>
|
||||||
{{ $t('settings.preferences.enable_autoplay') }}
|
{{ $t('settings.preferences.enable_autoplay') }}
|
||||||
</SettingsToggleItem>
|
</SettingsToggleItem>
|
||||||
|
<SettingsToggleItem
|
||||||
|
:checked="getPreferences(userSettings, 'enableDataSaving')"
|
||||||
|
@click="togglePreferences('enableDataSaving')"
|
||||||
|
>
|
||||||
|
{{ $t("settings.preferences.enable_data_saving") }}
|
||||||
|
<template #description>
|
||||||
|
{{ $t("settings.preferences.enable_data_saving_description") }}
|
||||||
|
</template>
|
||||||
|
</SettingsToggleItem>
|
||||||
<SettingsToggleItem
|
<SettingsToggleItem
|
||||||
:checked="getPreferences(userSettings, 'enablePinchToZoom')"
|
:checked="getPreferences(userSettings, 'enablePinchToZoom')"
|
||||||
@click="togglePreferences('enablePinchToZoom')"
|
@click="togglePreferences('enablePinchToZoom')"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { sendRedirect } from 'h3'
|
import { sendRedirect } from 'h3'
|
||||||
|
|
||||||
const BOT_RE = /bot\b|index|spider|facebookexternalhit|crawl|wget|slurp|mediapartners-google/i
|
const BOT_RE = /bot\b|index|spider|facebookexternalhit|crawl|wget|slurp|mediapartners-google|whatsapp/i
|
||||||
|
|
||||||
export default defineNuxtPlugin(async (nuxtApp) => {
|
export default defineNuxtPlugin(async (nuxtApp) => {
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
|
@ -2,10 +2,16 @@ import flatten from 'flat'
|
||||||
import { createResolver } from '@nuxt/kit'
|
import { createResolver } from '@nuxt/kit'
|
||||||
import fs from 'fs-extra'
|
import fs from 'fs-extra'
|
||||||
import { currentLocales } from '../config/i18n'
|
import { currentLocales } from '../config/i18n'
|
||||||
import vsCodeConfig from '../.vscode/settings.json'
|
|
||||||
import type { LocaleEntry } from '../docs/types'
|
import type { LocaleEntry } from '../docs/types'
|
||||||
import type { ElkTranslationStatus } from '~/types/translation-status'
|
import type { ElkTranslationStatus } from '~/types/translation-status'
|
||||||
|
|
||||||
|
const vsCodeConfig = JSON.parse(
|
||||||
|
await fs.readFile(
|
||||||
|
new URL('../.vscode/settings.json', import.meta.url),
|
||||||
|
'utf-8',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
export const localeData: [code: string, file: string[], title: string][]
|
export const localeData: [code: string, file: string[], title: string][]
|
||||||
= currentLocales.map((l: any) => [l.code, l.files ? l.files : [l.file!], l.name ?? l.code])
|
= currentLocales.map((l: any) => [l.code, l.files ? l.files : [l.file!], l.name ?? l.code])
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue