diff --git a/.dockerignore b/.dockerignore index f4b1198..357063f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,2 +1,15 @@ +__mocks__ .git .github +.vscode +.circleci +.env* +.eslint* +.log +Makefile +Procfile +app.json +crowdin.yml +build +docker-compose.yml +node_modules \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 15ca149..740eee4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,8 +7,9 @@ on: - dev paths: - .github/workflows/* - - shared/** - - src/** + - outline/** + - tools/translation.json + - tools/return_ru.patch - Dockerfile workflow_dispatch: @@ -75,7 +76,6 @@ jobs: outputs: type=image,"name=${{ github.repository }},ghcr.io/${{ github.repository }}",push-by-digest=true,name-canonical=true,push=true - name: Export Digests - if: ${{ github.ref == 'refs/heads/master' }} run: | mkdir -p ${{ runner.temp }}/digests digest="${{ steps.build.outputs.digest }}" @@ -83,7 +83,6 @@ jobs: - name: Upload Digests uses: actions/upload-artifact@v4 - if: ${{ github.ref == 'refs/heads/master' }} with: name: digests-linux-${{ matrix.arch }} path: ${{ runner.temp }}/digests/* @@ -94,7 +93,6 @@ jobs: name: Publish runs-on: ubuntu-24.04 needs: build - if: ${{ github.ref == 'refs/heads/master' }} permissions: contents: read packages: write @@ -131,8 +129,9 @@ jobs: ${{ github.repository }} ghcr.io/${{ github.repository }} tags: | - type=raw,value=latest - type=raw,value=${{ env.version }} + type=raw,value=nightly,enable=${{ github.ref == 'refs/heads/dev' }} + type=raw,value=latest,enable=${{ github.ref == 'refs/heads/master' }} + type=raw,value=${{ env.version }},enable=${{ github.ref == 'refs/heads/master' }} - name: Create Manifest & Push working-directory: ${{ runner.temp }}/digests @@ -144,6 +143,7 @@ jobs: - name: Create Release uses: softprops/action-gh-release@v2 + if: ${{ github.ref == 'refs/heads/master' }} with: name: ${{ env.version }} tag_name: ${{ env.version }} diff --git a/.gitignore b/.gitignore index a62a7ae..df40073 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -scripts/translation.json +tools/*.json +!tools/translation.json diff --git a/.gitmodules b/.gitmodules index 6ed23e2..5d17c00 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "src"] - path = src +[submodule "outline"] + path = outline url = https://github.com/outline/outline.git diff --git a/Dockerfile b/Dockerfile index 2c489ef..fe943fe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,12 +5,17 @@ ARG APP_PATH WORKDIR $APP_PATH FROM base AS build -COPY ./src/package.json ./src/yarn.lock ./ -COPY ./src/patches ./patches +RUN apt-get update && \ + apt-get install -y patch && \ + rm -rf /var/lib/apt/lists/* +COPY ./outline/package.json ./outline/yarn.lock ./ +COPY ./outline/patches ./patches RUN yarn install --no-optional --frozen-lockfile --network-timeout 1000000 && \ yarn cache clean -COPY src . -COPY shared ./shared +COPY ./outline . +COPY ./tools/translation.json ./shared/i18n/locales/ru_RU/translation.json +COPY ./tools/return_ru.patch . +RUN patch -p1 < return_ru.patch ARG CDN_URL RUN yarn build RUN rm -rf node_modules @@ -18,7 +23,6 @@ RUN yarn install --production=true --frozen-lockfile --network-timeout 1000000 & yarn cache clean ENV PORT=3000 - FROM base AS release ENV NODE_ENV=production COPY --from=build $APP_PATH/build ./build @@ -41,6 +45,6 @@ RUN mkdir -p "$FILE_STORAGE_LOCAL_ROOT_DIR" && \ chmod 1777 "$FILE_STORAGE_LOCAL_ROOT_DIR" VOLUME /var/lib/outline/data USER nodejs -HEALTHCHECK CMD wget -qO- http://localhost:${PORT}/_health | grep -q "OK" || exit 1 +HEALTHCHECK --interval=1m CMD wget -qO- "http://localhost:${PORT:-3000}/_health" | grep -q "OK" || exit 1 EXPOSE 3000 CMD ["yarn", "start"] diff --git a/LICENSE b/LICENSE index b8cf886..33551fd 100644 --- a/LICENSE +++ b/LICENSE @@ -1,103 +1,25 @@ -Business Source License 1.1 +MIT License -Parameters +Copyright (c) 2025 flameshikari -Licensor: General Outline, Inc. -Licensed Work: Outline 0.82.0-0 - The Licensed Work is (c) 2025 General Outline, Inc. -Additional Use Grant: You may make use of the Licensed Work, provided that - you may not use the Licensed Work for a Document - Service. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: - A “Document Service” is a commercial offering that - allows third parties (other than your employees and - contractors) to access the functionality of the - Licensed Work by creating teams and documents - controlled by such third parties. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -Change Date: 2029-01-31 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. -Change License: Apache License, Version 2.0 +--- -For information about alternative licensing arrangements for the Software, -please visit: https://www.getoutline.com - -Notice - -The Business Source License (this document, or the “License”) is not an Open -Source license. However, the Licensed Work will eventually be made available -under an Open Source License, as stated in this License. - -License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved. -“Business Source License” is a trademark of MariaDB Corporation Ab. - ------------------------------------------------------------------------------ - -Business Source License 1.1 - -Terms - -The Licensor hereby grants you the right to copy, modify, create derivative -works, redistribute, and make non-production use of the Licensed Work. The -Licensor may make an Additional Use Grant, above, permitting limited -production use. - -Effective on the Change Date, or the fourth anniversary of the first publicly -available distribution of a specific version of the Licensed Work under this -License, whichever comes first, the Licensor hereby grants you rights under -the terms of the Change License, and the rights granted in the paragraph -above terminate. - -If your use of the Licensed Work does not comply with the requirements -currently in effect as described in this License, you must purchase a -commercial license from the Licensor, its affiliated entities, or authorized -resellers, or you must refrain from using the Licensed Work. - -All copies of the original and modified Licensed Work, and derivative works -of the Licensed Work, are subject to this License. This License applies -separately for each version of the Licensed Work and the Change Date may vary -for each version of the Licensed Work released by Licensor. - -You must conspicuously display this License on each original or modified copy -of the Licensed Work. If you receive the Licensed Work in original or -modified form from a third party, the terms and conditions set forth in this -License apply to your use of that work. - -Any use of the Licensed Work in violation of this License will automatically -terminate your rights under this License for the current and all other -versions of the Licensed Work. - -This License does not grant you any right in any trademark or logo of -Licensor or its affiliates (provided that you may use a trademark or logo of -Licensor as expressly required by this License). - -TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON -AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, -EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND -TITLE. - -MariaDB hereby grants you permission to use this License’s text to license -your works, and to refer to it using the trademark “Business Source License”, -as long as you comply with the Covenants of Licensor below. - -Covenants of Licensor - -In consideration of the right to use this License’s text and the “Business -Source License” name and trademark, Licensor covenants to MariaDB, and to all -other recipients of the licensed work to be provided by Licensor: - -1. To specify as the Change License the GPL Version 2.0 or any later version, - or a license that is compatible with GPL Version 2.0 or a later version, - where “compatible” means that software provided under the Change License can - be included in a program with software provided under GPL Version 2.0 or a - later version. Licensor may specify additional Change Licenses without - limitation. - -2. To either: (a) specify an additional grant of rights to use that does not - impose any additional restriction on the right granted in this License, as - the Additional Use Grant; or (b) insert the text “None”. - -3. To specify a Change Date. - -4. Not to modify this License in any other way. +This repository includes Outline Wiki as a submodule, which is licensed under the Business Source License 1.1 (BSL). See the outline directory for its specific license terms. \ No newline at end of file diff --git a/README.md b/README.md index aba7b2b..b3fcaf9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -![](.github/assets/opengraph.png) - # 📚 [Outline](https://github.com/outline/outline) с русским переводом [![Build Status](https://img.shields.io/github/actions/workflow/status/flameshikari/outline-ru/build.yml)](https://github.com/flameshikari/outline-ru/actions) [![Version](https://img.shields.io/github/v/release/flameshikari/outline-ru?style=)](https://github.com/flameshikari/outline-ru/releases/latest) ## ❓ Зачем @@ -8,30 +6,27 @@ ## 📝 Примечания -За основу взят перевод из [данного коммита](https://github.com/outline/outline/commit/228d1faa9fd3cbb82409d98e1443fed65adc5715), который впоследствии улучшается и переводится здесь. - -Буду рад помощи в улучшении перевода или сборки; сообщить о некорректном переводе можно [здесь](https://github.com/flameshikari/outline-ru/discussions/8). - -Из доступных архитектур контейнера имеются только `amd64` и `arm64`. - -## ⚠️ Дисклеймер - -Данное программное обеспечение предоставляется «как есть», без каких-либо гарантий, явно выраженных или подразумеваемых, включая гарантии товарной пригодности, соответствия по его конкретному назначению и отсутствия нарушений, но не ограничиваясь ими. Ни в каком случае авторы или правообладатели не несут ответственности по каким-либо искам, за ущерб или по иным требованиям, в том числе, при действии контракта, деликте или иной ситуации, возникшим из-за использования программного обеспечения или иных действий с программным обеспечением. +- доступные архитектуры: `amd64` и `arm64` +- образ доступен в [Docker Hub](https://hub.docker.com/r/flameshikari/outline-ru/tags) и [GHCR](https://github.com/flameshikari/outline-ru/pkgs/container/outline-ru) +- за основу взят перевод из [этого коммита](https://github.com/outline/outline/commit/228d1faa9fd3cbb82409d98e1443fed65adc5715) +- сообщить о некорректном переводе можно [тут](https://github.com/flameshikari/outline-ru/discussions/8) ## 🐳 Установка -> Перед установкой **ОБЯЗАТЕЛЬНО** прочтите [про бэкапы перед обновлением](https://docs.getoutline.com/s/hosting/doc/backups-KZtPOADCHG). +> перед установкой **ОБЯЗАТЕЛЬНО** прочтите [про бэкапы перед обновлением](https://docs.getoutline.com/s/hosting/doc/backups-KZtPOADCHG) -Всё делается по [официальной документации](https://docs.getoutline.com/s/hosting/doc/docker-7pfeLP5a8t), только в качестве `image` укажите `flameshikari/outline-ru:latest` или `ghcr.io/flameshikari/outline-ru:latest` (вместо `latest` желательно указать версию; доступные смотреть [здесь](https://github.com/flameshikari/outline-ru/tags)), а также задайте переменную `DEFAULT_LANGUAGE=ru_RU` в [docker.env](https://github.com/outline/outline/blob/main/.env.sample) или в `environments` (в зависимости от вашей конфигурации). +> если вы используете переменную `DEFAULT_LANGUAGE`, то можете задать ей значение `ru_RU` + +Следуйте [официальной инструкции](https://docs.getoutline.com/s/hosting/doc/docker-7pfeLP5a8t), только в качестве `image` укажите `flameshikari/outline-ru:latest` (желательно зафиксировать версию, заменив `latest` на один из [доступных тегов](https://github.com/flameshikari/outline-ru/tags)). Например: ```yaml - ... - +services: outline: - image: flameshikari/outline-ru:latest + image: flameshikari/outline-ru:0.82.0 + # image: ghcr.io/flameshikari/outline-ru:0.82.0 env_file: ./docker.env - ports: - - "3000:3000" + expose: + - 3000 volumes: - storage-data:/var/lib/outline/data depends_on: diff --git a/src b/outline similarity index 100% rename from src rename to outline diff --git a/scripts/insert_ru_entries.sh b/scripts/insert_ru_entries.sh deleted file mode 100755 index 62deea8..0000000 --- a/scripts/insert_ru_entries.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash - -CWD="$(cd "$(dirname "${BASH_SOURCE[0]}")"/.. && pwd)" - -cat $CWD/src/shared/utils/date.ts | \ - sed '/^ pl,/a\ \ ru,' | \ - sed '/^ pl_PL: pl,/a\ \ ru_RU: ru,' \ - > $CWD/shared/utils/date.ts - -cat $CWD/src/shared/i18n/index.ts | \ - sed '/^export const languageOptions: LanguageOption\[\] = \[/a\ \ {\n label: "Русский (Russian)",\n value: "ru_RU",\n },' \ - > $CWD/shared/i18n/index.ts diff --git a/shared/i18n/index.ts b/shared/i18n/index.ts deleted file mode 100644 index 814b984..0000000 --- a/shared/i18n/index.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { locales } from "../utils/date"; - -type LanguageOption = { - label: string; - value: keyof typeof locales; -}; - -// Note: Updating the available languages? Make sure to also update the -// locales array in shared/utils/date.ts to enable translation for timestamps. -export const languageOptions: LanguageOption[] = [ - { - label: "Русский (Russian)", - value: "ru_RU", - }, - { - label: "English (US)", - value: "en_US", - }, - { - label: "Čeština (Czech)", - value: "cs_CZ", - }, - { - label: "简体中文 (Chinese, Simplified)", - value: "zh_CN", - }, - { - label: "繁體中文 (Chinese, Traditional)", - value: "zh_TW", - }, - { - label: "Deutsch (German)", - value: "de_DE", - }, - { - label: "Español (Spanish)", - value: "es_ES", - }, - { - label: "Français (French)", - value: "fr_FR", - }, - { - label: "Italiano (Italian)", - value: "it_IT", - }, - { - label: "日本語 (Japanese)", - value: "ja_JP", - }, - { - label: "한국어 (Korean)", - value: "ko_KR", - }, - { - label: "Nederland (Dutch, Netherlands)", - value: "nl_NL", - }, - { - label: "Norsk Bokmål (Norwegian)", - value: "nb_NO", - }, - { - label: "Português (Portuguese, Brazil)", - value: "pt_BR", - }, - { - label: "Português (Portuguese, Portugal)", - value: "pt_PT", - }, - { - label: "Polskie (Polish)", - value: "pl_PL", - }, - { - label: "فارسی (Persian)", - value: "fa_IR", - }, - { - label: "Svenska (Swedish)", - value: "sv_SE", - }, - { - label: "Türkçe (Turkish)", - value: "tr_TR", - }, - { - label: "Українська (Ukrainian)", - value: "uk_UA", - }, - { - label: "Tiếng Việt (Vietnamese)", - value: "vi_VN", - }, -]; - -export const languages = languageOptions.map((i) => i.value); diff --git a/shared/utils/date.ts b/shared/utils/date.ts deleted file mode 100644 index a45d418..0000000 --- a/shared/utils/date.ts +++ /dev/null @@ -1,198 +0,0 @@ -/* eslint-disable import/no-duplicates */ -import { - Locale, - addSeconds, - formatDistanceToNow, - subDays, - subMonths, - subWeeks, - subYears, -} from "date-fns"; -import { - cs, - de, - enUS, - es, - faIR, - fr, - it, - ja, - ko, - nb, - nl, - ptBR, - pt, - pl, - ru, - sv, - tr, - vi, - uk, - zhCN, - zhTW, -} from "date-fns/locale"; -import type { DateFilter } from "../types"; - -export function subtractDate(date: Date, period: DateFilter) { - switch (period) { - case "day": - return subDays(date, 1); - - case "week": - return subWeeks(date, 1); - - case "month": - return subMonths(date, 1); - - case "year": - return subYears(date, 1); - - default: - return date; - } -} - -/** - * Returns a humanized relative time string for the given date. - * - * @param date The date to convert - * @param options The options to pass to date-fns - * @returns The relative time string - */ -export function dateToRelative( - date: Date | number, - options?: { - includeSeconds?: boolean; - addSuffix?: boolean; - locale?: Locale | undefined; - shorten?: boolean; - } -) { - const now = new Date(); - const parsedDateTime = new Date(date); - - // Protect against "in less than a minute" when users computer clock is off. - const normalizedDateTime = - parsedDateTime > now && parsedDateTime < addSeconds(now, 60) - ? now - : parsedDateTime; - - const output = formatDistanceToNow(normalizedDateTime, options); - - // Some tweaks to make english language shorter. - if (options?.shorten) { - return output - .replace("about", "") - .replace("less than a minute ago", "just now") - .replace("minute", "min"); - } - - return output; -} - -/** - * Converts a locale string from Unicode CLDR format to BCP47 format. - * - * @param locale The locale string to convert - * @returns The converted locale string - */ -export function unicodeCLDRtoBCP47(locale: string) { - return locale.replace("_", "-").replace("root", "und"); -} - -/** - * Converts a locale string from BCP47 format to Unicode CLDR format. - * - * @param locale The locale string to convert - * @returns The converted locale string - */ -export function unicodeBCP47toCLDR(locale: string) { - return locale.replace("-", "_").replace("und", "root"); -} - -/** - * Converts a locale string from Unicode CLDR format to ISO 639 format. - * - * @param locale The locale string to convert - * @returns The converted locale string - */ -export function unicodeCLDRtoISO639(locale: string) { - return locale.split("_")[0]; -} - -/** - * Returns the current date as a string formatted depending on current locale. - * - * @returns The current date - */ -export function getCurrentDateAsString(locale?: Intl.LocalesArgument) { - return new Date().toLocaleDateString(locale, { - year: "numeric", - month: "long", - day: "numeric", - }); -} - -/** - * Returns the current time as a string formatted depending on current locale. - * - * @returns The current time - */ -export function getCurrentTimeAsString(locale?: Intl.LocalesArgument) { - return new Date().toLocaleTimeString(locale, { - hour: "numeric", - minute: "numeric", - }); -} - -/** - * Returns the current date and time as a string formatted depending on current - * locale. - * - * @returns The current date and time - */ -export function getCurrentDateTimeAsString(locale?: Intl.LocalesArgument) { - return new Date().toLocaleString(locale, { - year: "numeric", - month: "long", - day: "numeric", - hour: "numeric", - minute: "numeric", - }); -} - -const locales = { - cs_CZ: cs, - de_DE: de, - en_US: enUS, - es_ES: es, - fa_IR: faIR, - fr_FR: fr, - it_IT: it, - ja_JP: ja, - ko_KR: ko, - nb_NO: nb, - nl_NL: nl, - pt_BR: ptBR, - pt_PT: pt, - pl_PL: pl, - ru_RU: ru, - sv_SE: sv, - tr_TR: tr, - uk_UA: uk, - vi_VN: vi, - zh_CN: zhCN, - zh_TW: zhTW, -}; - -/** - * Returns the date-fns locale object for the given user language preference. - * - * @param language The user language - * @returns The date-fns locale. - */ -export function dateLocale(language: keyof typeof locales | undefined | null) { - return language ? locales[language] : undefined; -} - -export { locales }; diff --git a/scripts/check_missing_lines.py b/tools/merge_jsons.py similarity index 78% rename from scripts/check_missing_lines.py rename to tools/merge_jsons.py index 8599562..d673a9f 100755 --- a/scripts/check_missing_lines.py +++ b/tools/merge_jsons.py @@ -8,9 +8,12 @@ def workdir(path): abspath = os.path.abspath(basepath + '/' + path) return abspath -en_json_path = workdir('../src/shared/i18n/locales/en_US/translation.json') -ru_json_path = workdir('../shared/i18n/locales/ru_RU/translation.json') -out_json_path = workdir('./translation.json') + +en_json_path = workdir('../outline/shared/i18n/locales/en_US/translation.json') +ru_json_path = workdir('./translation.json') + +out_json_name = 'translation_merged.json' +out_json_path = workdir(f'./{out_json_name}') translated_lines = {} untranslated_lines = {} @@ -49,4 +52,4 @@ if (untranslated_lines): with open(out_json_path, 'w') as target: obj = json.dumps(out_json, indent=2, ensure_ascii=False) target.write(obj + '\n') - print('Переведенные и непереведенные строки смержены в translation.json') + print(f'Переведенные и непереведенные строки смержены в {out_json_name}') diff --git a/tools/return_ru.patch b/tools/return_ru.patch new file mode 100644 index 0000000..13481a6 --- /dev/null +++ b/tools/return_ru.patch @@ -0,0 +1,35 @@ +diff --git a/shared/i18n/index.ts b/shared/i18n/index.ts +index e315ef413..b580ec795 100644 +--- a/shared/i18n/index.ts ++++ b/shared/i18n/index.ts +@@ -72,6 +72,10 @@ export const languageOptions: LanguageOption[] = [ + label: "فارسی (Persian)", + value: "fa_IR", + }, ++ { ++ label: "Русский (Russian)", ++ value: "ru_RU", ++ }, + { + label: "Svenska (Swedish)", + value: "sv_SE", +diff --git a/shared/utils/date.ts b/shared/utils/date.ts +index 397b2c7a4..a45d418ae 100644 +--- a/shared/utils/date.ts ++++ b/shared/utils/date.ts +@@ -23,6 +23,7 @@ import { + ptBR, + pt, + pl, ++ ru, + sv, + tr, + vi, +@@ -175,6 +176,7 @@ const locales = { + pt_BR: ptBR, + pt_PT: pt, + pl_PL: pl, ++ ru_RU: ru, + sv_SE: sv, + tr_TR: tr, + uk_UA: uk, diff --git a/shared/i18n/locales/ru_RU/translation.json b/tools/translation.json similarity index 100% rename from shared/i18n/locales/ru_RU/translation.json rename to tools/translation.json