From 7ef7e101a66fe2de2913307bd2c5f76208079d64 Mon Sep 17 00:00:00 2001 From: Swoyamjeetcodes Date: Fri, 17 Apr 2026 11:04:16 +0530 Subject: [PATCH 01/34] fixes #5815 --- .../HintsEditor/HintsEditor.spec.js | 457 ++++++++---------- 1 file changed, 208 insertions(+), 249 deletions(-) diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/HintsEditor/HintsEditor.spec.js b/contentcuration/contentcuration/frontend/channelEdit/components/HintsEditor/HintsEditor.spec.js index 65b228a26f..b73993deda 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/HintsEditor/HintsEditor.spec.js +++ b/contentcuration/contentcuration/frontend/channelEdit/components/HintsEditor/HintsEditor.spec.js @@ -1,318 +1,277 @@ -import { shallowMount, mount } from '@vue/test-utils'; +import { render, screen, within, configure } from '@testing-library/vue'; +import userEvent from '@testing-library/user-event'; import { AssessmentItemToolbarActions } from '../../constants'; - import HintsEditor from './HintsEditor'; -jest.mock('shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue'); - -const clickNewHintBtn = async wrapper => { - await wrapper.findComponent('[data-test=newHintBtn]').find('button').trigger('click'); -}; +configure({ + testIdAttribute: 'data-test', +}); -const clickHint = async (wrapper, hintIdx) => { - await wrapper.findAll('[data-test=hint]').at(hintIdx).trigger('click'); +const MockTipTapEditor = { + name: 'TipTapEditor', + props: { + value: { + type: String, + default: '', + }, + mode: { + type: String, + default: 'view', + }, + }, + template: ` +
+

{{ value }}

+ +
+ `, }; -const clickMoveHintUp = async (wrapper, hintIdx) => { - await wrapper - .findAllComponents(`[data-test="toolbarIcon-${AssessmentItemToolbarActions.MOVE_ITEM_UP}"]`) - .at(hintIdx) - .trigger('click'); +const renderComponent = props => { + return render(HintsEditor, { + routes: [], + stubs: { + TipTapEditor: MockTipTapEditor, + }, + props: { + hints: [], + ...props, + }, + }); }; -const clickMoveHintDown = async (wrapper, hintIdx) => { - await wrapper - .findAllComponents(`[data-test="toolbarIcon-${AssessmentItemToolbarActions.MOVE_ITEM_DOWN}"]`) - .at(hintIdx) - .trigger('click'); +const getHintCards = () => { + return screen.getAllByTestId('hint'); }; -const clickDeleteHint = async (wrapper, hintIdx) => { - await wrapper - .findAllComponents(`[data-test="toolbarIcon-${AssessmentItemToolbarActions.DELETE_ITEM}"]`) - .at(hintIdx) - .trigger('click'); +const clickToolbarAction = async ({ action, hintIdx, user }) => { + const buttons = screen.getAllByTestId(`toolbarIcon-${action}`); + expect(buttons[hintIdx]).toBeDefined(); + await user.click(buttons[hintIdx]); }; describe('HintsEditor', () => { - let wrapper; - it('smoke test', () => { - const wrapper = shallowMount(HintsEditor); + renderComponent(); - expect(wrapper.exists()).toBe(true); + expect(screen.getByRole('button', { name: 'New hint' })).toBeInTheDocument(); }); - it('renders a placeholder when there are no hints', () => { - wrapper = mount(HintsEditor, { - propsData: { - hints: [], - }, + it('shows an empty-state message when a question has no hints', () => { + renderComponent({ + hints: [], }); - expect(wrapper.html()).toContain('Question has no hints'); + expect(screen.getByText('Question has no hints')).toBeInTheDocument(); }); - it('renders all hints in a correct order', () => { - wrapper = mount(HintsEditor, { - propsData: { - hints: [ - { hint: 'First hint', order: 1 }, - { hint: 'Second hint', order: 2 }, - ], - }, + it('shows hints in the same order as the question', () => { + renderComponent({ + hints: [ + { hint: 'First hint', order: 1 }, + { hint: 'Second hint', order: 2 }, + ], }); - // Find all instances of your new RichTextEditor component - const editors = wrapper.findAllComponents({ name: 'RichTextEditor' }); - expect(editors.length).toBe(2); - - // Instead of checking the raw HTML, we check the `value` prop passed to each editor. - expect(editors.at(0).props('value')).toBe('First hint'); - expect(editors.at(1).props('value')).toBe('Second hint'); + const hintCards = getHintCards(); + expect(within(hintCards[0]).getByText('First hint')).toBeInTheDocument(); + expect(within(hintCards[1]).getByText('Second hint')).toBeInTheDocument(); }); - describe('on hint text update', () => { - beforeEach(() => { - wrapper = mount(HintsEditor, { - propsData: { - hints: [ - { hint: 'First hint', order: 1 }, - { hint: 'Second hint', order: 2 }, - ], - openHintIdx: 1, - }, - }); - - const editors = wrapper.findAllComponents({ name: 'RichTextEditor' }); - editors.at(1).vm.$emit('update', 'Updated hint'); - }); - - it('emits update event with a payload containing updated hints', () => { - expect(wrapper.emitted().update).toBeTruthy(); - expect(wrapper.emitted().update.length).toBe(1); - expect(wrapper.emitted().update[0][0]).toEqual([ + it('lets the user update the text of the currently open hint', async () => { + const user = userEvent.setup(); + const { emitted } = renderComponent({ + hints: [ { hint: 'First hint', order: 1 }, - { hint: 'Updated hint', order: 2 }, - ]); + { hint: 'Second hint', order: 2 }, + ], + openHintIdx: 1, }); - }); - describe('on new hint button click', () => { - beforeEach(async () => { - wrapper = mount(HintsEditor, { - propsData: { - hints: [ - { hint: 'First hint', order: 1 }, - { hint: '', order: 2 }, - { hint: 'Third hint', order: 3 }, - ], - }, - }); - - await clickNewHintBtn(wrapper); - }); + await user.click(screen.getByRole('button', { name: 'Update hint text' })); + + expect(emitted().update).toHaveLength(1); + expect(emitted().update[0][0]).toEqual([ + { hint: 'First hint', order: 1 }, + { hint: 'Updated hint', order: 2 }, + ]); + }); - it('emits update event with a payload containing all non-empty hints and one new empty hint', () => { - expect(wrapper.emitted().update).toBeTruthy(); - expect(wrapper.emitted().update.length).toBe(1); - expect(wrapper.emitted().update[0][0]).toEqual([ + it('adds a new hint and removes existing empty hints when the user clicks New hint', async () => { + const user = userEvent.setup(); + const { emitted } = renderComponent({ + hints: [ { hint: 'First hint', order: 1 }, - { hint: 'Third hint', order: 2 }, - { hint: '', order: 3 }, - ]); + { hint: '', order: 2 }, + { hint: 'Third hint', order: 3 }, + ], }); - it('emits open event with a new hint idx', () => { - expect(wrapper.emitted().open).toBeTruthy(); - expect(wrapper.emitted().open.length).toBe(1); - expect(wrapper.emitted().open[0][0]).toBe(2); - }); + await user.click(screen.getByRole('button', { name: 'New hint' })); + + expect(emitted().update).toHaveLength(1); + expect(emitted().update[0][0]).toEqual([ + { hint: 'First hint', order: 1 }, + { hint: 'Third hint', order: 2 }, + { hint: '', order: 3 }, + ]); + expect(emitted().open).toHaveLength(1); + expect(emitted().open[0][0]).toBe(2); }); - describe('on hint click', () => { - beforeEach(async () => { - wrapper = mount(HintsEditor, { - propsData: { - hints: [ - { hint: 'First hint', order: 1 }, - { hint: 'Second hint', order: 2 }, - ], - }, - }); - - await clickHint(wrapper, 1); + it('opens a different hint when the user clicks that hint card', async () => { + const user = userEvent.setup(); + const { emitted } = renderComponent({ + hints: [ + { hint: 'First hint', order: 1 }, + { hint: 'Second hint', order: 2 }, + ], + openHintIdx: 0, }); - it('emits open event with a correct hint idx', () => { - expect(wrapper.emitted().open).toBeTruthy(); - expect(wrapper.emitted().open.length).toBe(1); - expect(wrapper.emitted().open[0][0]).toBe(1); - }); + const hintCards = getHintCards(); + await user.click(hintCards[1]); + + expect(emitted().open).toHaveLength(1); + expect(emitted().open[0][0]).toBe(1); }); - describe('on move hint up click', () => { - beforeEach(() => { - wrapper = mount(HintsEditor, { - propsData: { - hints: [ - { hint: 'First hint', order: 1 }, - { hint: 'Second hint', order: 2 }, - ], - }, - }); + it('moves a hint up and keeps the same hint open after moving', async () => { + const user = userEvent.setup(); + const { emitted } = renderComponent({ + hints: [ + { hint: 'First hint', order: 1 }, + { hint: 'Second hint', order: 2 }, + ], + openHintIdx: 1, }); - it('emits update event with a payload containing updated and properly ordered hints', async () => { - await clickMoveHintUp(wrapper, 1); - - expect(wrapper.emitted().update).toBeTruthy(); - expect(wrapper.emitted().update.length).toBe(1); - expect(wrapper.emitted().update[0][0]).toEqual([ - { hint: 'Second hint', order: 1 }, - { hint: 'First hint', order: 2 }, - ]); + await clickToolbarAction({ + action: AssessmentItemToolbarActions.MOVE_ITEM_UP, + hintIdx: 1, + user, }); - describe('if moved hint was open', () => { - beforeEach(async () => { - await wrapper.setProps({ - openHintIdx: 1, - }); - - await clickMoveHintUp(wrapper, 1); - }); + expect(emitted().update).toHaveLength(1); + expect(emitted().update[0][0]).toEqual([ + { hint: 'Second hint', order: 1 }, + { hint: 'First hint', order: 2 }, + ]); + expect(emitted().open).toHaveLength(1); + expect(emitted().open[0][0]).toBe(0); + }); - it('emits open event with updated hint index', () => { - expect(wrapper.emitted().open).toBeTruthy(); - expect(wrapper.emitted().open.length).toBe(1); - expect(wrapper.emitted().open[0][0]).toBe(0); - }); + it('keeps track of the open hint when the user moves the hint below it upward', async () => { + const user = userEvent.setup(); + const { emitted } = renderComponent({ + hints: [ + { hint: 'First hint', order: 1 }, + { hint: 'Second hint', order: 2 }, + ], + openHintIdx: 0, }); - describe('if a hint above a moved hint was open', () => { - beforeEach(async () => { - await wrapper.setProps({ - openHintIdx: 0, - }); - - await clickMoveHintUp(wrapper, 1); - }); - - it('emits open event with updated, originally open, hint index', () => { - expect(wrapper.emitted().open).toBeTruthy(); - expect(wrapper.emitted().open.length).toBe(1); - expect(wrapper.emitted().open[0][0]).toBe(1); - }); + await clickToolbarAction({ + action: AssessmentItemToolbarActions.MOVE_ITEM_UP, + hintIdx: 1, + user, }); + + expect(emitted().open).toHaveLength(1); + expect(emitted().open[0][0]).toBe(1); }); - describe('on move hint down click', () => { - beforeEach(() => { - wrapper = mount(HintsEditor, { - propsData: { - hints: [ - { hint: 'First hint', order: 1 }, - { hint: 'Second hint', order: 2 }, - ], - }, - }); + it('moves a hint down and keeps the same hint open after moving', async () => { + const user = userEvent.setup(); + const { emitted } = renderComponent({ + hints: [ + { hint: 'First hint', order: 1 }, + { hint: 'Second hint', order: 2 }, + ], + openHintIdx: 0, }); - it('emits update event with a payload containing updated and properly ordered hints', async () => { - await clickMoveHintDown(wrapper, 0); - - expect(wrapper.emitted().update).toBeTruthy(); - expect(wrapper.emitted().update.length).toBe(1); - expect(wrapper.emitted().update[0][0]).toEqual([ - { hint: 'Second hint', order: 1 }, - { hint: 'First hint', order: 2 }, - ]); + await clickToolbarAction({ + action: AssessmentItemToolbarActions.MOVE_ITEM_DOWN, + hintIdx: 0, + user, }); - describe('if moved hint was open', () => { - beforeEach(async () => { - await wrapper.setProps({ - openHintIdx: 0, - }); - - await clickMoveHintDown(wrapper, 0); - }); + expect(emitted().update).toHaveLength(1); + expect(emitted().update[0][0]).toEqual([ + { hint: 'Second hint', order: 1 }, + { hint: 'First hint', order: 2 }, + ]); + expect(emitted().open).toHaveLength(1); + expect(emitted().open[0][0]).toBe(1); + }); - it('emits open event with updated hint index', () => { - expect(wrapper.emitted().open).toBeTruthy(); - expect(wrapper.emitted().open.length).toBe(1); - expect(wrapper.emitted().open[0][0]).toBe(1); - }); + it('keeps track of the open hint when the user moves the hint above it downward', async () => { + const user = userEvent.setup(); + const { emitted } = renderComponent({ + hints: [ + { hint: 'First hint', order: 1 }, + { hint: 'Second hint', order: 2 }, + ], + openHintIdx: 1, }); - describe('if a hint below a moved hint was open', () => { - beforeEach(async () => { - await wrapper.setProps({ - openHintIdx: 1, - }); - - await clickMoveHintDown(wrapper, 0); - }); - - it('emits open event with updated, originally open, hint index', () => { - expect(wrapper.emitted().open).toBeTruthy(); - expect(wrapper.emitted().open.length).toBe(1); - expect(wrapper.emitted().open[0][0]).toBe(0); - }); + await clickToolbarAction({ + action: AssessmentItemToolbarActions.MOVE_ITEM_DOWN, + hintIdx: 0, + user, }); + + expect(emitted().open).toHaveLength(1); + expect(emitted().open[0][0]).toBe(0); }); - describe('on delete hint click', () => { - beforeEach(() => { - wrapper = mount(HintsEditor, { - propsData: { - hints: [ - { hint: 'First hint', order: 1 }, - { hint: 'Second hint', order: 2 }, - ], - }, - }); + it('deletes a hint and closes the editor when that hint was open', async () => { + const user = userEvent.setup(); + const { emitted } = renderComponent({ + hints: [ + { hint: 'First hint', order: 1 }, + { hint: 'Second hint', order: 2 }, + ], + openHintIdx: 0, }); - it('emits update event with a payload containing updated and properly ordered hints', async () => { - await clickDeleteHint(wrapper, 0); - - expect(wrapper.emitted().update).toBeTruthy(); - expect(wrapper.emitted().update.length).toBe(1); - expect(wrapper.emitted().update[0][0]).toEqual([{ hint: 'Second hint', order: 1 }]); + await clickToolbarAction({ + action: AssessmentItemToolbarActions.DELETE_ITEM, + hintIdx: 0, + user, }); - describe('if deleted hint was open', () => { - beforeEach(async () => { - await wrapper.setProps({ - openHintIdx: 0, - }); - - await clickDeleteHint(wrapper, 0); - }); + expect(emitted().update).toHaveLength(1); + expect(emitted().update[0][0]).toEqual([{ hint: 'Second hint', order: 1 }]); + expect(emitted().close).toHaveLength(1); + }); - it('emits close event', () => { - expect(wrapper.emitted().close).toBeTruthy(); - expect(wrapper.emitted().close.length).toBe(1); - }); + it('keeps track of the open hint when the user deletes a hint above it', async () => { + const user = userEvent.setup(); + const { emitted } = renderComponent({ + hints: [ + { hint: 'First hint', order: 1 }, + { hint: 'Second hint', order: 2 }, + ], + openHintIdx: 1, }); - describe('if a hint below a deleted hint was open', () => { - beforeEach(async () => { - await wrapper.setProps({ - openHintIdx: 1, - }); - - await clickDeleteHint(wrapper, 0); - }); - - it('emits open event with updated, originally open, hint index', () => { - expect(wrapper.emitted().open).toBeTruthy(); - expect(wrapper.emitted().open.length).toBe(1); - expect(wrapper.emitted().open[0][0]).toBe(0); - }); + await clickToolbarAction({ + action: AssessmentItemToolbarActions.DELETE_ITEM, + hintIdx: 0, + user, }); + + expect(emitted().open).toHaveLength(1); + expect(emitted().open[0][0]).toBe(0); }); }); From 90805aa6482f31444c12daa2e530eaf87bdced32 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 00:56:40 +0000 Subject: [PATCH 02/34] chore(deps): bump celery from 5.6.0 to 5.6.3 Bumps [celery](https://github.com/celery/celery) from 5.6.0 to 5.6.3. - [Release notes](https://github.com/celery/celery/releases) - [Changelog](https://github.com/celery/celery/blob/v5.6.3/Changelog.rst) - [Commits](https://github.com/celery/celery/compare/v5.6.0...v5.6.3) --- updated-dependencies: - dependency-name: celery dependency-version: 5.6.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.in | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.in b/requirements.in index a3b073c6ee..2d59962414 100644 --- a/requirements.in +++ b/requirements.in @@ -9,7 +9,7 @@ le-utils==0.2.17 gunicorn==25.1.0 django-postmark==0.1.6 jsonfield==3.1.0 -celery==5.6.0 +celery==5.6.3 redis python-postmark==0.7.0 Django==3.2.24 diff --git a/requirements.txt b/requirements.txt index 2c2ff26b5d..0d66015b7a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,7 +22,7 @@ botocore==1.20.75 # via # boto3 # s3transfer -celery==5.6.0 +celery==5.6.3 # via # -r requirements.in # django-celery-results From 71547d28e5e163e9154210919f735b2aeead483e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 00:57:08 +0000 Subject: [PATCH 03/34] chore(deps): bump docker/setup-qemu-action from 3 to 4 Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 3 to 4. - [Release notes](https://github.com/docker/setup-qemu-action/releases) - [Commits](https://github.com/docker/setup-qemu-action/compare/v3...v4) --- updated-dependencies: - dependency-name: docker/setup-qemu-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/containerbuild.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/containerbuild.yml b/.github/workflows/containerbuild.yml index f6c71009af..0dc9af1f07 100644 --- a/.github/workflows/containerbuild.yml +++ b/.github/workflows/containerbuild.yml @@ -35,7 +35,7 @@ jobs: uses: actions/checkout@v6 - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 @@ -91,7 +91,7 @@ jobs: uses: actions/checkout@v6 - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 From e02e99c091e96f423f17309a931c584460149c16 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 00:57:08 +0000 Subject: [PATCH 04/34] chore(deps-dev): bump @babel/preset-env in the babel group Bumps the babel group with 1 update: [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env). Updates `@babel/preset-env` from 7.29.0 to 7.29.2 - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.29.2/packages/babel-preset-env) --- updated-dependencies: - dependency-name: "@babel/preset-env" dependency-version: 7.29.2 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: babel ... Signed-off-by: dependabot[bot] --- package.json | 2 +- pnpm-lock.yaml | 237 +++++++++++++++++++++++++------------------------ 2 files changed, 120 insertions(+), 119 deletions(-) diff --git a/package.json b/package.json index c36068794a..0b319b29f8 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,7 @@ "@babel/core": "^7.29.0", "@babel/plugin-syntax-import-assertions": "^7.28.6", "@babel/plugin-transform-runtime": "^7.29.0", - "@babel/preset-env": "^7.29.0", + "@babel/preset-env": "^7.29.2", "@crowdin/cli": "^4.14.1", "@testing-library/jest-dom": "^6.6.3", "@testing-library/user-event": "^14.6.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ac38a36cef..d060e402b5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -181,8 +181,8 @@ importers: specifier: ^7.29.0 version: 7.29.0(@babel/core@7.29.0) '@babel/preset-env': - specifier: ^7.29.0 - version: 7.29.0(@babel/core@7.29.0) + specifier: ^7.29.2 + version: 7.29.2(@babel/core@7.29.0) '@crowdin/cli': specifier: ^4.14.1 version: 4.14.1(encoding@0.1.13) @@ -434,6 +434,11 @@ packages: peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + '@babel/helper-define-polyfill-provider@0.6.8': + resolution: {integrity: sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + '@babel/helper-globals@7.28.0': resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} engines: {node: '>=6.9.0'} @@ -957,12 +962,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/preset-env@7.29.0': - resolution: {integrity: sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/preset-env@7.29.2': resolution: {integrity: sha512-DYD23veRYGvBFhcTY1iUvJnDNpuqNd/BzBwCvzOTKUnJjKg5kpUBh3/u9585Agdkgj+QuygG7jLfOPWMa2KVNw==} engines: {node: '>=6.9.0'} @@ -2485,13 +2484,18 @@ packages: peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + babel-plugin-polyfill-corejs2@0.4.17: + resolution: {integrity: sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + babel-plugin-polyfill-corejs3@0.13.0: resolution: {integrity: sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - babel-plugin-polyfill-corejs3@0.14.0: - resolution: {integrity: sha512-AvDcMxJ34W4Wgy4KBIIePQTAOP1Ie2WFwkQp3dB7FQ/f0lI5+nM96zUnYEOE1P9sEg0es5VCP0HxiWu5fUHZAQ==} + babel-plugin-polyfill-corejs3@0.14.2: + resolution: {integrity: sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 @@ -2500,6 +2504,11 @@ packages: peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + babel-plugin-polyfill-regenerator@0.6.8: + resolution: {integrity: sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + babel-plugin-transform-es2015-modules-commonjs@6.26.2: resolution: {integrity: sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==} @@ -2561,6 +2570,11 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + baseline-browser-mapping@2.10.16: + resolution: {integrity: sha512-Lyf3aK28zpsD1yQMiiHD4RvVb6UdMoo8xzG2XzFIfR9luPzOpcBlAsT/qfB1XWS1bxWT+UtE4WmQgsp297FYOA==} + engines: {node: '>=6.0.0'} + hasBin: true + baseline-browser-mapping@2.9.19: resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==} hasBin: true @@ -2633,6 +2647,11 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} @@ -2710,6 +2729,9 @@ packages: caniuse-lite@1.0.30001769: resolution: {integrity: sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==} + caniuse-lite@1.0.30001787: + resolution: {integrity: sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==} + canvas-exif-orientation@0.4.0: resolution: {integrity: sha512-1NjYRG+44oKnY5Ou6NtaRoHchLHYlIzxfzTNBAToTiWOO7BkCW4ays709sYIdD+Wg6DReDAAAcHzfrMgZjyiRg==} @@ -3081,8 +3103,8 @@ packages: resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} engines: {node: '>= 0.6'} - core-js-compat@3.48.0: - resolution: {integrity: sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==} + core-js-compat@3.49.0: + resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==} core-js@2.6.12: resolution: {integrity: sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==} @@ -3508,6 +3530,9 @@ packages: electron-to-chromium@1.5.286: resolution: {integrity: sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==} + electron-to-chromium@1.5.334: + resolution: {integrity: sha512-mgjZAz7Jyx1SRCwEpy9wefDS7GvNPazLthHg8eQMJ76wBdGQQDW33TCrUTvQ4wzpmOrv2zrFoD3oNufMdyMpog==} + emittery@0.13.1: resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} engines: {node: '>=12'} @@ -5168,8 +5193,8 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@11.2.6: - resolution: {integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==} + lru-cache@11.3.3: + resolution: {integrity: sha512-JvNw9Y81y33E+BEYPr0U7omo+U9AySnsMsEiXgwT6yqd31VQWTLNQqmT4ou5eqPFUrTfIDFta2wKhB1hyohtAQ==} engines: {node: 20 || >=22} lru-cache@4.1.5: @@ -5499,6 +5524,9 @@ packages: node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + node-releases@2.0.37: + resolution: {integrity: sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==} + node-sass@9.0.0: resolution: {integrity: sha512-yltEuuLrfH6M7Pq2gAj5B6Zm7m+gdZoG66wTqG6mIZV/zijq3M2OO2HswtT6oBspPyFhHDcaxWpsBm0fRNDHPg==} engines: {node: '>=16'} @@ -6377,8 +6405,8 @@ packages: regjsgen@0.8.0: resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==} - regjsparser@0.13.0: - resolution: {integrity: sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==} + regjsparser@0.13.1: + resolution: {integrity: sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==} hasBin: true request@2.88.2: @@ -7976,7 +8004,7 @@ snapshots: dependencies: '@babel/compat-data': 7.29.0 '@babel/helper-validator-option': 7.27.1 - browserslist: 4.28.1 + browserslist: 4.28.2 lru-cache: 5.1.1 semver: 6.3.1 @@ -8011,6 +8039,17 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-define-polyfill-provider@0.6.8(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + debug: 4.4.3 + lodash.debounce: 4.0.8 + resolve: 1.22.11 + transitivePeerDependencies: + - supports-color + '@babel/helper-globals@7.28.0': {} '@babel/helper-member-expression-to-functions@7.28.5': @@ -8574,82 +8613,6 @@ snapshots: '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.28.6 - '@babel/preset-env@7.29.0(@babel/core@7.29.0)': - dependencies: - '@babel/compat-data': 7.29.0 - '@babel/core': 7.29.0 - '@babel/helper-compilation-targets': 7.28.6 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.29.0) - '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0) - '@babel/plugin-syntax-import-assertions': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.29.0) - '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.29.0) - '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-computed-properties': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) - '@babel/plugin-transform-dotall-regex': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0) - '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-explicit-resource-management': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-exponentiation-operator': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-json-strings': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-modules-systemjs': 7.29.0(@babel/core@7.29.0) - '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0) - '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-numeric-separator': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) - '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-regenerator': 7.29.0(@babel/core@7.29.0) - '@babel/plugin-transform-regexp-modifiers': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-spread': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-unicode-property-regex': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-unicode-sets-regex': 7.28.6(@babel/core@7.29.0) - '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.29.0) - babel-plugin-polyfill-corejs2: 0.4.15(@babel/core@7.29.0) - babel-plugin-polyfill-corejs3: 0.14.0(@babel/core@7.29.0) - babel-plugin-polyfill-regenerator: 0.6.6(@babel/core@7.29.0) - core-js-compat: 3.48.0 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - '@babel/preset-env@7.29.2(@babel/core@7.29.0)': dependencies: '@babel/compat-data': 7.29.0 @@ -8718,10 +8681,10 @@ snapshots: '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.0) '@babel/plugin-transform-unicode-sets-regex': 7.28.6(@babel/core@7.29.0) '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.29.0) - babel-plugin-polyfill-corejs2: 0.4.15(@babel/core@7.29.0) - babel-plugin-polyfill-corejs3: 0.14.0(@babel/core@7.29.0) - babel-plugin-polyfill-regenerator: 0.6.6(@babel/core@7.29.0) - core-js-compat: 3.48.0 + babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.0) + babel-plugin-polyfill-corejs3: 0.14.2(@babel/core@7.29.0) + babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.0) + core-js-compat: 3.49.0 semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -10615,19 +10578,28 @@ snapshots: transitivePeerDependencies: - supports-color + babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.0): + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.29.0): dependencies: '@babel/core': 7.29.0 '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.29.0) - core-js-compat: 3.48.0 + core-js-compat: 3.49.0 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-corejs3@0.14.0(@babel/core@7.29.0): + babel-plugin-polyfill-corejs3@0.14.2(@babel/core@7.29.0): dependencies: '@babel/core': 7.29.0 - '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.29.0) - core-js-compat: 3.48.0 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) + core-js-compat: 3.49.0 transitivePeerDependencies: - supports-color @@ -10638,6 +10610,13 @@ snapshots: transitivePeerDependencies: - supports-color + babel-plugin-polyfill-regenerator@0.6.8(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + babel-plugin-transform-es2015-modules-commonjs@6.26.2: dependencies: babel-plugin-transform-strict-mode: 6.24.1 @@ -10752,6 +10731,8 @@ snapshots: base64-js@1.5.1: {} + baseline-browser-mapping@2.10.16: {} + baseline-browser-mapping@2.9.19: {} batch@0.6.1: {} @@ -10843,6 +10824,14 @@ snapshots: node-releases: 2.0.27 update-browserslist-db: 1.2.3(browserslist@4.28.1) + browserslist@4.28.2: + dependencies: + baseline-browser-mapping: 2.10.16 + caniuse-lite: 1.0.30001787 + electron-to-chromium: 1.5.334 + node-releases: 2.0.37 + update-browserslist-db: 1.2.3(browserslist@4.28.2) + bser@2.1.1: dependencies: node-int64: 0.4.0 @@ -10953,8 +10942,8 @@ snapshots: caniuse-api@3.0.0: dependencies: - browserslist: 4.28.1 - caniuse-lite: 1.0.30001769 + browserslist: 4.28.2 + caniuse-lite: 1.0.30001787 lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 @@ -10962,6 +10951,8 @@ snapshots: caniuse-lite@1.0.30001769: {} + caniuse-lite@1.0.30001787: {} + canvas-exif-orientation@0.4.0: {} canvg@1.5.3: @@ -11156,9 +11147,9 @@ snapshots: cookie@0.7.1: {} - core-js-compat@3.48.0: + core-js-compat@3.49.0: dependencies: - browserslist: 4.28.1 + browserslist: 4.28.2 core-js@2.6.12: {} @@ -11284,7 +11275,7 @@ snapshots: cssnano-preset-default@7.0.7(postcss@8.5.6): dependencies: - browserslist: 4.28.1 + browserslist: 4.28.2 css-declaration-sorter: 7.2.0(postcss@8.5.6) cssnano-utils: 5.0.1(postcss@8.5.6) postcss: 8.5.6 @@ -11576,6 +11567,8 @@ snapshots: electron-to-chromium@1.5.286: {} + electron-to-chromium@1.5.334: {} + emittery@0.13.1: {} emoji-regex@8.0.0: {} @@ -13916,7 +13909,7 @@ snapshots: lru-cache@10.4.3: {} - lru-cache@11.2.6: {} + lru-cache@11.3.3: {} lru-cache@4.1.5: dependencies: @@ -14268,6 +14261,8 @@ snapshots: node-releases@2.0.27: {} + node-releases@2.0.37: {} + node-sass@9.0.0: dependencies: async-foreach: 0.1.3 @@ -14543,7 +14538,7 @@ snapshots: path-scurry@2.0.1: dependencies: - lru-cache: 11.2.6 + lru-cache: 11.3.3 minipass: 7.1.3 path-to-regexp@0.1.12: {} @@ -14607,7 +14602,7 @@ snapshots: postcss-colormin@7.0.3(postcss@8.5.6): dependencies: - browserslist: 4.28.1 + browserslist: 4.28.2 caniuse-api: 3.0.0 colord: 2.9.3 postcss: 8.5.6 @@ -14615,7 +14610,7 @@ snapshots: postcss-convert-values@7.0.5(postcss@8.5.6): dependencies: - browserslist: 4.28.1 + browserslist: 4.28.2 postcss: 8.5.6 postcss-value-parser: 4.2.0 @@ -14679,7 +14674,7 @@ snapshots: postcss-merge-rules@7.0.5(postcss@8.5.6): dependencies: - browserslist: 4.28.1 + browserslist: 4.28.2 caniuse-api: 3.0.0 cssnano-utils: 5.0.1(postcss@8.5.6) postcss: 8.5.6 @@ -14699,7 +14694,7 @@ snapshots: postcss-minify-params@7.0.3(postcss@8.5.6): dependencies: - browserslist: 4.28.1 + browserslist: 4.28.2 cssnano-utils: 5.0.1(postcss@8.5.6) postcss: 8.5.6 postcss-value-parser: 4.2.0 @@ -14762,7 +14757,7 @@ snapshots: postcss-normalize-unicode@7.0.3(postcss@8.5.6): dependencies: - browserslist: 4.28.1 + browserslist: 4.28.2 postcss: 8.5.6 postcss-value-parser: 4.2.0 @@ -14784,7 +14779,7 @@ snapshots: postcss-reduce-initial@7.0.3(postcss@8.5.6): dependencies: - browserslist: 4.28.1 + browserslist: 4.28.2 caniuse-api: 3.0.0 postcss: 8.5.6 @@ -15190,13 +15185,13 @@ snapshots: regenerate: 1.4.2 regenerate-unicode-properties: 10.2.2 regjsgen: 0.8.0 - regjsparser: 0.13.0 + regjsparser: 0.13.1 unicode-match-property-ecmascript: 2.0.0 unicode-match-property-value-ecmascript: 2.2.1 regjsgen@0.8.0: {} - regjsparser@0.13.0: + regjsparser@0.13.1: dependencies: jsesc: 3.1.0 @@ -15830,7 +15825,7 @@ snapshots: stylehacks@7.0.5(postcss@8.5.6): dependencies: - browserslist: 4.28.1 + browserslist: 4.28.2 postcss: 8.5.6 postcss-selector-parser: 7.1.0 @@ -16334,6 +16329,12 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + update-browserslist-db@1.2.3(browserslist@4.28.2): + dependencies: + browserslist: 4.28.2 + escalade: 3.2.0 + picocolors: 1.1.1 + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -16856,7 +16857,7 @@ snapshots: dependencies: '@apideck/better-ajv-errors': 0.3.6(ajv@8.17.1) '@babel/core': 7.29.0 - '@babel/preset-env': 7.29.0(@babel/core@7.29.0) + '@babel/preset-env': 7.29.2(@babel/core@7.29.0) '@babel/runtime': 7.28.6 '@rollup/plugin-babel': 5.3.1(@babel/core@7.29.0)(@types/babel__core@7.20.5)(rollup@2.79.2) '@rollup/plugin-node-resolve': 15.3.1(rollup@2.79.2) From 98951c30909c47020e2a669d24209682c663c759 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 00:57:14 +0000 Subject: [PATCH 05/34] chore(deps): bump axios from 1.13.5 to 1.15.0 Bumps [axios](https://github.com/axios/axios) from 1.13.5 to 1.15.0. - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v1.13.5...v1.15.0) --- updated-dependencies: - dependency-name: axios dependency-version: 1.15.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- package.json | 2 +- pnpm-lock.yaml | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 0b319b29f8..d189c05cff 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "@tiptap/starter-kit": "^3.13.0", "@tiptap/vue-2": "^3.13.0", "ajv": "^8.12.0", - "axios": "^1.13.5", + "axios": "^1.15.0", "broadcast-channel": "^7.1.0", "core-js": "^3.47.0", "crc-32": "^1.2.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d060e402b5..ca48706f83 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -39,8 +39,8 @@ importers: specifier: ^8.12.0 version: 8.17.1 axios: - specifier: ^1.13.5 - version: 1.13.5 + specifier: ^1.15.0 + version: 1.15.0 broadcast-channel: specifier: ^7.1.0 version: 7.1.0 @@ -2417,8 +2417,8 @@ packages: aws4@1.13.2: resolution: {integrity: sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==} - axios@1.13.5: - resolution: {integrity: sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==} + axios@1.15.0: + resolution: {integrity: sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==} babel-code-frame@6.26.0: resolution: {integrity: sha512-XqYMR2dfdGMW+hd0IUZ2PwK+fGeFkOxZJ0wY+JaQAHzt1Zx8LcvpiZD2NiGkEG8qx0CfkAOr5xt76d1e8vG90g==} @@ -6244,8 +6244,9 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} - proxy-from-env@1.1.0: - resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + proxy-from-env@2.1.0: + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} pseudomap@1.0.2: resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} @@ -10477,11 +10478,11 @@ snapshots: aws4@1.13.2: {} - axios@1.13.5: + axios@1.15.0: dependencies: follow-redirects: 1.15.11 form-data: 4.0.5 - proxy-from-env: 1.1.0 + proxy-from-env: 2.1.0 transitivePeerDependencies: - debug @@ -14991,7 +14992,7 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 - proxy-from-env@1.1.0: {} + proxy-from-env@2.1.0: {} pseudomap@1.0.2: {} From a51605fa67aafa5bd3cf1eba0f95a63673b6713f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 16:08:49 +0000 Subject: [PATCH 06/34] chore(deps): bump workbox-precaching from 7.3.0 to 7.4.0 Bumps [workbox-precaching](https://github.com/googlechrome/workbox) from 7.3.0 to 7.4.0. - [Release notes](https://github.com/googlechrome/workbox/releases) - [Commits](https://github.com/googlechrome/workbox/compare/v7.3.0...v7.4.0) --- updated-dependencies: - dependency-name: workbox-precaching dependency-version: 7.4.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package.json | 2 +- pnpm-lock.yaml | 13 ++----------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index d189c05cff..f1b6ea2963 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "vuetify": "^1.5.24", "vuex": "^3.0.1", "workbox-core": "^7.4.0", - "workbox-precaching": "^7.3.0", + "workbox-precaching": "^7.4.0", "workbox-routing": "^7.3.0", "workbox-strategies": "^7.3.0", "workbox-window": "^7.3.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ca48706f83..0c39cb507c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -159,8 +159,8 @@ importers: specifier: ^7.4.0 version: 7.4.0 workbox-precaching: - specifier: ^7.3.0 - version: 7.3.0 + specifier: ^7.4.0 + version: 7.4.0 workbox-routing: specifier: ^7.3.0 version: 7.3.0 @@ -7796,9 +7796,6 @@ packages: workbox-navigation-preload@7.4.0: resolution: {integrity: sha512-etzftSgdQfjMcfPgbfaZCfM2QuR1P+4o8uCA2s4rf3chtKTq/Om7g/qvEOcZkG6v7JZOSOxVYQiOu6PbAZgU6w==} - workbox-precaching@7.3.0: - resolution: {integrity: sha512-ckp/3t0msgXclVAYaNndAGeAoWQUv7Rwc4fdhWL69CCAb2UHo3Cef0KIUctqfQj1p8h6aGyz3w8Cy3Ihq9OmIw==} - workbox-precaching@7.4.0: resolution: {integrity: sha512-VQs37T6jDqf1rTxUJZXRl3yjZMf5JX/vDPhmx2CPgDDKXATzEoqyRqhYnRoxl6Kr0rqaQlp32i9rtG5zTzIlNg==} @@ -16921,12 +16918,6 @@ snapshots: dependencies: workbox-core: 7.4.0 - workbox-precaching@7.3.0: - dependencies: - workbox-core: 7.3.0 - workbox-routing: 7.3.0 - workbox-strategies: 7.3.0 - workbox-precaching@7.4.0: dependencies: workbox-core: 7.4.0 From 1a13d0d9476f887bae273099567bb1a6014db92d Mon Sep 17 00:00:00 2001 From: Marcella Maki Date: Thu, 16 Apr 2026 15:36:01 -0400 Subject: [PATCH 07/34] String updates --- .../views/TreeView/TreeViewBase.vue | 2 +- .../TreeView/__tests__/TreeViewBase.spec.js | 8 ++--- .../AboutCommunityLibraryModal.vue | 6 ++-- .../pages/Storage/SubscriptionCard.vue | 14 ++++---- .../strings/communityChannelsStrings.js | 33 ++++++++++--------- .../strings/searchRecommendationsStrings.js | 2 +- .../frontend/shared/views/InfoModal.vue | 2 +- 7 files changed, 35 insertions(+), 32 deletions(-) diff --git a/contentcuration/contentcuration/frontend/channelEdit/views/TreeView/TreeViewBase.vue b/contentcuration/contentcuration/frontend/channelEdit/views/TreeView/TreeViewBase.vue index e7739fc4f9..d18cf0bd5d 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/views/TreeView/TreeViewBase.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/views/TreeView/TreeViewBase.vue @@ -632,7 +632,7 @@ // Share menu section shareMenuButton: 'Share', - submitToCommunityLibrary: 'Submit to community library', + submitToCommunityLibrary: 'Submit to Community Library', inviteCollaborators: 'Invite collaborators', shareToken: 'Share token', diff --git a/contentcuration/contentcuration/frontend/channelEdit/views/TreeView/__tests__/TreeViewBase.spec.js b/contentcuration/contentcuration/frontend/channelEdit/views/TreeView/__tests__/TreeViewBase.spec.js index 5987cb99d0..16724aac2d 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/views/TreeView/__tests__/TreeViewBase.spec.js +++ b/contentcuration/contentcuration/frontend/channelEdit/views/TreeView/__tests__/TreeViewBase.spec.js @@ -307,7 +307,7 @@ describe('TreeViewBase', () => { const wrapper = initWrapper({ getters }); const menuItems = getShareMenuItems(wrapper); const submitItem = menuItems.wrappers.find(item => - item.text().includes('Submit to community library'), + item.text().includes('Submit to Community Library'), ); expect(submitItem).toBeDefined(); }); @@ -321,7 +321,7 @@ describe('TreeViewBase', () => { const wrapper = initWrapper({ getters }); const menuItems = getShareMenuItems(wrapper); const submitItem = menuItems.wrappers.find(item => - item.text().includes('Submit to community library'), + item.text().includes('Submit to Community Library'), ); expect(submitItem).toBeUndefined(); }); @@ -335,7 +335,7 @@ describe('TreeViewBase', () => { const wrapper = initWrapper({ getters }); const menuItems = getShareMenuItems(wrapper); const submitItem = menuItems.wrappers.find(item => - item.text().includes('Submit to community library'), + item.text().includes('Submit to Community Library'), ); expect(submitItem).toBeUndefined(); }); @@ -398,7 +398,7 @@ describe('TreeViewBase', () => { const menuItems = getShareMenuItems(wrapper); expect(menuItems.length).toBe(3); expect( - menuItems.wrappers.some(item => item.text().includes('Submit to community library')), + menuItems.wrappers.some(item => item.text().includes('Submit to Community Library')), ).toBe(true); expect(menuItems.wrappers.some(item => item.text().includes('Invite collaborators'))).toBe( true, diff --git a/contentcuration/contentcuration/frontend/channelList/views/Channel/CommunityLibraryList/AboutCommunityLibraryModal.vue b/contentcuration/contentcuration/frontend/channelList/views/Channel/CommunityLibraryList/AboutCommunityLibraryModal.vue index b6180ccf57..da794f0060 100644 --- a/contentcuration/contentcuration/frontend/channelList/views/Channel/CommunityLibraryList/AboutCommunityLibraryModal.vue +++ b/contentcuration/contentcuration/frontend/channelList/views/Channel/CommunityLibraryList/AboutCommunityLibraryModal.vue @@ -2,7 +2,7 @@

{{ aboutCommunityLibraryDescription$() }}

@@ -28,6 +28,7 @@ diff --git a/contentcuration/contentcuration/frontend/settings/pages/Storage/SubscriptionCard.vue b/contentcuration/contentcuration/frontend/settings/pages/Storage/SubscriptionCard.vue index 93f408a7ce..6811f8308f 100644 --- a/contentcuration/contentcuration/frontend/settings/pages/Storage/SubscriptionCard.vue +++ b/contentcuration/contentcuration/frontend/settings/pages/Storage/SubscriptionCard.vue @@ -225,22 +225,22 @@ }; }, $trs: { - instantUpgrade: 'Instant Storage Upgrade', + instantUpgrade: 'Upgrade storage now', upgradeDescription: 'Purchase additional storage at $15/GB per year.', - upgradeNow: 'Upgrade Now', + upgradeNow: 'Upgrade now', storageAmount: 'Storage (GB)', storageRange: 'Enter a value between 1 and 50', annualPrice: '${price, number}/year', - subscriptionActive: 'Storage Subscription Active', - subscriptionCanceling: 'Subscription Canceling', + subscriptionActive: 'Storage subscription active', + subscriptionCanceling: 'Subscription cancelled', cancelNotice: 'Your subscription will expire on {date, date, medium}. Storage will be removed after that.', renewalNotice: 'Your subscription will automatically renew on {date, date, medium}.', - storageIncluded: '{size} included with your subscription', - manageSubscription: 'Manage Subscription', + storageIncluded: '{size} included in your subscription', + manageSubscription: 'Manage subscription', upgradeSuccess: 'Storage increased to {size}', dismiss: 'Dismiss', - genericError: 'There was a problem connecting to our payment provider. Please try again.', + genericError: 'There was a problem connecting to the payment provider. Please try again.', }, }; diff --git a/contentcuration/contentcuration/frontend/shared/strings/communityChannelsStrings.js b/contentcuration/contentcuration/frontend/shared/strings/communityChannelsStrings.js index 22139cc76a..d4a76f4df2 100644 --- a/contentcuration/contentcuration/frontend/shared/strings/communityChannelsStrings.js +++ b/contentcuration/contentcuration/frontend/shared/strings/communityChannelsStrings.js @@ -176,7 +176,7 @@ export const communityChannelsStrings = createTranslator('CommunityChannelsStrin }, countriesInfoText: { message: - 'Select one or more countries to tag your channels submission with. For example, if your channel contains materials aligned to a national curriculum or regionally-specific content, selecting the relevant countries will help users find it. Otherwise, leave this blank.', + 'Select one or more countries to label your channel submission with. For example, if your channel contains materials aligned to a national curriculum or regionally-specific content, selecting the relevant countries will help users find it. Otherwise, leave this blank.', context: 'Help text for the country selection field in the "Submit to Community Library" panel', }, notPublishedWarningTitle: { @@ -297,27 +297,26 @@ export const communityChannelsStrings = createTranslator('CommunityChannelsStrin context: 'Button text to hide the channel fit checklist criteria', }, channelFitChecklistLicense: { - message: 'Is your content openly licensed or in the public domain?', + message: 'Are the resources in your channel openly licensed or in the public domain?', context: 'License checklist item asking whether the channel content uses open or public domain licenses', }, channelFitChecklistOfflineUse: { - message: 'Does your channel work without an internet connection?', + message: 'Do the resources in your channel work without an Internet connection?', context: 'Offline use checklist item asking whether the channel works offline', }, channelFitChecklistAttribution: { - message: 'Does each piece of content have an author listed?', + message: 'Does each resource have an author listed?', context: 'Attribution checklist item asking whether content items have authors listed', }, channelFitChecklistQuality: { - message: - 'Has anyone on your team or in your organization reviewed it and found it useful for learners?', + message: 'Has anyone on your team or in your organization reviewed the channel?', context: 'Quality checklist item asking whether the channel has been reviewed and validated by someone on the team', }, channelFitChecklistChannelInfo: { message: - "Is your channel's basic information filled in — title, description, thumbnail, language, subject, and level?", + "Is your channel's basic information filled in — title, description, thumbnail, language, category, and level?", context: 'Channel information checklist item asking whether the channel metadata is complete', }, confirmReplacementText: { @@ -384,7 +383,7 @@ export const communityChannelsStrings = createTranslator('CommunityChannelsStrin context: 'Title shown when special permissions licenses are detected in the channel', }, confirmDistributionRights: { - message: 'Please confirm you have the right to distribute this content via Kolibri.', + message: 'Please confirm you have the right to distribute these resources via Kolibri.', context: 'Message asking user to confirm they have distribution rights for special permissions content', }, @@ -460,7 +459,7 @@ export const communityChannelsStrings = createTranslator('CommunityChannelsStrin 'Notification message shown to the user when their submission to the Community Library is successful', }, flaggedNotification: { - message: 'Changes required for {channelVersion}', + message: 'Changes required for version {channelVersion}', context: 'Notification message shown when a user flags a channel version for review', }, submissionNotification: { @@ -579,7 +578,7 @@ export const communityChannelsStrings = createTranslator('CommunityChannelsStrin context: 'Description for the Community Library List page', }, whatIsCommunityLibrary: { - message: 'What is community library?', + message: 'What is the Community Library?', context: 'Label for the section explaining what the Community Library is on the Community Library List page', }, @@ -612,7 +611,8 @@ export const communityChannelsStrings = createTranslator('CommunityChannelsStrin context: 'Button to clear all filters', }, needKolibriVersionToImport: { - message: 'You will need Kolibri version 0.19.4 or higher to import community channels.', + message: + 'You will need Kolibri version 0.19.4 or higher to import channels from the Community Library.', context: 'Message shown in the Community Library List page informing users that they need a certain Kolibri version to import channels from the Community Library', }, @@ -623,7 +623,7 @@ export const communityChannelsStrings = createTranslator('CommunityChannelsStrin }, communityLibraryCTADescription: { message: - 'Have a channel worth sharing with educators and learners? Submit it for review through the Share menu so it can be discovered in Studio.', + 'Have a channel worth sharing with other educators and learners? Submit it for review through the Share menu so it can be discovered in Studio.', context: 'Description for the call to action section in the Community Library List page encouraging users to contribute to the Community Library', }, @@ -659,8 +659,9 @@ export const communityChannelsStrings = createTranslator('CommunityChannelsStrin message: 'View channel details including description and metadata', context: 'Third item in the list of things users can do in the Community Library', }, - gotItLabel: { - message: 'Got it', - context: 'Button text to dismiss the about Community Library modal', - }, + // gotItLabel: { + // message: 'Got it', + // context: 'Button text to dismiss the about Community Library modal', + // }, + // use CLOSE ACTION INSTEAD OF GOT IT TO BE CONSISTENT WITH OTHER MODALS }); diff --git a/contentcuration/contentcuration/frontend/shared/strings/searchRecommendationsStrings.js b/contentcuration/contentcuration/frontend/shared/strings/searchRecommendationsStrings.js index b459ff423b..2aefaeda26 100644 --- a/contentcuration/contentcuration/frontend/shared/strings/searchRecommendationsStrings.js +++ b/contentcuration/contentcuration/frontend/shared/strings/searchRecommendationsStrings.js @@ -7,7 +7,7 @@ export const searchRecommendationsStrings = createTranslator('SearchRecommendati }, trySearchRecommendationsText: { message: - "Based on the title and descriptions of your folders, we'll suggest content from the Kolibri Library. Choose 'Import from channels' in any channel folder to see your recommendations.", + "Based on the title and description of the folder you are working on, we'll suggest relevant resources from the Kolibri Library. Choose 'Import from channels' in any folder of your channels to see the recommendations.", context: 'Body text for a banner announcing the new recommendations feature', }, viewRecommendationsButton: { diff --git a/contentcuration/contentcuration/frontend/shared/views/InfoModal.vue b/contentcuration/contentcuration/frontend/shared/views/InfoModal.vue index d12a10ab9a..14b39b948d 100644 --- a/contentcuration/contentcuration/frontend/shared/views/InfoModal.vue +++ b/contentcuration/contentcuration/frontend/shared/views/InfoModal.vue @@ -66,7 +66,7 @@ }; }, $trs: { - open: 'Open help dialog', + open: 'More information about licenses', close: 'Close', }, }; From 0b98151ac174ea39ac1317492a8111a4fa630bfd Mon Sep 17 00:00:00 2001 From: Allan Otodi Opeto <103313919+AllanOXDi@users.noreply.github.com> Date: Thu, 16 Apr 2026 19:21:15 +0300 Subject: [PATCH 08/34] refresh user list after deletion --- .../frontend/administration/composables/useTable.js | 1 + .../frontend/administration/pages/Users/UserItem.vue | 1 + .../frontend/administration/pages/Users/UserTable.vue | 4 +++- contentcuration/contentcuration/viewsets/user.py | 2 +- 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/contentcuration/contentcuration/frontend/administration/composables/useTable.js b/contentcuration/contentcuration/frontend/administration/composables/useTable.js index 7039723941..76a1d98d01 100644 --- a/contentcuration/contentcuration/frontend/administration/composables/useTable.js +++ b/contentcuration/contentcuration/frontend/administration/composables/useTable.js @@ -115,5 +115,6 @@ export function useTable({ fetchFunc, filterFetchQueryParams }) { return { pagination, loading, + loadItems, }; } diff --git a/contentcuration/contentcuration/frontend/administration/pages/Users/UserItem.vue b/contentcuration/contentcuration/frontend/administration/pages/Users/UserItem.vue index b447dc01ca..cc40a870da 100644 --- a/contentcuration/contentcuration/frontend/administration/pages/Users/UserItem.vue +++ b/contentcuration/contentcuration/frontend/administration/pages/Users/UserItem.vue @@ -113,6 +113,7 @@ diff --git a/contentcuration/contentcuration/frontend/administration/pages/Users/UserTable.vue b/contentcuration/contentcuration/frontend/administration/pages/Users/UserTable.vue index f7fdf07722..b99234b265 100644 --- a/contentcuration/contentcuration/frontend/administration/pages/Users/UserTable.vue +++ b/contentcuration/contentcuration/frontend/administration/pages/Users/UserTable.vue @@ -123,6 +123,7 @@ @@ -246,7 +247,7 @@ return store.dispatch('userAdmin/loadUsers', fetchParams); } - const { pagination, loading } = useTable({ + const { pagination, loading, loadItems } = useTable({ fetchFunc: fetchParams => loadUsers(fetchParams), filterFetchQueryParams, }); @@ -261,6 +262,7 @@ clearSearch, pagination, loading, + loadItems, filterFetchQueryParams, }; }, diff --git a/contentcuration/contentcuration/viewsets/user.py b/contentcuration/contentcuration/viewsets/user.py index bbd2389ee5..806e5a69c8 100644 --- a/contentcuration/contentcuration/viewsets/user.py +++ b/contentcuration/contentcuration/viewsets/user.py @@ -431,7 +431,7 @@ class AdminUserViewSet( "edit_count", "view_count", ) - queryset = User.objects.all() + queryset = User.objects.filter(deleted=False) def annotate_queryset(self, queryset): edit_channel_query = ( From 64dcb503a9e6ec72e112c95e6b523d0e59d1b47e Mon Sep 17 00:00:00 2001 From: rtibblesbot Date: Fri, 17 Apr 2026 12:18:48 -0700 Subject: [PATCH 09/34] fix: populate draft ChannelVersion metadata during draft publishes (#5841) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: populate draft ChannelVersion metadata during draft publishes fill_published_fields now accepts an optional draft_channel_version parameter. When provided, ChannelVersion-level fields are written to the draft object while channel-level fields (total_resource_count, published_size, published_data, version_info) are left untouched. mark_channel_version_as_distributable is also skipped for draft publishes. publish_channel now calls fill_published_fields in the draft branch, passing the draft ChannelVersion returned by create_draft_channel_version. Closes #5839 * refactor: simplify draft publish metadata implementation after review * fix: use queryset update to bypass ChannelVersion full_clean validation ChannelVersion.save() always calls full_clean(), which validates choices on ArrayField(IntegerField(choices=...)) for included_licenses. Custom license IDs used in existing tests (e.g. IDs 100, 101) are not in the standard choices list from le_utils, so save() raised ValidationError. Replace version_obj.save() with a queryset .update() to write metadata fields directly without triggering model-level validation. The data originates from the DB so validation is unnecessary, and M2M operations (special_permissions_included) continue to use the model instance. Co-Authored-By: Claude Sonnet 4.6 * refactor: address review feedback on draft publish tests and publish_channel structure - Merge redundant 'if not is_draft_version:' block into the 'else:' branch of the is_draft_version conditional in publish_channel, as requested by reviewer. - Refactor FillPublishedFieldsDraftTestCase into DraftPublishChannelTestCase which tests the complete publish_channel flow (with save_export_database mocked) rather than calling fill_published_fields directly. Tests now use a Special Permissions license node with published=True to exercise the special_permissions_included logic. - test_second_draft_publish_replaces_special_permissions_included now makes two real publish_channel calls with different license_description values and verifies the M2M is replaced (not accumulated) between calls. - test_mark_channel_version_as_distributable_not_called replaced by test_special_permissions_distributable_false_for_draft_publish which asserts the distributable field stays False on the resulting AuditedSpecialPermissionsLicense objects after a draft publish of a public channel, rather than mocking the method. * fix: replace channel.included_languages on publish instead of accumulating Use .set() instead of .add() so languages removed from a channel are cleared on subsequent publishes rather than accumulated indefinitely. Flagged by AlexVelezLl, confirmed as a bug by rtibbles. * fix: use get() instead of create() for Special Permissions license in tests loadconstants inserts licenses with explicit PKs, leaving the PK sequence at 1. Calling License.objects.create() in setUp() then collides with the existing row. Use get() to fetch the pre-existing license — the same pattern used in test_create_channel_versions.py and test_sync.py. Co-Authored-By: Claude Sonnet 4.6 * fix: move delete_public_channel_cache_keys into else branch of is_draft_version The previous commit claimed to have merged all 'if not is_draft_version' blocks into the else branch, but missed the delete_public_channel_cache_keys call. Move it inside the else block and simplify the condition to 'if channel.public'. * fix: revert queryset update to version_obj.save() for ChannelVersion metadata Co-Authored-By: Claude Sonnet 4.6 --------- Co-authored-by: Claude Sonnet 4.6 --- .../tests/utils/test_publish.py | 203 ++++++++++++++++++ .../contentcuration/utils/publish.py | 105 ++++----- 2 files changed, 257 insertions(+), 51 deletions(-) diff --git a/contentcuration/contentcuration/tests/utils/test_publish.py b/contentcuration/contentcuration/tests/utils/test_publish.py index 81d1c23453..62005a7b01 100644 --- a/contentcuration/contentcuration/tests/utils/test_publish.py +++ b/contentcuration/contentcuration/tests/utils/test_publish.py @@ -4,7 +4,9 @@ from unittest import mock from django.conf import settings +from le_utils.constants import licenses +import contentcuration.models as ccmodels from contentcuration.tests import testdata from contentcuration.tests.base import StudioTestCase from contentcuration.tests.utils.restricted_filesystemstorage import ( @@ -13,6 +15,7 @@ from contentcuration.utils.publish import create_draft_channel_version from contentcuration.utils.publish import ensure_versioned_database_exists from contentcuration.utils.publish import increment_channel_version +from contentcuration.utils.publish import publish_channel class EnsureVersionedDatabaseTestCase(StudioTestCase): @@ -176,3 +179,203 @@ def test_mixed_draft_and_published_versions(self): self.assertIsNotNone(self.channel.version_info) self.assertEqual(self.channel.version_info.version, 1) self.assertIsNone(self.channel.version_info.secret_token) + + +class DraftPublishChannelTestCase(StudioTestCase): + """ + Tests for the draft publish flow using the full publish_channel function. + + Verifies that draft publishes correctly populate the draft ChannelVersion + metadata while leaving channel-level fields untouched, and that + special_permissions_included is fully replaced (not accumulated) on each + draft publish. + + save_export_database is mocked to avoid file-system operations; all other + publish_channel logic runs as normal. + """ + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls._patch_save_export = mock.patch( + "contentcuration.utils.publish.save_export_database" + ) + cls._patch_save_export.start() + + @classmethod + def tearDownClass(cls): + cls._patch_save_export.stop() + super().tearDownClass() + + def setUp(self): + super().setUp() + self.channel = testdata.channel() + + # Fetch the Special Permissions license loaded by loadconstants. + # Do NOT call create() — loadconstants inserts licenses with explicit PKs + # so the PK sequence is still at 1, and create() would collide with id=1. + self.special_perms_license = ccmodels.License.objects.get( + license_name=licenses.SPECIAL_PERMISSIONS + ) + + # Give the channel an existing published version so version_info is set. + # increment_channel_version calls channel.save(), which triggers + # Channel.on_update() (models.py ~line 1392). on_update() runs + # ChannelVersion.objects.get_or_create(channel=self, version=self.version) + # and assigns the result to self.version_info — so after this call, + # channel.version_info is a real ChannelVersion, not None. + increment_channel_version(self.channel) + self.channel.refresh_from_db() + + # Snapshot channel state before any draft publish. + self.original_total_resource_count = self.channel.total_resource_count + self.original_published_size = self.channel.published_size + self.original_published_data = dict(self.channel.published_data) + self.original_version_info_id = ( + self.channel.version_info.id if self.channel.version_info else None + ) + + def _run_draft_publish(self, version_notes=""): + publish_channel( + self.admin_user.id, + self.channel.id, + version_notes=version_notes, + force=False, + force_exercises=False, + send_email=False, + progress_tracker=None, + is_draft_version=True, + use_staging_tree=False, + ) + + def _get_draft_version(self): + return ccmodels.ChannelVersion.objects.get(channel=self.channel, version=None) + + # ------------------------------------------------------------------ + # Test 1: draft ChannelVersion metadata fields are populated + # ------------------------------------------------------------------ + + def test_draft_channel_version_fields_are_populated(self): + """ + After a draft publish, the draft ChannelVersion has its metadata fields set. + + testdata.channel() has no *published* nodes, so counts are 0 and lists are []. + We assert specific values rather than assertIsNotNone to avoid vacuous passes + (e.g. assertIsNotNone(0) always passes even if the field was never written). + date_published is the only field we can only check for non-None, since its + exact value is non-deterministic. + """ + self._run_draft_publish(version_notes="draft notes") + draft_version = self._get_draft_version() + + self.assertEqual(draft_version.resource_count, 0) + self.assertEqual(draft_version.size, 0) + self.assertEqual(draft_version.kind_count, []) + self.assertIsNotNone(draft_version.date_published) + self.assertEqual(draft_version.version_notes, "draft notes") + self.assertEqual(draft_version.included_languages, []) + self.assertEqual(draft_version.included_licenses, []) + self.assertEqual(draft_version.included_categories, []) + self.assertEqual(draft_version.non_distributable_licenses_included, []) + + # ------------------------------------------------------------------ + # Test 2: channel-level fields are NOT touched + # ------------------------------------------------------------------ + + def test_channel_fields_not_modified_during_draft_publish(self): + """ + A draft publish must not change channel.total_resource_count, + channel.published_size, channel.published_data, or channel.version_info. + """ + self._run_draft_publish() + self.channel.refresh_from_db() + + self.assertEqual( + self.channel.total_resource_count, self.original_total_resource_count + ) + self.assertEqual(self.channel.published_size, self.original_published_size) + self.assertEqual(self.channel.published_data, self.original_published_data) + + current_version_info_id = ( + self.channel.version_info.id if self.channel.version_info else None + ) + self.assertEqual(current_version_info_id, self.original_version_info_id) + + # ------------------------------------------------------------------ + # Test 3: second draft publish replaces special_permissions_included + # ------------------------------------------------------------------ + + def test_second_draft_publish_replaces_special_permissions_included(self): + """ + On a second consecutive draft publish, special_permissions_included on the + draft ChannelVersion reflects only the current publish — licenses from the + previous draft publish that are no longer present are removed. + + Two publish_channel calls are made with a different license_description on + the special-permissions node between them. After the second call the M2M + must contain only the description used in that second call. + """ + # Get a video node from the channel's main tree to use as the content node. + sp_node = ( + self.channel.main_tree.get_descendants().filter(kind_id="video").first() + ) + + # First draft publish: node has Special Permissions with "License A". + sp_node.license = self.special_perms_license + sp_node.license_description = "License A" + sp_node.published = True + sp_node.save() + + self._run_draft_publish() + draft_version = self._get_draft_version() + self.assertEqual(draft_version.special_permissions_included.count(), 1) + self.assertEqual( + draft_version.special_permissions_included.first().description, "License A" + ) + + # Second draft publish: node's description changes to "License B". + sp_node.license_description = "License B" + sp_node.save() + + self._run_draft_publish() + draft_version.refresh_from_db() + self.assertEqual( + draft_version.special_permissions_included.count(), + 1, + "special_permissions_included should be fully replaced on each draft publish", + ) + self.assertEqual( + draft_version.special_permissions_included.first().description, "License B" + ) + + # ------------------------------------------------------------------ + # Test 4: distributable stays False for draft publishes of public channels + # ------------------------------------------------------------------ + + def test_special_permissions_distributable_false_for_draft_publish(self): + """ + Even when channel.public is True, a draft publish must not mark + AuditedSpecialPermissionsLicense records as distributable — the + distributable field must remain False for all licenses linked to the + draft ChannelVersion. + """ + sp_node = ( + self.channel.main_tree.get_descendants().filter(kind_id="video").first() + ) + sp_node.license = self.special_perms_license + sp_node.license_description = "Custom License" + sp_node.published = True + sp_node.save() + + self.channel.public = True + self.channel.save() + + self._run_draft_publish() + draft_version = self._get_draft_version() + + self.assertGreater(draft_version.special_permissions_included.count(), 0) + for license_obj in draft_version.special_permissions_included.all(): + self.assertFalse( + license_obj.distributable, + "distributable must stay False for draft publishes", + ) diff --git a/contentcuration/contentcuration/utils/publish.py b/contentcuration/contentcuration/utils/publish.py index 458004aa6a..ffdb74a2ef 100644 --- a/contentcuration/contentcuration/utils/publish.py +++ b/contentcuration/contentcuration/utils/publish.py @@ -915,23 +915,22 @@ def add_tokens_to_channel(channel): channel.make_token() -def fill_published_fields(channel, version_notes): - channel.last_published = timezone.now() +def fill_published_fields(channel, version_notes, draft_channel_version=None): + is_draft = draft_channel_version is not None + date_now = timezone.now() + published_nodes = ( channel.main_tree.get_descendants() .filter(published=True) .prefetch_related("files") ) - channel.total_resource_count = published_nodes.exclude( - kind_id=content_kinds.TOPIC - ).count() + total_resource_count = published_nodes.exclude(kind_id=content_kinds.TOPIC).count() kind_counts = list( published_nodes.values("kind_id") .annotate(count=Count("kind_id")) .order_by("kind_id") ) - channel.published_kind_count = json.dumps(kind_counts) - channel.published_size = ( + published_size = ( published_nodes.values("files__checksum", "files__file_size") .distinct() .aggregate(resource_size=Sum("files__file_size"))["resource_size"] @@ -946,10 +945,6 @@ def fill_published_fields(channel, version_notes): ) language_list = list(set(chain(node_languages, file_languages))) - for lang in language_list: - if lang: - channel.included_languages.add(lang) - included_licenses = published_nodes.exclude(license=None).values_list( "license", flat=True ) @@ -969,24 +964,6 @@ def fill_published_fields(channel, version_notes): ) ) - # TODO: Eventually, consolidate above operations to just use this field for storing historical data - channel.published_data.update( - { - channel.version: { - "resource_count": channel.total_resource_count, - "kind_count": kind_counts, - "size": channel.published_size, - "date_published": channel.last_published.strftime( - settings.DATE_TIME_FORMAT - ), - "version_notes": version_notes, - "included_languages": language_list, - "included_licenses": license_list, - "included_categories": category_list, - } - } - ) - # Calculate non-distributable licenses (All Rights Reserved) all_rights_reserved_id = ( ccmodels.License.objects.filter(license_name=licenses.ALL_RIGHTS_RESERVED) @@ -1030,36 +1007,60 @@ def fill_published_fields(channel, version_notes): new_licenses, ignore_conflicts=True ) - if channel.version_info: - channel.version_info.resource_count = channel.total_resource_count - channel.version_info.kind_count = kind_counts - channel.version_info.size = int(channel.published_size) - channel.version_info.date_published = channel.last_published - channel.version_info.version_notes = version_notes - channel.version_info.included_languages = language_list - channel.version_info.included_licenses = license_list - channel.version_info.included_categories = category_list - channel.version_info.non_distributable_licenses_included = ( + if not is_draft: + channel.last_published = date_now + channel.total_resource_count = total_resource_count + channel.published_kind_count = json.dumps(kind_counts) + channel.published_size = published_size + + channel.included_languages.set([lang for lang in language_list if lang]) + + # TODO: Eventually, consolidate above operations to just use this field for storing historical data + channel.published_data.update( + { + channel.version: { + "resource_count": total_resource_count, + "kind_count": kind_counts, + "size": published_size, + "date_published": date_now.strftime(settings.DATE_TIME_FORMAT), + "version_notes": version_notes, + "included_languages": language_list, + "included_licenses": license_list, + "included_categories": category_list, + } + } + ) + channel.save() + + version_obj = draft_channel_version if is_draft else channel.version_info + if version_obj is not None: + version_obj.resource_count = total_resource_count + version_obj.kind_count = kind_counts + version_obj.size = int(published_size) + version_obj.date_published = date_now + version_obj.version_notes = version_notes + version_obj.included_languages = language_list + version_obj.included_licenses = license_list + version_obj.included_categories = category_list + version_obj.non_distributable_licenses_included = ( non_distributable_licenses_included ) - channel.version_info.save() + version_obj.save() if special_perms_descriptions: - channel.version_info.special_permissions_included.set( + version_obj.special_permissions_included.set( ccmodels.AuditedSpecialPermissionsLicense.objects.filter( description__in=special_perms_descriptions ) ) else: - channel.version_info.special_permissions_included.clear() + version_obj.special_permissions_included.clear() - if channel.public: + if not is_draft and channel.public: ccmodels.AuditedSpecialPermissionsLicense.mark_channel_version_as_distributable( - channel.version_info.id + version_obj.id ) - channel.save() - def sync_contentnode_and_channel_tsvectors(channel_id): """ @@ -1161,10 +1162,12 @@ def publish_channel( # noqa: C901 ) add_tokens_to_channel(channel) if is_draft_version: - create_draft_channel_version(channel) + draft_channel_version = create_draft_channel_version(channel) + fill_published_fields( + channel, version_notes, draft_channel_version=draft_channel_version + ) else: increment_channel_version(channel) - if not is_draft_version: ccmodels.ChannelVersion.objects.filter( channel=channel, version=None ).delete() @@ -1180,9 +1183,9 @@ def publish_channel( # noqa: C901 base_tree.published = True base_tree.save() - # Delete public channel cache. - if not is_draft_version and channel.public: - delete_public_channel_cache_keys() + # Delete public channel cache. + if channel.public: + delete_public_channel_cache_keys() if send_email: with override(language): From f6c925df09bc625104004db32c693649be00508b Mon Sep 17 00:00:00 2001 From: Blaine Jester Date: Fri, 17 Apr 2026 11:19:52 -0700 Subject: [PATCH 10/34] Approve urns-resolver build used by jest --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index f1b6ea2963..d12d284d00 100644 --- a/package.json +++ b/package.json @@ -196,6 +196,7 @@ "deasync", "es5-ext", "node-sass", + "unrs-resolver", "vue-demi" ] } From 613ca1ab6265711d73bb22b50768abe66380e596 Mon Sep 17 00:00:00 2001 From: rtibblesbot Date: Sat, 18 Apr 2026 15:57:16 -0700 Subject: [PATCH 11/34] fix: snapshot channel fields on draft ChannelVersion in save() Loosen the snapshot condition in ChannelVersion.save() to also fire when version is None (draft rows), so every draft publish captures the current channel name, description, thumbnail encoding, tagline, and language on the draft ChannelVersion. Repeated draft publishes always reflect the latest channel state. Tests cover: first draft publish populates all four snapshot fields, and a second draft publish after mutating channel metadata (including language change from "en" to "fr") refreshes all four fields on the existing row. Co-Authored-By: Claude Sonnet 4.6 --- contentcuration/contentcuration/models.py | 9 ++- .../tests/utils/test_publish.py | 60 +++++++++++++++++++ 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/contentcuration/contentcuration/models.py b/contentcuration/contentcuration/models.py index 22e7c96f94..c852fa28ac 100644 --- a/contentcuration/contentcuration/models.py +++ b/contentcuration/contentcuration/models.py @@ -1671,9 +1671,12 @@ def save(self, *args, **kwargs): if self.version is not None and self.version > self.channel.version: raise ValidationError("Version cannot be greater than channel version") - if self._state.adding and self.version == self.channel.version: - # When creating a new ChannelVersion for current channel version, - # snapshot the current channel info + if self.version is None or ( + self._state.adding and self.version == self.channel.version + ): + # Snapshot channel info when creating a versioned ChannelVersion for the + # current channel version, or on every save for draft (version=None) rows + # so that repeated draft publishes always reflect the latest channel state. self.channel_name = self.channel.name self.channel_description = self.channel.description self.channel_tagline = self.channel.tagline diff --git a/contentcuration/contentcuration/tests/utils/test_publish.py b/contentcuration/contentcuration/tests/utils/test_publish.py index 62005a7b01..ea0db6d75d 100644 --- a/contentcuration/contentcuration/tests/utils/test_publish.py +++ b/contentcuration/contentcuration/tests/utils/test_publish.py @@ -379,3 +379,63 @@ def test_special_permissions_distributable_false_for_draft_publish(self): license_obj.distributable, "distributable must stay False for draft publishes", ) + + def test_draft_publish_populates_channel_snapshot_fields(self): + """ + After the first draft publish, channel_name, channel_description, + channel_thumbnail_encoding, and channel_language on the draft + ChannelVersion reflect the channel's state at publish time. + """ + self.channel.name = "Snapshot Channel" + self.channel.description = "Snapshot description" + self.channel.thumbnail_encoding = {"base64": "abc123"} + self.channel.save() + + self._run_draft_publish() + draft_version = self._get_draft_version() + + self.assertEqual(draft_version.channel_name, "Snapshot Channel") + self.assertEqual(draft_version.channel_description, "Snapshot description") + self.assertEqual(draft_version.channel_thumbnail_encoding, {"base64": "abc123"}) + self.assertEqual(draft_version.channel_language_id, self.channel.language_id) + + def test_second_draft_publish_refreshes_channel_snapshot_fields(self): + """ + A second draft publish after mutating channel metadata must overwrite + the stale snapshot values on the existing draft ChannelVersion row — + for all four snapshot fields. + + channel.language is changed from "en" to "fr" to confirm the refresh + is not vacuous (the language snapshot must change to the new value). + """ + self.channel.name = "Old Name" + self.channel.description = "Old description" + self.channel.thumbnail_encoding = {"base64": "old_thumb"} + # language is already "en" from testdata.channel(); no need to set it + self.channel.save() + + self._run_draft_publish() + draft_version = self._get_draft_version() + + self.assertEqual(draft_version.channel_name, "Old Name") + self.assertEqual( + draft_version.channel_thumbnail_encoding, {"base64": "old_thumb"} + ) + self.assertEqual(draft_version.channel_language_id, "en") + + # Mutate all four fields; language changes from "en" to "fr" + self.channel.name = "New Name" + self.channel.description = "New description" + self.channel.thumbnail_encoding = {"base64": "new_thumb"} + self.channel.language_id = "fr" + self.channel.save() + + self._run_draft_publish() + draft_version.refresh_from_db() + + self.assertEqual(draft_version.channel_name, "New Name") + self.assertEqual(draft_version.channel_description, "New description") + self.assertEqual( + draft_version.channel_thumbnail_encoding, {"base64": "new_thumb"} + ) + self.assertEqual(draft_version.channel_language_id, "fr") From 27107ab668844d486ab33d91e1b1c3fced2e27b1 Mon Sep 17 00:00:00 2001 From: rtibblesbot Date: Sat, 18 Apr 2026 20:39:41 -0700 Subject: [PATCH 12/34] fix: write version=0 in ChannelMetadata for draft publishes Propagate is_draft_version from create_content_database into map_channel_to_kolibri_channel so the exported content DB records version=0 for draft publishes instead of channel.version + 1. Kolibri's version-upgrade logic replaces any DB whose version is lower than the incoming version, so version=0 ensures any real publish (version >= 1) will always supersede a draft. Co-Authored-By: Claude Sonnet 4.6 --- .../tests/utils/test_publish.py | 45 +++++++++++++++++++ .../contentcuration/utils/publish.py | 11 +++-- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/contentcuration/contentcuration/tests/utils/test_publish.py b/contentcuration/contentcuration/tests/utils/test_publish.py index ea0db6d75d..922a90b1a1 100644 --- a/contentcuration/contentcuration/tests/utils/test_publish.py +++ b/contentcuration/contentcuration/tests/utils/test_publish.py @@ -4,6 +4,8 @@ from unittest import mock from django.conf import settings +from kolibri_content import models as kolibrimodels +from kolibri_content.router import using_content_database from le_utils.constants import licenses import contentcuration.models as ccmodels @@ -12,6 +14,7 @@ from contentcuration.tests.utils.restricted_filesystemstorage import ( RestrictedFileSystemStorage, ) +from contentcuration.utils.publish import create_content_database from contentcuration.utils.publish import create_draft_channel_version from contentcuration.utils.publish import ensure_versioned_database_exists from contentcuration.utils.publish import increment_channel_version @@ -439,3 +442,45 @@ def test_second_draft_publish_refreshes_channel_snapshot_fields(self): draft_version.channel_thumbnail_encoding, {"base64": "new_thumb"} ) self.assertEqual(draft_version.channel_language_id, "fr") + + +class MapChannelToKolibriChannelTestCase(StudioTestCase): + def setUp(self): + super().setUp() + self.channel = testdata.channel() + # icon_encoding must not be None — the kolibri content DB thumbnail column is NOT NULL. + # publish_channel calls set_channel_icon_encoding before create_content_database; + # since we call create_content_database directly we set it here instead. + self.channel.icon_encoding = "" + self.channel.save() + # increment_channel_version + refresh_from_db gives channel.version > 0, + # so channel.version + 1 is distinguishable from the draft value of 0. + increment_channel_version(self.channel) + self.channel.refresh_from_db() + + def _get_channel_metadata_version(self, is_draft_version): + with mock.patch("contentcuration.utils.publish.save_export_database"): + tempdb = create_content_database( + self.channel, + force=True, + user_id=self.admin_user.id, + force_exercises=False, + is_draft_version=is_draft_version, + ) + try: + with using_content_database(tempdb): + return kolibrimodels.ChannelMetadata.objects.get( + id=self.channel.id + ).version + finally: + if os.path.exists(tempdb): + os.remove(tempdb) + + def test_draft_publish_sets_version_zero(self): + self.assertEqual(self._get_channel_metadata_version(is_draft_version=True), 0) + + def test_non_draft_publish_sets_version_plus_one(self): + self.assertEqual( + self._get_channel_metadata_version(is_draft_version=False), + self.channel.version + 1, + ) diff --git a/contentcuration/contentcuration/utils/publish.py b/contentcuration/contentcuration/utils/publish.py index ffdb74a2ef..6e18dea57e 100644 --- a/contentcuration/contentcuration/utils/publish.py +++ b/contentcuration/contentcuration/utils/publish.py @@ -183,7 +183,9 @@ def create_content_database( inherit_metadata=bool(channel.ricecooker_version), ) tree_mapper.map_nodes() - kolibri_channel = map_channel_to_kolibri_channel(channel, use_staging_tree) + kolibri_channel = map_channel_to_kolibri_channel( + channel, use_staging_tree, is_draft_version=is_draft_version + ) # It should be at this percent already, but just in case. if progress_tracker: progress_tracker.track(90) @@ -796,7 +798,9 @@ def map_prerequisites(root_node): ) -def map_channel_to_kolibri_channel(channel, use_staging_tree=False): +def map_channel_to_kolibri_channel( + channel, use_staging_tree=False, is_draft_version=False +): logging.debug("Generating the channel metadata.") base_tree = channel.staging_tree if use_staging_tree else channel.main_tree kolibri_channel = kolibrimodels.ChannelMetadata.objects.create( @@ -804,8 +808,7 @@ def map_channel_to_kolibri_channel(channel, use_staging_tree=False): name=channel.name, description=channel.description, tagline=channel.tagline, - version=channel.version - + 1, # Need to save as version being published, not current version + version=0 if is_draft_version else channel.version + 1, thumbnail=channel.icon_encoding, root_pk=base_tree.node_id, root_id=base_tree.node_id, From b32a9bd77d3d6bc9d943cf1bdffa4c5e3a2a7600 Mon Sep 17 00:00:00 2001 From: marcellamaki <17235236+marcellamaki@users.noreply.github.com> Date: Mon, 20 Apr 2026 19:33:33 +0000 Subject: [PATCH 13/34] Update translations from Crowdin This includes: - Updated translation files (.po and .json) - Compiled Django messages (.mo files) - Updated frontend i18n files --- .../LC_MESSAGES/contentcuration-messages.json | 540 +++++++--- .../locale/ar/LC_MESSAGES/django.po | 31 +- .../LC_MESSAGES/contentcuration-messages.json | 486 +++++++-- .../LC_MESSAGES/contentcuration-messages.json | 682 +++++++++---- .../locale/es_ES/LC_MESSAGES/django.po | 37 +- .../LC_MESSAGES/contentcuration-messages.json | 548 +++++++--- .../locale/fr_FR/LC_MESSAGES/django.po | 31 +- .../LC_MESSAGES/contentcuration-messages.json | 948 ++++++++++++++---- .../locale/hi_IN/LC_MESSAGES/django.po | 105 +- .../LC_MESSAGES/contentcuration-messages.json | 528 +++++++--- .../locale/pt_BR/LC_MESSAGES/django.po | 31 +- 11 files changed, 2960 insertions(+), 1007 deletions(-) diff --git a/contentcuration/locale/ar/LC_MESSAGES/contentcuration-messages.json b/contentcuration/locale/ar/LC_MESSAGES/contentcuration-messages.json index d5d976b6ec..6fcd11b703 100644 --- a/contentcuration/locale/ar/LC_MESSAGES/contentcuration-messages.json +++ b/contentcuration/locale/ar/LC_MESSAGES/contentcuration-messages.json @@ -67,8 +67,6 @@ "AdministrationAppError.unauthorizedDetails": "يجب أن تكون مسؤولاً في الاستوديو لتتمكن من عرض هذه الصفحة", "AdministrationIndex.channelsLabel": "القنوات التعليمية", "AdministrationIndex.usersLabel": "المستخدمون", - "Alert.closeButtonLabel": "موافقة", - "Alert.dontShowAgain": "لا تعرض هذه الرسالة مرة أخرى", "AnswersEditor.answersLabel": "الأجوبة", "AnswersEditor.newAnswerBtnLabel": "إجابة جديدة", "AnswersEditor.noAnswersPlaceholder": "لا توجد خيارات لإجابة هذا السؤال", @@ -184,26 +182,31 @@ "CatalogFilterBar.keywords": "\"{text}\"", "CatalogFilterBar.starred": "مميزة بنجمة", "CatalogFilterBar.subtitles": "الترجمات", - "CatalogFilters.coachDescription": "يمكن عرض مصادر المدرِّبين فقط للمدرِّبين في كوليبري", - "CatalogFilters.coachLabel": "مصادر للمدرِّبين", - "CatalogFilters.copyright": "© {year} المساواة في التعلم", - "CatalogFilters.formatLabel": "الصيغ", - "CatalogFilters.frequentlyAskedQuestionsLink": "الأسئلة الأكثر تكراراً", - "CatalogFilters.includesLabel": "عرض القنوات فقط مع", - "CatalogFilters.licenseLabel": "التراخيص", - "CatalogFilters.searchLabel": "الكلمات المفتاح", - "CatalogFilters.searchText": "بحث", - "CatalogFilters.starredLabel": "مميزة بنجمة", - "CatalogFilters.subtitlesLabel": "عناوين الصور أو النص السمعي ", + "CatalogFilterPanelContent.coachDescription": "يمكن عرض مصادر المدرِّبين فقط للمدرِّبين في كوليبري", + "CatalogFilterPanelContent.coachLabel": "مصادر للمدرِّبين", + "CatalogFilterPanelContent.copyright": "© {year} منظمة Learning Equality", + "CatalogFilterPanelContent.formatLabel": "الصيغ", + "CatalogFilterPanelContent.frequentlyAskedQuestionsLink": "الأسئلة الأكثر تكراراً", + "CatalogFilterPanelContent.includesLabel": "عرض القنوات فقط مع", + "CatalogFilterPanelContent.licenseLabel": "التراخيص", + "CatalogFilterPanelContent.searchLabel": "الكلمات المفتاح", + "CatalogFilterPanelContent.starredLabel": "تم التمييز بنجمة", + "CatalogFilterPanelContent.subtitlesLabel": "عناوين الصور أو النص السمعي ", + "CatalogFilters.filterLabel": "فلترة", "CatalogList.cancelButton": "إلغاء", "CatalogList.channelSelectionCount": "{count, plural, zero {قنوات} one {# قناة تم تحديدها} two {# قناتان تم تحديدهما} few {# قنوات تم تحديدها} many {# قنوات تم تحديدها}\n =1 {# قناة تم تحديدها}\n other {# قنوات تم تحديدها}}", + "CatalogList.copyToken": "نسخ معرّف القناة", "CatalogList.downloadButton": "تحميل", "CatalogList.downloadCSV": "تنزيل ملف CSV", "CatalogList.downloadPDF": "تنزيل ملف PDF", "CatalogList.downloadingMessage": "بدأ التنزيل", + "CatalogList.goToWebsite": "التوجّه إلى موقع المصدر الإلكتروني", + "CatalogList.moreOptions": "خيارات أخرى", "CatalogList.resultsText": "{count, plural, zero {نتائج} one {# نتيجة تم العثور عليها} two {# نتيجتان تم العثور عليهما} few {# نتائج تم العثور عليها} many {# نتائج تم العثور عليها}\n =1 {#نتيجة تم العثور عليها}\n other {# نتائج تم العثور عليها}}", "CatalogList.selectAll": "تحديد الكل", "CatalogList.selectChannels": "تنزيل ملخص القنوات المحددة", + "CatalogList.title": "مكتبة المحتوى", + "CatalogList.viewContent": "عرض القناة على كوليبري", "CategoryOptions.noCategoryFoundText": " لم يتم العثور على الفئة", "ChangePasswordForm.cancelAction": "إلغاء", "ChangePasswordForm.changePasswordHeader": "تغيير كلمة المرور", @@ -224,9 +227,6 @@ "ChannelCatalogFrontPage.languagesHeading": "اللغات", "ChannelCatalogFrontPage.numberOfChannels": "{ num } قنوات", "ChannelCatalogFrontPage.subtitlesIncludedText": "عناوين الصور أو النص السمعي ", - "ChannelDeletedError.backToHomeAction": "العودة إلى الصفحة الرئيسية", - "ChannelDeletedError.channelDeletedDetails": "هذه القناة غير موجودة أو ربما تمت إزالتها. يرجى التواصل معنا عبر البريد الإلكتروني content@learningequality.org في حال كنت تظن أن هنالك خطأ.", - "ChannelDeletedError.channelDeletedHeader": "لم يتم إيجاد القناة التعليمية", "ChannelDetailsModal.downloadButton": "تنزيل ملخص القناة", "ChannelDetailsModal.downloadCSV": "تنزيل ملف CSV", "ChannelDetailsModal.downloadPDF": "تنزيل ملف PDF", @@ -263,23 +263,17 @@ "ChannelInvitation.editText": "{sender} قام بدعوتك لتحرير {channel}", "ChannelInvitation.goToChannelSnackbarAction": "التوجّه إلى القناة", "ChannelInvitation.viewText": "{sender} قام بدعوتك لعرض {channel}", - "ChannelItem.cancel": "إلغاء", "ChannelItem.channelDeletedSnackbar": "تم حذف القناة", "ChannelItem.channelLanguageNotSetIndicator": "لم يتم تعيين لغة", "ChannelItem.channelRemovedSnackbar": "تمت إزالة القناة", "ChannelItem.copyToken": "نسخ الرمز التعريفي للقناة", "ChannelItem.deleteChannel": "حذف القناة التعليمية", - "ChannelItem.deletePrompt": "سيتم حذف هذه القناة بشكل دائم. لا يمكن التراجع عن هذه الخطوة.", - "ChannelItem.deleteTitle": "حذف هذه القناة", "ChannelItem.details": "التفاصيل", "ChannelItem.editChannel": "تعديل تفاصيل القناة", "ChannelItem.goToWebsite": "التوجّه إلى موقع المصدر الإلكتروني", "ChannelItem.lastPublished": "منشور {last_published}", "ChannelItem.lastUpdated": "{updated} تم تحديثها", - "ChannelItem.removeBtn": "إزالة", - "ChannelItem.removeChannel": "إزالة من قائمة القنوات", - "ChannelItem.removePrompt": "الوصول المتاح لك يمكنك من عرض هذه القناة فقط. يرجى تأكيد أنك تريد إزالتها من قائمة القنوات الخاصة بك.", - "ChannelItem.removeTitle": "إزالة من قائمة القنوات", + "ChannelItem.removeChannel": "Remove channel", "ChannelItem.resourceCount": "{count, plural, zero {# مصادر} one {# مصادر} two {# مصدران} few {# مصادر} many {# مصدراً}\n =1 {# مصدر}\n other {# مصادر}}", "ChannelItem.unpublishedText": "غير منشور", "ChannelItem.versionText": "نسخة الإصدار {version}", @@ -292,7 +286,6 @@ "ChannelListIndex.catalog": "مكتبة المحتوى", "ChannelListIndex.channelSets": "المجموعات", "ChannelListIndex.frequentlyAskedQuestions": "الأسئلة المتكررة", - "ChannelListIndex.invitations": "لديكَ {count, plural, zero {# دعوات} one {# دعوات} two {# دعوتان} few {# دعوات} many {# دعوة}\n =1 {# دعوة}\n other {# دعوات}}", "ChannelListIndex.libraryTitle": "دليل مكتبة محتوى كوليبري", "ChannelModal.APIText": "القنوات التي يتم إنشاؤها تلقائياً غير قابلة للتحرير.", "ChannelModal.changesSaved": "تم حفظ التغييرات", @@ -316,25 +309,6 @@ "ChannelNotFoundError.channelNotFoundHeader": "لم يتم إيجاد القناة التعليمية", "ChannelSelectionList.noChannelsFound": "لم يتم العثور على أية قنوات", "ChannelSelectionList.searchText": "البحث عن قناة", - "ChannelSetItem.cancel": "إلغاء", - "ChannelSetItem.delete": "حذف المجموعة", - "ChannelSetItem.deleteChannelSetText": "هل أنت متأكد من أنك تريد حذف هذه المجموعة؟", - "ChannelSetItem.deleteChannelSetTitle": "حذف المجموعة", - "ChannelSetItem.edit": "تحرير المجموعة", - "ChannelSetItem.options": "الخيارات", - "ChannelSetItem.saving": "جاري الحفظ", - "ChannelSetList.aboutChannelSets": "حول المجموعات", - "ChannelSetList.aboutChannelSetsLink": "تعرّف على المزيد حول المجموعات", - "ChannelSetList.addChannelSetTitle": "مجموعة جديدة", - "ChannelSetList.cancelButtonLabel": "إغلاق", - "ChannelSetList.channelNumber": "عدد القنوات", - "ChannelSetList.channelSetsDescriptionText": "تحتوي المجموعة على العديد من قنوات استوديو كوليبري التي يمكن استيرادها في دفعة واحدة إلى كوليبري برمز تعريفي واحد.", - "ChannelSetList.channelSetsDisclaimer": "ستحتاج إلى إصدار كوليبري 0.12.0 أو ما بعده لاستيراد مجموعات القنوات", - "ChannelSetList.channelSetsInstructionsText": "يمكنك إنشاء مجموعة عن طريق تحديد القنوات التي تريد استيرادها معاً.", - "ChannelSetList.noChannelSetsFound": "يمكنك تجميع قنوات متعددة لإنشاء مجموعة. ثم يمكن استيراد المجموعة بأكملها إلى كوليبري دفعة واحدة باستخدام رمز المجموعة التعريفي.", - "ChannelSetList.options": "الخيارات", - "ChannelSetList.title": "اسم المجموعة", - "ChannelSetList.token": "معرّف الرمز التعريفي", "ChannelSetModal.bookmark": "تم التمييز بنجمة", "ChannelSetModal.channelAdded": "تمت إضافة القناة", "ChannelSetModal.channelCountText": "{channelCount, plural, zero {# قنوات} one {# قنوات} two {# قناتان} few {# قنوات} many {# قناة} =0 {لا توجد قنوات منشورة في المجموعة الخاصة بك} =1 {# قناة} other {# قنوات}}", @@ -533,6 +507,163 @@ "CommonMetadataStrings.webDesign": "تصميم مواقع الإنترنت", "CommonMetadataStrings.work": "عمل", "CommonMetadataStrings.writing": "الكتابة", + "CommonStrings.backAction": "رجوع", + "CommonStrings.channelDetailsLabel": "Channel Details", + "CommonStrings.clearAction": "إزالة", + "CommonStrings.closeAction": "إغلاق", + "CommonStrings.copyChannelTokenAction": "نسخ معرّف القناة", + "CommonStrings.dismissAction": "رفض", + "CommonStrings.genericErrorMessage": "عذرًا لقد حدث خطأ ما، يُرجى المحاولة مرة أخرى.", + "CommonStrings.previewAction": "معاينة", + "CommonStrings.seeAllAction": "See all", + "CommonStrings.seeLessAction": "See less", + "CommunityChannelsStrings.aboutCommunityLibraryDescription": "Community library contains channels submitted by the community and approved for discovery in Studio.", + "CommunityChannelsStrings.aboutCommunityLibraryTitle": "About Community Library", + "CommunityChannelsStrings.activityHistoryLabel": "Activity history", + "CommunityChannelsStrings.adminLabel": "المشرف", + "CommunityChannelsStrings.allLicensesCompatible": "All licenses are compatible with Community Library.", + "CommunityChannelsStrings.allNotificationsLabel": "All Notifications", + "CommunityChannelsStrings.alreadySubmittedWarningDescription": "Please wait for the channel to be reviewed. You will see a notification in your Studio account after the review is complete.", + "CommunityChannelsStrings.alreadySubmittedWarningTitle": "This version of the channel has already been submitted to the Community Library.", + "CommunityChannelsStrings.approvedNotification": "{author} ({userType}) approved {channelVersion}", + "CommunityChannelsStrings.approvedPrimaryInfo": "A previous version is live in the Community Library. Reviewers will see the latest submission first.", + "CommunityChannelsStrings.approvedStatus": "Approved", + "CommunityChannelsStrings.availableStatus": "Available in Community Library", + "CommunityChannelsStrings.cancelAction": "إلغاء", + "CommunityChannelsStrings.categoriesLabel": "الفئات", + "CommunityChannelsStrings.channelCannotBeDistributed": "This channel cannot be distributed via Kolibri.", + "CommunityChannelsStrings.channelFitAttributionLabel": "Attribution", + "CommunityChannelsStrings.channelFitChannelInfoLabel": "Channel information", + "CommunityChannelsStrings.channelFitChecklistAttribution": "Does each resource have an author listed?", + "CommunityChannelsStrings.channelFitChecklistChannelInfo": "Is your channel's basic information filled in — title, description, thumbnail, language, category, and level?", + "CommunityChannelsStrings.channelFitChecklistIntro": "Criteria for submitting to the Community Library", + "CommunityChannelsStrings.channelFitChecklistLicense": "Are the resources in your channel openly licensed or in the public domain?", + "CommunityChannelsStrings.channelFitChecklistOfflineUse": "Do the resources in your channel work without an Internet connection?", + "CommunityChannelsStrings.channelFitChecklistQuality": "Has anyone on your team or in your organization reviewed the channel?", + "CommunityChannelsStrings.channelFitLicenseLabel": "الترخيص", + "CommunityChannelsStrings.channelFitOfflineUseLabel": "Offline use", + "CommunityChannelsStrings.channelFitQualityLabel": "Quality", + "CommunityChannelsStrings.channelTokenDescription": "You can use this token to import and preview the draft channel in Kolibri. Please note that the token for the final published channel will be different.", + "CommunityChannelsStrings.channelVersion": "{name} v{version}", + "CommunityChannelsStrings.channelVersionTokenLabel": "Channel version token", + "CommunityChannelsStrings.clearAll": "مسح الكل", + "CommunityChannelsStrings.clearAllAction": "مسح الكل", + "CommunityChannelsStrings.communityLibraryCTADescription": "Have a channel worth sharing with other educators and learners? Submit it for review through the Share menu so it can be discovered in Studio.", + "CommunityChannelsStrings.communityLibraryCTATitle": "Help grow the Community Library", + "CommunityChannelsStrings.communityLibraryDescription": "Browse community-submitted channels approved for discovery in Studio. Copy a token to use a channel in Kolibri.", + "CommunityChannelsStrings.communityLibraryLabel": "Community Library", + "CommunityChannelsStrings.communityLibrarySubmissionLabel": "Community Library submission", + "CommunityChannelsStrings.compatibleLicensesDescription": "{licenseNames} - All licenses are compatible with Community Library.", + "CommunityChannelsStrings.confirmDistributionRights": "Please confirm you have the right to distribute these resources via Kolibri.", + "CommunityChannelsStrings.confirmReplacementText": "I understand this will replace my earlier submission on the review queue", + "CommunityChannelsStrings.countriesInfoText": "Select one or more countries to label your channel submission with. For example, if your channel contains materials aligned to a national curriculum or regionally-specific content, selecting the relevant countries will help users find it. Otherwise, leave this blank.", + "CommunityChannelsStrings.countryLabel": "البلدان", + "CommunityChannelsStrings.descriptionLabel": "Describe what's included in this submission", + "CommunityChannelsStrings.dismissAction": "رفض", + "CommunityChannelsStrings.draftBeingPublishedNotice": "Draft version is being published", + "CommunityChannelsStrings.draftPublishedNotice": "Draft published successfully", + "CommunityChannelsStrings.draftTokenLabel": "Draft token", + "CommunityChannelsStrings.editorLabel": "Editor", + "CommunityChannelsStrings.emptyNotificationsNotice": "You have no notifications at this time.", + "CommunityChannelsStrings.emptyNotificationsWithFiltersNotice": "No notifications match the applied filters.", + "CommunityChannelsStrings.errorLoadingVersions": "Unable to load version history", + "CommunityChannelsStrings.errorSnackbar": "There was an error submitting the channel", + "CommunityChannelsStrings.feedbackNotesLabel": "Notes from the reviewer", + "CommunityChannelsStrings.filterByDateLabel": "Filter by date", + "CommunityChannelsStrings.filterByStatusLabel": "Filter by status", + "CommunityChannelsStrings.filterLabel": "فلترة", + "CommunityChannelsStrings.fixLicensingBeforeSubmission": "Please fix licensing before submitting a new version.", + "CommunityChannelsStrings.flaggedNotification": "Changes required for version {channelVersion}", + "CommunityChannelsStrings.flaggedStatus": "Needs changes", + "CommunityChannelsStrings.getDraftTokenAction": "Copy token for draft channel", + "CommunityChannelsStrings.goToMyChannelsAction": "Go to My channels", + "CommunityChannelsStrings.hideCriteriaAction": "Hide criteria", + "CommunityChannelsStrings.hideVersions": "Hide versions", + "CommunityChannelsStrings.incompatibleLicensesDescription": "\"{licenseNames}\" - this channel cannot be distributed via Kolibri. If you cannot change the license, remove all resources with \"{licenseNames}\" before submitting again.", + "CommunityChannelsStrings.incompatibleLicensesDetected": "Incompatible licenses detected.", + "CommunityChannelsStrings.incompleteResourcesDescription1": "لن يتم نشر المصادر غير المكتملة وإتاحتها للتنزيل في \"كوليبري\".", + "CommunityChannelsStrings.incompleteResourcesWarning": "{count, number} {count, plural, one {incomplete resource} other {incomplete resources}}", + "CommunityChannelsStrings.internalNotesLabel": "Admin notes (internal use only)", + "CommunityChannelsStrings.invalidLicensingReason": "Invalid or non-compliant licenses", + "CommunityChannelsStrings.invalidMetadataReason": "Invalid or missing metadata", + "CommunityChannelsStrings.languageLabel": "اللغة", + "CommunityChannelsStrings.languagesLabel": "اللغات", + "CommunityChannelsStrings.lessDetailsButton": "Hide details", + "CommunityChannelsStrings.licenseCheckPassed": "License check passed.", + "CommunityChannelsStrings.licensesLabel": "التراخيص", + "CommunityChannelsStrings.loadError": "There was an error loading channels.", + "CommunityChannelsStrings.loadingVersionHistory": "Loading version history", + "CommunityChannelsStrings.moreDetails": "After your submission is approved, the channel will be available to other Kolibri Studio users on the 'Community Library' page.", + "CommunityChannelsStrings.moreDetailsButton": "More details", + "CommunityChannelsStrings.needKolibriVersionToImport": "You will need Kolibri version 0.19.4 or higher to import channels from the Community Library.", + "CommunityChannelsStrings.needsChangesPrimaryInfo": "Your previously submitted version needs changes. Make sure you have addressed all comments before resubmitting.", + "CommunityChannelsStrings.newLabel": "جديد", + "CommunityChannelsStrings.newNotificationsNotice": "New notifications available.", + "CommunityChannelsStrings.nextPageAction": "التالي", + "CommunityChannelsStrings.noCommunityChannels": "No channels have been published to the Community Library yet.", + "CommunityChannelsStrings.noResultsWithFilters": "No channels match the selected filters.", + "CommunityChannelsStrings.noVersionsAvailable": "No version history available", + "CommunityChannelsStrings.nonePrimaryInfo": "We're inviting members of the Kolibri community to submit channels they've created for offline learning in low-resource settings. ", + "CommunityChannelsStrings.notPublishedWarningDescription": "Publish to Studio first, then submit to the Community Library.", + "CommunityChannelsStrings.notPublishedWarningTitle": "This channel isn't published to Kolibri Studio yet", + "CommunityChannelsStrings.notificationsLabel": "Notifications", + "CommunityChannelsStrings.otherIssuesReason": "Other issues", + "CommunityChannelsStrings.pageIndicator": "{currentPage} من {totalPages}", + "CommunityChannelsStrings.pendingStatus": "Submitted", + "CommunityChannelsStrings.portabilityIssuesReason": "Portability problems", + "CommunityChannelsStrings.previewYourDraftTitle": "Preview your draft channel in Kolibri", + "CommunityChannelsStrings.previousPageAction": "السؤال السابق", + "CommunityChannelsStrings.publicWarningDescription": "It is not possible to submit public channels to the Community Library.", + "CommunityChannelsStrings.publicWarningTitle": "This channel is currently public in the Kolibri Library.", + "CommunityChannelsStrings.publishAction": "نشر", + "CommunityChannelsStrings.publishChannel": "Publish channel", + "CommunityChannelsStrings.publishChannelDescription": "To see your channel in Kolibri, import using the channel token.", + "CommunityChannelsStrings.publishChannelMode": "Publish channel", + "CommunityChannelsStrings.publishDraftDescription": "Your channel will be saved as a draft, allowing you to review and do quality checks on the draft, without altering the current version available for Kolibri users. To see this draft channel in Kolibri, import using the draft channel token.", + "CommunityChannelsStrings.publishDraftMode": "Publish draft channel", + "CommunityChannelsStrings.publishedVersionLabel": "الإصدار المنشور:", + "CommunityChannelsStrings.publishingInfo": "You're publishing: Version {version}", + "CommunityChannelsStrings.publishingMessage": "Channel is being published", + "CommunityChannelsStrings.qualityAssuranceReason": "Quality assurance issues", + "CommunityChannelsStrings.reasonLabel": "Reason: {reason}", + "CommunityChannelsStrings.resubmitAction": "Resubmit", + "CommunityChannelsStrings.resubmitModalBodyFirst": "{channelName} v{version} is also published to the Community Library.", + "CommunityChannelsStrings.resubmitModalBodySecond": "Would you like to resubmit this version with your changes for Community Library review?", + "CommunityChannelsStrings.resubmitModalTitle": "Resubmit channel for Community library review?", + "CommunityChannelsStrings.resultsText": "{count, plural, =1 {# result found} other {# results found}}", + "CommunityChannelsStrings.retry": "إعادة المحاولة", + "CommunityChannelsStrings.reviewAction": "مراجعة", + "CommunityChannelsStrings.saveDraft": "Save draft", + "CommunityChannelsStrings.searchLabel": "بحث", + "CommunityChannelsStrings.searchNotificationsLabel": "Search notifications", + "CommunityChannelsStrings.seeAllVersions": "See all versions", + "CommunityChannelsStrings.showMore": "عرض المزيد", + "CommunityChannelsStrings.showOlderAction": "Show older", + "CommunityChannelsStrings.specialPermissionsDetected": "Special Permissions licenses detected", + "CommunityChannelsStrings.submissionCreationNotification": "Your submission to the Community Library was successful and is now under review.", + "CommunityChannelsStrings.submissionNotesLabel": "Submission notes", + "CommunityChannelsStrings.submissionNotification": "{author} ({userType}) submitted {channelVersion}", + "CommunityChannelsStrings.submitButton": "Submit for review", + "CommunityChannelsStrings.submitToCommunityLibrary": "Submit to Community Library", + "CommunityChannelsStrings.submittedPrimaryInfo": "A previous version is still pending review. Reviewers will see the most recent submission by default.", + "CommunityChannelsStrings.submittedSnackbar": "Channel submitted to Community Library", + "CommunityChannelsStrings.submittingSnackbar": "Submitting channel to Community Library...", + "CommunityChannelsStrings.supersededStatus": "Superseded", + "CommunityChannelsStrings.thisMonthLabel": "هذا الشهر", + "CommunityChannelsStrings.thisWeekLabel": "This week", + "CommunityChannelsStrings.thisYearLabel": "This year", + "CommunityChannelsStrings.todayLabel": "Today", + "CommunityChannelsStrings.unreadNotificationsLabel": "Unread", + "CommunityChannelsStrings.versionDescriptionLabel": "وصف الإصدار", + "CommunityChannelsStrings.versionLabel": "نسخة الإصدار {version}", + "CommunityChannelsStrings.versionNotesLabel": "قدّم وصفاً لما هو جديد في هذا الإصدار من القناة", + "CommunityChannelsStrings.viewCriteriaAction": "View criteria", + "CommunityChannelsStrings.viewMoreAction": "عرض المزيد", + "CommunityChannelsStrings.whatCanYouDoHere": "What you can do here", + "CommunityChannelsStrings.whatCanYouDoHereItem1": "Browse channels by country, category, and language", + "CommunityChannelsStrings.whatCanYouDoHereItem2": "Copy a channel token to use in Kolibri", + "CommunityChannelsStrings.whatCanYouDoHereItem3": "View channel details including description and metadata", + "CommunityChannelsStrings.whatIsCommunityLibrary": "What is the Community Library?", "CommunityStandardsModal.communityStandardsHeader": "معايير المجتمع", "CommunityStandardsModal.coreValuesLink": "تعرف على المزيد حول القيم الأساسية داخل منظمة Learning Equality", "CommunityStandardsModal.description": "Learning Equality هي منظمة غير ربحية تعمل على تمكين الوصول العادل إلى التجارب التعليمية عالية الجودة. وبالإضافة إلى بيان القيم الأساسية لدينا، تهدف معايير المجتمع هذه إلى تعزيز بيئة داعمة وشاملة لمستخدمينا.", @@ -819,40 +950,11 @@ "DeleteAccountForm.emailInvalidText": "البريد الإلكتروني لا يتطابق مع البريد الإلكتروني للحساب الخاص بك", "DeleteAccountForm.fieldRequired": "هذا الحقل مطلوب", "DeleteAccountForm.ok": "موافقة", - "DetailsPanel.AVERAGE": "متوسط", - "DetailsPanel.LARGE": "كبير", - "DetailsPanel.SMALL": "صغير", - "DetailsPanel.VERY_LARGE": "كبير جداً", - "DetailsPanel.VERY_SMALL": "صغير جداً", - "DetailsPanel.aggregatorToolTip": "الموقع الإلكتروني أو المؤسسة التي تستضيف مجموعة المحتوى ولكن ليس بالضرورة أن تكون هي منشئ المحتوى أو مالك حقوق النشر", - "DetailsPanel.aggregatorsLabel": "جامعو المحتوى", - "DetailsPanel.assessmentsIncludedText": "التقييمات", - "DetailsPanel.authorToolTip": "الشخص أو المنظمة التي أنشأت هذا المحتوى", - "DetailsPanel.authorsLabel": "المؤلفون", - "DetailsPanel.categoriesHeading": "الفئات", - "DetailsPanel.coachDescription": "يمكن عرض مصادر المدرِّبين فقط للمدرِّبين في كوليبري", - "DetailsPanel.coachHeading": "مصادر للمدرِّبين", - "DetailsPanel.containsContentHeading": "يتضمن محتوى من", - "DetailsPanel.containsHeading": "يتضمن", - "DetailsPanel.copyrightHoldersLabel": "مالكو حقوق النشر", - "DetailsPanel.creationHeading": "تم الإنشاء في", - "DetailsPanel.currentVersionHeading": "الإصدار المنشور", - "DetailsPanel.languagesHeading": "اللغات", - "DetailsPanel.levelsHeading": "المستويات", - "DetailsPanel.licensesLabel": "التراخيص", - "DetailsPanel.primaryLanguageHeading": "اللغة الأساسية", - "DetailsPanel.providerToolTip": "المنظمة التي كلَّفت بإنشاء المحتوى أو تقوم بنشر المحتوى", - "DetailsPanel.providersLabel": "مقدمو المحتوى", - "DetailsPanel.publishedHeading": "تم النشر في", - "DetailsPanel.resourceHeading": "إجمالي عدد المصادر", - "DetailsPanel.sampleFromChannelHeading": "محتوى كعينة من هذه القناة", - "DetailsPanel.sampleFromTopicHeading": "محتوى كعينة من هذا الموضوع", - "DetailsPanel.sizeHeading": "حجم القناة", - "DetailsPanel.sizeText": "{text} ({size})", - "DetailsPanel.subtitlesHeading": "عناوين الصور والنصوص السمعية", - "DetailsPanel.tagsHeading": "وسوم شائعة", - "DetailsPanel.tokenHeading": "الرمز التعريفي للقناة", - "DetailsPanel.unpublishedText": "لم يتم النشر", + "DeleteChannelModal.cancel": "إلغاء", + "DeleteChannelModal.channelDeletedSnackbar": "تم حذف القناة", + "DeleteChannelModal.deleteChannel": "حذف القناة التعليمية", + "DeleteChannelModal.deletePrompt": "سيتم حذف هذه القناة بشكل دائم. لا يمكن التراجع عن هذه الخطوة.", + "DeleteChannelModal.deleteTitle": "حذف هذه القناة", "DetailsTabView.aggregatorLabel": "جامع المحتوى", "DetailsTabView.aggregatorToolTip": "الموقع الإلكتروني أو المنظمة التي تستضيف مجموعة المحتوى ولكن ليس بالضرورة أن تكون هي منشئ المحتوى أو مالك حقوق النشر", "DetailsTabView.assessmentOptionsLabel": "خيارات التقييم", @@ -1008,10 +1110,8 @@ "ForgotPassword.forgotPasswordPrompt": "يرجى إدخال عنوان البريد الإلكتروني الخاص بك لتلقي تعليمات إعادة تعيين كلمة المرور الخاصة بك", "ForgotPassword.forgotPasswordTitle": "إعادة تعيين كلمة المرور الخاصة بك", "ForgotPassword.submitButton": "تقديم", - "FormulasMenu.btnLabelInsert": "إدراج", - "FormulasMenu.formulasMenuTitle": "أحرف خاصة", "FormulasStrings.addition": "Addition", - "FormulasStrings.advancedCategory": "Advanced", + "FormulasStrings.advancedCategory": "خيارات متقدّمة", "FormulasStrings.alpha": "alpha", "FormulasStrings.and": "And", "FormulasStrings.angle": "Angle", @@ -1019,7 +1119,7 @@ "FormulasStrings.bar": "Bar", "FormulasStrings.basicCategory": "Basic", "FormulasStrings.because": "Because", - "FormulasStrings.beta": "beta", + "FormulasStrings.beta": "الإصدار بيتا", "FormulasStrings.binomialCoefficient": "Binomial coefficient", "FormulasStrings.cardinality": "Cardinality", "FormulasStrings.charactersCategory": "Characters", @@ -1029,7 +1129,7 @@ "FormulasStrings.congruentTo": "Congruent to", "FormulasStrings.conjugate": "Conjugate", "FormulasStrings.conjugateTranspose": "Conjugate transpose", - "FormulasStrings.contains": "Contains", + "FormulasStrings.contains": "يتضمن", "FormulasStrings.contourIntegral": "Contour integral", "FormulasStrings.coproduct": "Coproduct", "FormulasStrings.definition": "Definition", @@ -1061,7 +1161,7 @@ "FormulasStrings.fraction": "Fraction", "FormulasStrings.gamma": "gamma", "FormulasStrings.gammaCapital": "Gamma", - "FormulasStrings.geometryCategory": "Geometry", + "FormulasStrings.geometryCategory": "علم الهندسة", "FormulasStrings.givenThat": "Given that/Such that", "FormulasStrings.greaterThan": "Greater than", "FormulasStrings.greaterThanOrEqual": "Greater than or equal to", @@ -1078,7 +1178,7 @@ "FormulasStrings.lambda": "lambda", "FormulasStrings.lambdaCapital": "Lambda", "FormulasStrings.left": "Left", - "FormulasStrings.leftArrow": "Left arrow", + "FormulasStrings.leftArrow": "سهم أيسر", "FormulasStrings.leftCeiling": "Left ceiling", "FormulasStrings.leftDouble": "Left (double)", "FormulasStrings.leftFloor": "Left floor", @@ -1122,8 +1222,8 @@ "FormulasStrings.perpendicular": "Perpendicular", "FormulasStrings.phi": "phi", "FormulasStrings.phiCapital": "Phi", - "FormulasStrings.pi": "pi", - "FormulasStrings.piCapital": "Pi", + "FormulasStrings.pi": "الثابت باي", + "FormulasStrings.piCapital": "الثابت باي", "FormulasStrings.planckConstant": "Planck's constant", "FormulasStrings.plusMinus": "Plus-minus", "FormulasStrings.product": "Product", @@ -1134,7 +1234,7 @@ "FormulasStrings.reducibleTo": "Reducible to", "FormulasStrings.rho": "rho", "FormulasStrings.right": "Right", - "FormulasStrings.rightArrow": "Right arrow", + "FormulasStrings.rightArrow": "سهم أيمن", "FormulasStrings.rightCeiling": "Right ceiling", "FormulasStrings.rightDouble": "Right (double)", "FormulasStrings.rightFloor": "Right floor", @@ -1155,21 +1255,21 @@ "FormulasStrings.southeast": "Southeast", "FormulasStrings.southwest": "Southwest", "FormulasStrings.spade": "Spade", - "FormulasStrings.squareRoot": "Square root", + "FormulasStrings.squareRoot": "جذر تربيعي", "FormulasStrings.subscript": "Subscript", "FormulasStrings.subset": "Subset", "FormulasStrings.subsetOrEqual": "Subset or equal", "FormulasStrings.subtraction": "Subtraction", "FormulasStrings.sum": "Sum", - "FormulasStrings.superscript": "Superscript", + "FormulasStrings.superscript": "أحرف علوية", "FormulasStrings.superset": "Superset", "FormulasStrings.supersetOrEqual": "Superset or equal", "FormulasStrings.symmetricDifference": "Symmetric difference", "FormulasStrings.tau": "tau", "FormulasStrings.tensorProduct": "Tensor product", "FormulasStrings.therefore": "Therefore", - "FormulasStrings.theta": "theta", - "FormulasStrings.thetaCapital": "Theta", + "FormulasStrings.theta": "ثيتا", + "FormulasStrings.thetaCapital": "ثيتا", "FormulasStrings.topElement": "Top element", "FormulasStrings.triangleDown": "Triangle down", "FormulasStrings.triangleUp": "Triangle up", @@ -1204,16 +1304,6 @@ "HintsEditor.newHintBtnLabel": "تلميح جديد", "HintsEditor.noHintsPlaceholder": "لا يوجد تلميحات للسؤال", "ImageOnlyThumbnail.thumbnail": "{title} صورة مصغرة", - "ImagesMenu.acceptsText": "أنواع الملفات المدعومة: {acceptedFormats}", - "ImagesMenu.altTextHint": "إن وصف الصورة ضروري لتمكين المتعلمين ضعاف البصر من الإجابة على الأسئلة، إضافة إلى عرضه في حال فشل تحميل الصورة", - "ImagesMenu.altTextLabel": "وصف الصورة", - "ImagesMenu.btnLabelCancel": "إلغاء", - "ImagesMenu.btnLabelInsert": "إدراج", - "ImagesMenu.currentImageDefaultText": "الصورة الحالية", - "ImagesMenu.defaultDropText": "سحب وإفلات صورة هنا، أو رفعها يدوياً", - "ImagesMenu.imageHeader": "تحميل صُورة", - "ImagesMenu.selectFile": "حدد ملفاً", - "ImagesMenu.selectFileButton": "حدد ملفاً", "ImportFromChannelsModal.addButton": "إضافة", "ImportFromChannelsModal.addedText": "تمّت الإضافة", "ImportFromChannelsModal.importAction": "الاستيراد", @@ -1223,6 +1313,7 @@ "ImportFromChannelsModal.reviewAction": "مراجعة", "ImportFromChannelsModal.reviewTitle": "تحديد المصدر", "InfoModal.close": "إغلاق", + "InfoModal.open": "More information about licenses", "InheritAncestorMetadataModal.applyResourceDetailsTitle": "تطبيق التفاصيل من المجلد '{folder}'", "InheritAncestorMetadataModal.cancelAction": "إلغاء", "InheritAncestorMetadataModal.categories": "الفئات: {categories}", @@ -1255,17 +1346,48 @@ "MainNavigationDrawer.helpLink": "المساعدة والدعم", "MainNavigationDrawer.logoutLink": "تسجيل الخروج", "MainNavigationDrawer.settingsLink": "الإعدادات", - "MarkdownEditor.bold": "خط عريض (Ctrl+B)", - "MarkdownEditor.formulas": "إدراج صيغة (Ctrl+F)", - "MarkdownEditor.image": "إدراج صورة (Ctrl+P)", - "MarkdownEditor.italic": "خط مائل (Ctrl+I)\n", - "MarkdownEditor.minimize": "تصغير (Ctrl+M)", - "MarkdownImageField.editImageOption": "تعديل", - "MarkdownImageField.removeImageOption": "إزالة", - "MarkdownImageField.resizeImageOption": "تغيير الحجم", "MasteryCriteriaGoal.labelText": "الهدف", "MasteryCriteriaMofNFields.mHint": "الإجابات الصحيحة مطلوبة", "MasteryCriteriaMofNFields.nHint": "الإجابات الأخيرة", + "MathLiveA11yStrings.accented": "accented", + "MathLiveA11yStrings.array": "array", + "MathLiveA11yStrings.box": "box", + "MathLiveA11yStrings.chemicalFormula": "chemical formula", + "MathLiveA11yStrings.crossOut": "شطب", + "MathLiveA11yStrings.deleted": "تمّ الحذف: ", + "MathLiveA11yStrings.delimiter": "delimiter", + "MathLiveA11yStrings.denominator": "denominator", + "MathLiveA11yStrings.endOf": "{spokenText}; end of {relationName}", + "MathLiveA11yStrings.endOfMathfield": "{spokenText}; end of mathfield", + "MathLiveA11yStrings.error": "خطأ", + "MathLiveA11yStrings.extensibleSymbol": "extensible symbol", + "MathLiveA11yStrings.first": "first", + "MathLiveA11yStrings.fraction": "fraction", + "MathLiveA11yStrings.group": "group", + "MathLiveA11yStrings.index": "index", + "MathLiveA11yStrings.latex": "LaTeX", + "MathLiveA11yStrings.line": "السطر", + "MathLiveA11yStrings.mathField": "math field", + "MathLiveA11yStrings.mathfield": "math field", + "MathLiveA11yStrings.numerator": "numerator", + "MathLiveA11yStrings.operator": "operator", + "MathLiveA11yStrings.outOf": "out of {relationName};", + "MathLiveA11yStrings.overUnder": "over-under", + "MathLiveA11yStrings.parent": "parent", + "MathLiveA11yStrings.placeholder": "placeholder", + "MathLiveA11yStrings.prompt": "prompt", + "MathLiveA11yStrings.radicand": "radicand", + "MathLiveA11yStrings.rule": "rule", + "MathLiveA11yStrings.selected": "تم التحديد: ", + "MathLiveA11yStrings.space": "space", + "MathLiveA11yStrings.spacing": "spacing", + "MathLiveA11yStrings.squareRoot": "جذر تربيعي", + "MathLiveA11yStrings.startOf": "start of {relationName}: ", + "MathLiveA11yStrings.subscript": "subscript", + "MathLiveA11yStrings.subscriptSuperscript": "subscript-superscript", + "MathLiveA11yStrings.superscript": "أحرف علوية", + "MathLiveA11yStrings.superscriptAndSubscript": "superscript and subscript", + "MathLiveA11yStrings.text": "text", "MessageLayout.backToLogin": "المتابعة إلى صفحة تسجيل الدخول", "MoveModal.addTopic": "إضافة مجلد جديد", "MoveModal.cancel": "إلغاء", @@ -1296,7 +1418,7 @@ "PasswordField.passwordLabel": "كلمة المرور", "PasswordInstructionsSent.passwordInstructionsHeader": "تم إرسال التعليمات. شكراً لك!", "PasswordInstructionsSent.passwordInstructionsText": "في حال كان هناك بالفعل حساب مرتبط بعنوان البريد الإلكتروني المرسل، فستصلك التعليمات قريباً. في حال لم تتلقّ بريداً إلكترونياً من طرفنا، فيرجى التحقق من صندوق البريد العشوائي.", - "PermissionsError.goToHomePageAction": "انتقل إلى الصفحة الرئيسية", + "PermissionsError.backToHome": "العودة إلى الصفحة الرئيسية", "PermissionsError.permissionDeniedHeader": "هل نسيت تسجيل الدخول؟", "PoliciesModal.checkboxText": "لقد وافقت على الشروط الواردة أعلاه", "PoliciesModal.closeButton": "إغلاق", @@ -1306,6 +1428,7 @@ "PrivacyPolicyModal.updatedPrivacyHeader": "سياسة الخصوصية المحدثة", "ProgressBar.progressText": "{percent}%", "ProgressModal.defaultErrorText": "فشلت آخر محاولة للنشر", + "ProgressModal.draftHeader": "Saving draft...", "ProgressModal.lastPublished": "منشور {last_published}", "ProgressModal.publishHeader": "نشر القناة", "ProgressModal.syncError": "فشلت آخر محاولة للمزامنة", @@ -1346,6 +1469,19 @@ "RelatedResourcesTab.showPreviewBtnLabel": "عرض", "RelatedResourcesTab.tooManyNextStepsWarning": "قم بالحد من عدد الخطوات التالية لإنشاء تجربة تعلم أكثر توجيهاً", "RelatedResourcesTab.tooManyPreviousStepsWarning": "قم بالحد من عدد الخطوات السابقة لإنشاء تجربة تعلم أكثر توجيهاً", + "RemoveChannelFromListModal.cancel": "إلغاء", + "RemoveChannelFromListModal.channelRemovedSnackbar": "تمت إزالة القناة", + "RemoveChannelFromListModal.removeBtn": "إزالة", + "RemoveChannelFromListModal.removePrompt": "الوصول المتاح لك يمكنك من عرض هذه القناة فقط. يرجى تأكيد أنك تريد إزالتها من قائمة القنوات الخاصة بك.", + "RemoveChannelFromListModal.removeTitle": "إزالة من قائمة القنوات", + "RemoveChannelModal.cancel": "إلغاء", + "RemoveChannelModal.deleteChannel": "حذف القناة التعليمية", + "RemoveChannelModal.deleteChannelWithCLWarning": "This channel has been shared with the Community Library. Deleting it here will not remove it from the Community Library — it may still be approved or remain available there.", + "RemoveChannelModal.deletePrompt": "سيتم حذف هذه القناة بشكل دائم. لا يمكن التراجع عن هذه الخطوة.", + "RemoveChannelModal.deleteTitle": "حذف هذه القناة", + "RemoveChannelModal.removeBtn": "إزالة", + "RemoveChannelModal.removePrompt": "الوصول المتاح لك يمكنك من عرض هذه القناة فقط. يرجى تأكيد أنك تريد إزالتها من قائمة القنوات الخاصة بك.", + "RemoveChannelModal.removeTitle": "إزالة من قائمة القنوات", "ReportErrorModal.closeAction": "إغلاق", "ReportErrorModal.emailDescription": "اتصل بفريق الدعم الفني لإرسال تفاصيل الخطأ، وسوف نبذل قصارى جهدنا للمساعدة.", "ReportErrorModal.emailPrompt": "أرسل بريدًا إلكترونيًا إلى الفريق الفني", @@ -1533,8 +1669,11 @@ "SearchRecommendationsStrings.tooAdvancedForLearnersLabel": "مُتقدم جدًا بالنسبة لمستوى معرفة المتعلمين الذي أبحث عنه", "SearchRecommendationsStrings.tooBasicForLearnersLabel": "بسيط جدًا بالنسبة لمستوى معرفة المتعلمين الذي أبحث عنه", "SearchRecommendationsStrings.tryAgainLink": "حاول مرة أخرى", + "SearchRecommendationsStrings.trySearchRecommendationsHeader": "Try our new 'Recommendations' feature!", + "SearchRecommendationsStrings.trySearchRecommendationsText": "Based on the title and description of the folder you are working on, we'll suggest relevant resources from the Kolibri Library. Choose 'Import from channels' in any folder of your channels to see the recommendations.", "SearchRecommendationsStrings.undoAction": "تراجع", "SearchRecommendationsStrings.viewMoreLink": "عرض المزيد", + "SearchRecommendationsStrings.viewRecommendationsButton": "View recommendations", "SearchResultsList.failedToLoad": "يتعذر تحميل نتائج البحث", "SearchResultsList.resultsPerPageLabel": "النتائج لكل صفحة", "SearchResultsList.saveSearchAction": "حفظ عملية البحث", @@ -1588,10 +1727,121 @@ "Storage.spaceUsedOfMax": "{qty} من {max}", "Storage.storagePercentageUsed": "{qty}% مساحة تخزين مستخدمة", "Storage.videoFiles": "يجب ضغط ملفات الفيديو لزيادة مساحة التخزين وضمان سلاسة التوزيع والتشغيل دون اتصال. وبمجرد ضغطها، قد يكون مجموع التخزين المطلوب أقل مما توقعته في البداية.", + "StudioChannelCard.channelLanguageNotSetIndicator": "لم يتم تعيين لغة", + "StudioChannelCard.details": "التفاصيل", + "StudioChannelCard.lastPublished": "منشور {last_published}", + "StudioChannelCard.lastUpdated": "{updated} تم تحديثها", + "StudioChannelCard.multipleCountries": "Multiple countries", + "StudioChannelCard.resourceCount": "{count, plural, zero {# مصادر} one {# مصادر} two {# مصدران} few {# مصادر} many {# مصدراً}\n =1 {# مصدر}\n other {# مصادر}}", + "StudioChannelCard.selectChannel": "Select {name}", + "StudioChannelCard.unpublishedText": "لم يتم النشر", + "StudioChannelsPage.invitations": "لديكَ {count, plural, zero {# دعوات} one {# دعوات} two {# دعوتان} few {# دعوات} many {# دعوة}\n =1 {# دعوة}\n other {# دعوات}}", + "StudioChannelsPage.noChannelsFound": "لم يتم العثور على أي قناة", + "StudioCollectionsTable.aboutChannelSets": "حول المجموعات", + "StudioCollectionsTable.aboutChannelSetsLink": "Learn more about collections", + "StudioCollectionsTable.addChannelSetTitle": "مجموعة جديدة", + "StudioCollectionsTable.cancel": "إلغاء", + "StudioCollectionsTable.cancelButtonLabel": "إغلاق", + "StudioCollectionsTable.channelNumber": "عدد القنوات", + "StudioCollectionsTable.channelSetsDescriptionText": "تحتوي المجموعة على العديد من قنوات استوديو كوليبري التي يمكن استيرادها في دفعة واحدة إلى كوليبري برمز تعريفي واحد.", + "StudioCollectionsTable.channelSetsDisclaimer": "ستحتاج إلى إصدار كوليبري 0.12.0 أو ما بعده لاستيراد مجموعات القنوات", + "StudioCollectionsTable.channelSetsInstructionsText": "يمكنك إنشاء مجموعة عن طريق تحديد القنوات التي تريد استيرادها معاً.", + "StudioCollectionsTable.collectionDeleted": "Collection deleted", + "StudioCollectionsTable.copiedTokenId": "تم نسخ الرمز التعريفي", + "StudioCollectionsTable.copyFailed": "فشل النسخ", + "StudioCollectionsTable.copyToken": "Copy token", + "StudioCollectionsTable.delete": "حذف المجموعة", + "StudioCollectionsTable.deleteChannelSetText": "هل أنت متأكد من أنك تريد حذف هذه المجموعة؟", + "StudioCollectionsTable.deleteChannelSetTitle": "حذف المجموعة", + "StudioCollectionsTable.deleteError": "Error deleting collection", + "StudioCollectionsTable.edit": "تحرير المجموعة", + "StudioCollectionsTable.noChannelSetsFound": "يمكنك تجميع قنوات متعددة لإنشاء مجموعة. ثم يمكن استيراد المجموعة بأكملها إلى كوليبري دفعة واحدة باستخدام رمز المجموعة التعريفي.", + "StudioCollectionsTable.options": "الخيارات", + "StudioCollectionsTable.pageTitle": "المجموعات", + "StudioCollectionsTable.saving": "جاري الحفظ", + "StudioCollectionsTable.tableCaption": "List of collections", + "StudioCollectionsTable.title": "اسم المجموعة", + "StudioCollectionsTable.token": "معرّف الرمز التعريفي", + "StudioCopyToken.copiedTokenId": "تم نسخ الرمز التعريفي", + "StudioCopyToken.copyFailed": "فشل النسخ", + "StudioCopyToken.token": "الرمز التعريفي", + "StudioCopyToken.tooltipText": "انسخ الرمز التعريفي لاستيراد القناة إلى كوليبري", + "StudioDetailsPanel.AVERAGE": "متوسط", + "StudioDetailsPanel.LARGE": "كبير", + "StudioDetailsPanel.SMALL": "صغير", + "StudioDetailsPanel.VERY_LARGE": "كبير جداً", + "StudioDetailsPanel.VERY_SMALL": "صغير جداً", + "StudioDetailsPanel.aggregatorToolTip": "الموقع الإلكتروني أو المؤسسة التي تستضيف مجموعة المحتوى ولكن ليس بالضرورة أن تكون هي منشئ المحتوى أو مالك حقوق النشر", + "StudioDetailsPanel.aggregatorsLabel": "جامعو المحتوى", + "StudioDetailsPanel.assessmentsIncludedText": "التقييمات", + "StudioDetailsPanel.authorToolTip": "الشخص أو المنظمة التي أنشأت هذا المحتوى", + "StudioDetailsPanel.authorsLabel": "المؤلفون", + "StudioDetailsPanel.categoriesHeading": "الفئات", + "StudioDetailsPanel.coachDescription": "يمكن عرض مصادر المدرِّبين فقط للمدرِّبين في كوليبري", + "StudioDetailsPanel.coachHeading": "مصادر للمدرِّبين", + "StudioDetailsPanel.containsContentHeading": "يتضمن محتوى من", + "StudioDetailsPanel.containsHeading": "يتضمن", + "StudioDetailsPanel.copyrightHoldersLabel": "مالكو حقوق النشر", + "StudioDetailsPanel.creationHeading": "تم الإنشاء في", + "StudioDetailsPanel.currentVersionHeading": "الإصدار المنشور", + "StudioDetailsPanel.languagesHeading": "اللغات", + "StudioDetailsPanel.levelsHeading": "المستويات", + "StudioDetailsPanel.licensesLabel": "التراخيص", + "StudioDetailsPanel.primaryLanguageHeading": "اللغة الأساسية", + "StudioDetailsPanel.providerToolTip": "المنظمة التي كلَّفت بإنشاء المحتوى أو تقوم بنشر المحتوى", + "StudioDetailsPanel.providersLabel": "مقدمو المحتوى", + "StudioDetailsPanel.publishedHeading": "تم النشر في", + "StudioDetailsPanel.resourceHeading": "إجمالي عدد المصادر", + "StudioDetailsPanel.sampleFromChannelHeading": "محتوى كعينة من هذه القناة", + "StudioDetailsPanel.sizeHeading": "حجم القناة", + "StudioDetailsPanel.sizeText": "{text} ({size})", + "StudioDetailsPanel.subtitlesHeading": "عناوين الصور والنصوص السمعية", + "StudioDetailsPanel.tagsHeading": "وسوم شائعة", + "StudioDetailsPanel.tokenHeading": "الرمز التعريفي للقناة", + "StudioDetailsPanel.unpublishedText": "لم يتم النشر", + "StudioImmersiveModal.close": "إغلاق", + "StudioMessageLayout.backToLogin": "المتابعة إلى صفحة تسجيل الدخول", + "StudioMyChannels.copyToken": "نسخ معرّف القناة", + "StudioMyChannels.deleteChannel": "حذف القناة التعليمية", + "StudioMyChannels.editChannel": "تعديل تفاصيل القناة", + "StudioMyChannels.goToWebsite": "التوجّه إلى موقع المصدر الإلكتروني", + "StudioMyChannels.moreOptions": "خيارات إضافية", + "StudioMyChannels.newChannel": "قناة جديدة", + "StudioMyChannels.title": "قنواتي", + "StudioMyChannels.viewContent": "عرض القناة على كوليبري", "StudioOfflineAlert.offlineText": "يبدو أنك غير متصل بالانترنت. سيتم حفظ التغييرات الخاصة بك بمجرد عودة الاتصال الخاص بك.", "StudioOfflineAlert.onlineText": "عاد الاتصال.", + "StudioStarredChannels.copyToken": "نسخ معرّف القناة", + "StudioStarredChannels.deleteChannel": "حذف القناة التعليمية", + "StudioStarredChannels.editChannel": "تعديل تفاصيل القناة", + "StudioStarredChannels.goToWebsite": "التوجّه إلى موقع المصدر الإلكتروني", + "StudioStarredChannels.moreOptions": "خيارات إضافية", + "StudioStarredChannels.removeChannel": "Remove channel", + "StudioStarredChannels.title": "Starred channels", + "StudioStarredChannels.viewContent": "عرض القناة على كوليبري", "StudioTree.missingTitle": "عنوان مفقود", "StudioTree.optionsTooltip": "الخيارات", + "StudioViewOnlyChannels.copyToken": "نسخ معرّف القناة", + "StudioViewOnlyChannels.goToWebsite": "التوجّه إلى موقع المصدر الإلكتروني", + "StudioViewOnlyChannels.moreOptions": "خيارات أخرى", + "StudioViewOnlyChannels.removeChannel": "Remove channel", + "StudioViewOnlyChannels.title": "View-only channels", + "StudioViewOnlyChannels.viewContent": "عرض القناة على كوليبري", + "SubscriptionCard.annualPrice": "${price, number}/year", + "SubscriptionCard.cancelNotice": "Your subscription will expire on {date, date, medium}. Storage will be removed after that.", + "SubscriptionCard.dismiss": "رفض", + "SubscriptionCard.genericError": "There was a problem connecting to the payment provider. Please try again.", + "SubscriptionCard.instantUpgrade": "Upgrade storage now", + "SubscriptionCard.manageSubscription": "Manage subscription", + "SubscriptionCard.renewalNotice": "Your subscription will automatically renew on {date, date, medium}.", + "SubscriptionCard.storageAmount": "Storage (GB)", + "SubscriptionCard.storageIncluded": "{size} included in your subscription", + "SubscriptionCard.storageRange": "Enter a value between 1 and 50", + "SubscriptionCard.subscriptionActive": "Storage subscription active", + "SubscriptionCard.subscriptionCanceling": "Subscription cancelled", + "SubscriptionCard.upgradeDescription": "Purchase additional storage at $15/GB per year.", + "SubscriptionCard.upgradeNow": "Upgrade now", + "SubscriptionCard.upgradeSuccess": "Storage increased to {size}", "SubtitlesList.acceptedFormatsTooltip": "الصيغ مدعومة: {extensions}", "SubtitlesList.addSubtitleText": "إضافة عناوين للصور", "SubtitlesList.subtitlesHeader": "عناوين الصور والنص السمعي", @@ -1620,7 +1870,7 @@ "TechnicalTextBlock.copiedToClipboardConfirmation": "تمّ النّسخ إلى الحافظة", "TechnicalTextBlock.copiedToClipboardFailure": "فشلت عملية النسخ إلى الحافظة", "TechnicalTextBlock.copyToClipboardButtonPrompt": "نسخ إلى الحافظة", - "TermsOfServiceModal.ToSHeader": "Terms of Service", + "TermsOfServiceModal.ToSHeader": "شروط الخدمة", "TermsOfServiceModal.acceptableUseHeader": "Acceptable Use Restrictions", "TermsOfServiceModal.acceptableUseItem1": "Will be in strict accordance with these Terms;", "TermsOfServiceModal.acceptableUseItem10": "Will not interfere with, disrupt, or attack any service or network; and", @@ -1651,7 +1901,7 @@ "TermsOfServiceModal.changesToToSP1": "We are constantly updating our Service and that means sometimes we have to change the legal terms under which our Service is offered. These Terms may only be modified by a written amendment signed by an authorized executive of Learning Equality, or by the posting by Learning Equality of a revised version. If we make changes that are material, we will let you know by posting on one of our blogs, or by sending you an email or other communication before the changes take effect. The notice will designate a reasonable period of time after which the new terms will take effect. If you disagree with our changes, then you should stop using the Service within the designated notice period, or once the changes become effective. Your continued use of the Service will be subject to the new terms. However, any dispute that arose before the changes shall be governed by the Terms (including the binding individual arbitration clause) that were in place when the dispute arose.", "TermsOfServiceModal.communicationsHeader": "Communications with Learning Equality", "TermsOfServiceModal.communicationsP1": "For contractual purposes, you (1) consent to receive communications from us in an electronic form via the email address you have submitted or via the Service; and (2) agree that all Terms of Service, agreements, notices, disclosures, and other communications that we provide to you electronically satisfy any legal requirement that those communications would satisfy if they were on paper. This section does not affect your non-waivable rights.", - "TermsOfServiceModal.communityStandardsHeader": "Community Standards", + "TermsOfServiceModal.communityStandardsHeader": "معايير المجتمع", "TermsOfServiceModal.communityStandardsLink": "Learn more about Studio's community standards", "TermsOfServiceModal.communityStandardsP1": "For more information about the intended use of the Service, and standards around Content, please see our Community Standards page.", "TermsOfServiceModal.definitionsHeader": "Definitions", @@ -1690,7 +1940,7 @@ "TermsOfServiceModal.thirdPartyP1": "The links to third party websites, any third party content, and any third party applications may be provided for your convenience and information only. The content on any linked website or in any third party application is not under our control and we are not responsible for the content of linked websites and/or third party applications, including any further links contained in a third party website. We make no representations or warranties in connection with any third party content or third party applications, which at all times and in each instance is provided \"as is.\" Third party applications may be subject to additional policies and conditions or agreements between you and the provider of such third party applications. You agree to fully comply with all such additional policies, conditions and agreements. If you decide to access any third party content, and/or any third party application, you do so entirely at your own risk.", "TermsOfServiceModal.thirdPartyRightsHeader": "Third Party Rights", "TermsOfServiceModal.thirdPartyRightsP1": "Nothing in our Terms is intended to confer on any third party any benefit or any right (under the Contracts (Rights of Third Parties) Act 1999 UK or otherwise) to enforce any provision of our Terms or any agreement entered into in connection with it.", - "TermsOfServiceModal.updatedToSHeader": "Updated terms of service", + "TermsOfServiceModal.updatedToSHeader": "شروط الخدمة المحدثة", "TermsOfServiceModal.userContentHeader": "User-Generated Content", "TermsOfServiceModal.userContentList1Item1": "We do not endorse any uploaded Content or represent that Content is accurate, useful, or non-harmful. Content could be offensive, indecent, or objectionable; include technical inaccuracies, typographical mistakes, or other errors; or violate or infringe the privacy, publicity rights, intellectual property rights (see our Copyright Infringement and DMCA Policy section to submit copyright complaints), or other proprietary rights of third parties.", "TermsOfServiceModal.userContentList1Item2": "If you upload or author Content, or otherwise make (or allow any third party to make) Content available on the Service, you are entirely responsible for the Content, and any harm resulting from, that Content or your conduct.", @@ -1715,43 +1965,59 @@ "TextArea.fieldRequiredMessage": "هذا الحقل مطلوب", "TextField.fieldRequiredMessage": "هذا الحقل مطلوب", "Thumbnail.thumbnail": "{title} صورة مصغرة", + "ThumbnailGenerator.closeButtonLabel": "موافقة", "ThumbnailGenerator.generatedDefaultFilename": "صورة مصغرة تم إنشاؤها", "ThumbnailGenerator.thumbnailGenerationFailedHeader": "تعذّر إنشاء صورة مصغرة", "ThumbnailGenerator.thumbnailGenerationFailedText": "حدثت مشكلة أثناء إنشاء صورة مصغرة", + "TipTapEditorStrings.TipTapEditorLabel": "text editor - Press Enter to start editing", + "TipTapEditorStrings.TipTapViewerLabel": "text editor content", "TipTapEditorStrings.addLink": "Add link", + "TipTapEditorStrings.alignLeft": "Align left", + "TipTapEditorStrings.alignRight": "Align right", "TipTapEditorStrings.altTextDescription": "Alt text is necessary to enable visually impaired learners to answer questions, and it also displays when the image fails to load", "TipTapEditorStrings.altTextLabel": "Alt text (Optional)", "TipTapEditorStrings.altTextPlaceholder": "Describe your image...", "TipTapEditorStrings.bold": "Strong", "TipTapEditorStrings.bulletList": "Bullet list", - "TipTapEditorStrings.cancel": "Cancel", + "TipTapEditorStrings.cancel": "إلغاء", "TipTapEditorStrings.cancelLoading": "Cancel loading", + "TipTapEditorStrings.clearFormatting": "Clear formatting", "TipTapEditorStrings.clipboardAccessFailed": "Clipboard access failed. Try copying again.", - "TipTapEditorStrings.close": "Close", + "TipTapEditorStrings.close": "إغلاق", "TipTapEditorStrings.closeModal": "Close modal", "TipTapEditorStrings.codeBlock": "Code block", - "TipTapEditorStrings.copy": "Copy", + "TipTapEditorStrings.collapseFormattingBar": "Collapse formatting bar", + "TipTapEditorStrings.copy": "نسخ", "TipTapEditorStrings.copyAndPasteActions": "Copy and paste actions", "TipTapEditorStrings.copyLink": "Copy link", + "TipTapEditorStrings.decreaseFormatSize": "Decrease format size", "TipTapEditorStrings.defaultImageName": "Image", - "TipTapEditorStrings.edit": "Edit", + "TipTapEditorStrings.edit": "تعديل", "TipTapEditorStrings.editImage": "Edit image", "TipTapEditorStrings.editLink": "Edit link", + "TipTapEditorStrings.editorControls": "Editor controls", + "TipTapEditorStrings.errorUploadingImage": "Error uploading image", + "TipTapEditorStrings.expandFormattingBar": "Expand formatting bar", "TipTapEditorStrings.failedToProcessImage": "Failed to process the image file.", - "TipTapEditorStrings.fileSizeUnit": "MB.", + "TipTapEditorStrings.fileSizeUnit": "ميغا بايت.", "TipTapEditorStrings.fileTooLarge": "File is too large. Maximum size is ", "TipTapEditorStrings.formatHeader1": "Header 1", "TipTapEditorStrings.formatHeader2": "Header 2", "TipTapEditorStrings.formatHeader3": "Header 3", "TipTapEditorStrings.formatNormal": "Normal", "TipTapEditorStrings.formatOptions": "Format options", - "TipTapEditorStrings.formatSmall": "Small", + "TipTapEditorStrings.formatSize": "Format size", + "TipTapEditorStrings.formatSmall": "صغير", "TipTapEditorStrings.formulasMenuTitle": "Special Characters", "TipTapEditorStrings.goToLink": "Go to link", "TipTapEditorStrings.historyActions": "History actions", "TipTapEditorStrings.imageDropZoneText": "Drag and drop an image here or upload manually", "TipTapEditorStrings.imagePreview": "Image preview", - "TipTapEditorStrings.insert": "Insert", + "TipTapEditorStrings.increaseFormatSize": "Increase format size", + "TipTapEditorStrings.insert": "إدراج", + "TipTapEditorStrings.insertContent": "Insert content", + "TipTapEditorStrings.insertContentMenu": "Insert content menu", + "TipTapEditorStrings.insertContentOption": "Insert content option", "TipTapEditorStrings.insertImage": "Insert image", "TipTapEditorStrings.insertLink": "Insert link", "TipTapEditorStrings.insertTools": "Insert tools", @@ -1760,8 +2026,11 @@ "TipTapEditorStrings.link": "Link", "TipTapEditorStrings.linkActions": "Link actions", "TipTapEditorStrings.listFormatting": "List formatting", + "TipTapEditorStrings.loadingFormulas": "Loading math editor", "TipTapEditorStrings.mathFormula": "Math formula", + "TipTapEditorStrings.moreButtonText": "المزيد", "TipTapEditorStrings.multipleFilesDroppedWarning": "Multiple files were dropped. Only the first file has been selected.", + "TipTapEditorStrings.noEnoughStorageSpace": "Not enough storage space available. File size exceeds remaining storage.", "TipTapEditorStrings.noFileProvided": "No file provided.", "TipTapEditorStrings.numberedList": "Numbered list", "TipTapEditorStrings.opensInNewTab": "(opens in new tab)", @@ -1770,27 +2039,27 @@ "TipTapEditorStrings.pasteOptionsMenu": "Paste options menu", "TipTapEditorStrings.pasteWithoutFormatting": "Paste without formatting", "TipTapEditorStrings.redo": "Redo", - "TipTapEditorStrings.remove": "Remove", + "TipTapEditorStrings.remove": "إزالة", "TipTapEditorStrings.removeImage": "Remove image", "TipTapEditorStrings.removeLink": "Remove link", - "TipTapEditorStrings.replaceFile": "Replace file", - "TipTapEditorStrings.save": "Save", - "TipTapEditorStrings.saveChanges": "Save changes", + "TipTapEditorStrings.replaceFile": "استبدال ملف", + "TipTapEditorStrings.save": "حفظ", + "TipTapEditorStrings.saveChanges": "حفظ التغييرات", "TipTapEditorStrings.scriptFormatting": "Script formatting", - "TipTapEditorStrings.selectFile": "Select file", + "TipTapEditorStrings.selectFile": "تحديد ملف", "TipTapEditorStrings.selectFileToUpload": "Select file to upload", "TipTapEditorStrings.strikethrough": "Strikethrough", "TipTapEditorStrings.subscript": "Subscript", - "TipTapEditorStrings.superscript": "Superscript", - "TipTapEditorStrings.supportedFileTypes": "Supported file types: png, jpg, jpeg, svg, webp", + "TipTapEditorStrings.superscript": "أحرف علوية", + "TipTapEditorStrings.supportedFileTypes": "أنواع الملفات المدعومة: { extensions }", "TipTapEditorStrings.text": "Text", "TipTapEditorStrings.textFormatOptions": "Text format options", "TipTapEditorStrings.textFormattingOptions": "Text formatting options", "TipTapEditorStrings.textFormattingToolbar": "Text formatting toolbar", "TipTapEditorStrings.textStyleFormatting": "Text style formatting", "TipTapEditorStrings.underline": "Underline", - "TipTapEditorStrings.undo": "Undo", - "TipTapEditorStrings.uploadImage": "Upload image", + "TipTapEditorStrings.undo": "تراجع", + "TipTapEditorStrings.uploadImage": "رفع صورة", "TitleStrings.catalogTitle": "دليل مكتبة محتوى كوليبري", "TitleStrings.defaultTitle": "استوديو كوليبري", "TitleStrings.tabTitle": "{title} - {site}", @@ -1814,26 +2083,27 @@ "TreeView.showSidebar": "إظهار الشريط الجانبي", "TreeView.updatedResourcesReadyForReview": "المصادر المحدثة جاهزة للمعاينة", "TreeViewBase.apiGenerated": "تم توليدها/ ها من API", - "TreeViewBase.cancel": "إلغاء", "TreeViewBase.channelDeletedSnackbar": "تم حذف القناة", "TreeViewBase.channelDetails": "عرض تفاصيل القناة", "TreeViewBase.deleteChannel": "حذف القناة التعليمية", - "TreeViewBase.deleteChannelButton": "حذف القناة التعليمية", - "TreeViewBase.deletePrompt": "سيتم حذف هذه القناة بشكل دائم. لا يمكن التراجع عن هذه الخطوة.", - "TreeViewBase.deleteTitle": "حذف هذه القناة", "TreeViewBase.editChannel": "تعديل تفاصيل القناة", "TreeViewBase.emptyChannelTooltip": "لا يمكنك نشر قناة فارغة", "TreeViewBase.getToken": "احصل على رمز تعريف", "TreeViewBase.incompleteDescendantsText": "{count, number, integer} {count, plural, zero {مصادر لم تكتمل ولا يمكن نشرها} one {مصدر لم يكتمل ولا يمكن نشره} two {مصدران لم يكتملا ولا يمكن نشرهما} few {مصادر لم تكتمل ولا يمكن نشرها} many {مصدراً لم يكتمل ولا يمكن نشره} other {مصادر لم تكتمل ولا يمكن نشرها}}", + "TreeViewBase.inviteCollaborators": "دعوة المتعاونين", "TreeViewBase.noChangesText": "لم يتم العثور على تغييرات في القناة", "TreeViewBase.noLanguageSetError": "لغة القناة مطلوبة", "TreeViewBase.openTrash": "فتح سلة المهملات", "TreeViewBase.publishButton": "نشر", "TreeViewBase.publishButtonTitle": "جعل هذه القناة متاحة للاستيراد إلى كوليبري", "TreeViewBase.shareChannel": "شارك القناة", + "TreeViewBase.shareMenuButton": "مشاركة", + "TreeViewBase.shareToken": "Share token", + "TreeViewBase.submitToCommunityLibrary": "Submit to Community Library", "TreeViewBase.syncChannel": "مزامنة المصادر", "TreeViewBase.viewOnly": "قنوات للعرض فقط", - "Uploader.listDelimiter": ", ", + "Uploader.closeButtonLabel": "موافقة", + "Uploader.listDelimiter": ",", "Uploader.maxFileSizeText": "{count, plural, zero {# ملفات لن يتم تحميلها.} one {# ملف لن يتم تحميله.} two {# ملفان لن يتم تحميلهما.} few {# ملفات لن يتم تحميلها.} many {# ملفاً لن يتم تحميلها.}\n =1 {# ملف لن يتم تحميله.}\n other {# ملفات لن يتم تحميلها.}} حجم الملف يجب أن يكون أقل من {size}", "Uploader.noStorageHeader": "لا توجد سعة كافية", "Uploader.remainingStorage": "مساحة التخزين المتبقية: {size}", @@ -1906,4 +2176,4 @@ "sharedVue.masteryModelRequired": "الإتقان مطلوب", "sharedVue.shortActivityLteThirty": "يجب أن تكون القيمة مساوية أو أقل من 30", "sharedVue.titleRequired": "العنوان إلزامي" -} +} \ No newline at end of file diff --git a/contentcuration/locale/ar/LC_MESSAGES/django.po b/contentcuration/locale/ar/LC_MESSAGES/django.po index f202da9788..34ebd363a0 100644 --- a/contentcuration/locale/ar/LC_MESSAGES/django.po +++ b/contentcuration/locale/ar/LC_MESSAGES/django.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: kolibri-studio\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-09-03 19:45+0000\n" -"PO-Revision-Date: 2025-09-12 13:59\n" +"POT-Creation-Date: 2026-04-16 00:57+0000\n" +"PO-Revision-Date: 2026-04-20 19:33\n" "Last-Translator: \n" "Language-Team: Arabic\n" "Language: ar_SA\n" @@ -14,8 +14,8 @@ msgstr "" "X-Crowdin-Project: kolibri-studio\n" "X-Crowdin-Project-ID: 286000\n" "X-Crowdin-Language: ar\n" -"X-Crowdin-File: /search-recommendations-closed-beta/django.po\n" -"X-Crowdin-File-ID: 4886\n" +"X-Crowdin-File: /unstable/django.po\n" +"X-Crowdin-File-ID: 4322\n" #: contentcuration/catalog_settings.py:4 contentcuration/sandbox_settings.py:8 #: contentcuration/settings.py:271 @@ -26,25 +26,25 @@ msgstr "العربيّة" msgid "The site is currently in read-only mode. Please try again later." msgstr "الموقع حالياً في وضع القراءة فقط. الرجاء المحاولة مجدداً في وقت لاحق." -#: contentcuration/models.py:342 +#: contentcuration/models.py:356 msgid "Not enough space. Check your storage under Settings page." msgstr "لا توجد مساحة كافية. تحقق من مساحة التخزين الخاصة بك تحت صفحة الإعدادات." -#: contentcuration/models.py:374 contentcuration/models.py:386 +#: contentcuration/models.py:440 contentcuration/models.py:452 msgid "Out of storage! Request more space under Settings > Storage." msgstr "نفذت مساحة التخزين! قم بطلب المزيد من المساحة من الإعدادات> التخزين." -#: contentcuration/models.py:2155 +#: contentcuration/models.py:2531 msgid " (Original)" msgstr " (أصلي)" -#: contentcuration/models.py:3297 +#: contentcuration/models.py:3848 msgid "Created DateTime" -msgstr "أنشئ في DateTime" +msgstr "" -#: contentcuration/models.py:3299 +#: contentcuration/models.py:3850 msgid "Datetime field when the custom_metadata for task was created in UTC" -msgstr "حقل Datetime لإنشاء custom_metadata الخاصة بالمهمة بالتوقيت العالمي المنسق (UTC)" +msgstr "" #: contentcuration/settings.py:269 msgid "English" @@ -716,7 +716,7 @@ msgstr "نواجه مشكلات مع خدمة مرتبطة بجهة خارجية msgid "We are encountering issues with our data center. This means you may encounter networking problems while using Studio. We appreciate your patience while these issues are being resolved. To check the status of this service, please visit here" msgstr " نواجه مشكلات مع مركز البيانات الخاص بنا. هذا يعني أنك قد تواجه مشاكل في الشبكات أثناء استخدام الاستوديو. نقدر صبرك أثناء حل هذه المشكلات. للتحقق من حالة هذه الخدمة قم بزيارة هنا" -#: contentcuration/utils/publish.py:101 +#: contentcuration/utils/publish.py:103 msgid "Kolibri Studio Channel Published" msgstr "تم نشر قناة استوديو كوليبري" @@ -728,14 +728,15 @@ msgstr "الإبلاغ عن مشكلة في استوديو كوليبري" msgid "Kolibri Studio account deleted" msgstr "تم حذف حساب استوديو كوليبري" -#: kolibri_public/views.py:223 +#: kolibri_public/views.py:323 msgid "Resource" msgstr "مصدر" -#: kolibri_public/views_v1.py:79 kolibri_public/views_v1.py:94 +#: kolibri_public/views_v1.py:152 kolibri_public/views_v1.py:167 msgid "API version is unavailable" msgstr "إصدار الـ API غير متوفر" -#: kolibri_public/views_v1.py:97 +#: kolibri_public/views_v1.py:170 msgid "No channel matching {} found" msgstr "لم يتم العثور على قناة مطابقة {}" + diff --git a/contentcuration/locale/en/LC_MESSAGES/contentcuration-messages.json b/contentcuration/locale/en/LC_MESSAGES/contentcuration-messages.json index 27ddf2ddfc..1983272e37 100644 --- a/contentcuration/locale/en/LC_MESSAGES/contentcuration-messages.json +++ b/contentcuration/locale/en/LC_MESSAGES/contentcuration-messages.json @@ -67,8 +67,6 @@ "AdministrationAppError.unauthorizedDetails": "You need to be an administrator of Studio to view this page", "AdministrationIndex.channelsLabel": "Channels", "AdministrationIndex.usersLabel": "Users", - "Alert.closeButtonLabel": "OK", - "Alert.dontShowAgain": "Don't show this message again", "AnswersEditor.answersLabel": "Answers", "AnswersEditor.newAnswerBtnLabel": "New answer", "AnswersEditor.noAnswersPlaceholder": "Question has no answer options", @@ -115,10 +113,10 @@ "BrowsingCard.resourcesCount": "{count, number} {count, plural, one {resource} other {resources}}", "BrowsingCard.tagsList": "Tags: {tags}", "BytesForHumansStrings.fileSizeInBytes": "{n, number, integer} B", - "BytesForHumansStrings.fileSizeInGigabytes": "{n, number, integer} GB", + "BytesForHumansStrings.fileSizeInGigabytes": "{n, number} GB", "BytesForHumansStrings.fileSizeInKilobytes": "{n, number, integer} KB", "BytesForHumansStrings.fileSizeInMegabytes": "{n, number, integer} MB", - "BytesForHumansStrings.fileSizeInTerabytes": "{n, number, integer} TB", + "BytesForHumansStrings.fileSizeInTerabytes": "{n, number} TB", "CatalogFAQ.KolibriAnswer": "Kolibri is an open source ed-tech platform designed for low-resource communities, focused on:", "CatalogFAQ.KolibriAnswerItem1": "Overcoming infrastructural barriers that prevent equitable access to quality education for learners in low-resource and low-connectivity contexts", "CatalogFAQ.KolibriAnswerItem2": "Increasing the availability of open learning materials suitable for many curricula, learning goals, and situations", @@ -184,26 +182,31 @@ "CatalogFilterBar.keywords": "\"{text}\"", "CatalogFilterBar.starred": "Starred", "CatalogFilterBar.subtitles": "Subtitles", - "CatalogFilters.coachDescription": "Resources for coaches are only visible to coaches in Kolibri", - "CatalogFilters.coachLabel": "Resources for coaches", - "CatalogFilters.copyright": "© {year} Learning Equality", - "CatalogFilters.formatLabel": "Formats", - "CatalogFilters.frequentlyAskedQuestionsLink": "Frequently asked questions", - "CatalogFilters.includesLabel": "Display only channels with", - "CatalogFilters.licenseLabel": "Licenses", - "CatalogFilters.searchLabel": "Keywords", - "CatalogFilters.searchText": "Search", - "CatalogFilters.starredLabel": "Starred", - "CatalogFilters.subtitlesLabel": "Captions or subtitles", + "CatalogFilterPanelContent.coachDescription": "Resources for coaches are only visible to coaches in Kolibri", + "CatalogFilterPanelContent.coachLabel": "Resources for coaches", + "CatalogFilterPanelContent.copyright": "© {year} Learning Equality", + "CatalogFilterPanelContent.formatLabel": "Formats", + "CatalogFilterPanelContent.frequentlyAskedQuestionsLink": "Frequently asked questions", + "CatalogFilterPanelContent.includesLabel": "Display only channels with", + "CatalogFilterPanelContent.licenseLabel": "Licenses", + "CatalogFilterPanelContent.searchLabel": "Keywords", + "CatalogFilterPanelContent.starredLabel": "Starred", + "CatalogFilterPanelContent.subtitlesLabel": "Captions or subtitles", + "CatalogFilters.filterLabel": "Filter", "CatalogList.cancelButton": "Cancel", "CatalogList.channelSelectionCount": "{count, plural,\n =1 {# channel selected}\n other {# channels selected}}", + "CatalogList.copyToken": "Copy channel token", "CatalogList.downloadButton": "Download", "CatalogList.downloadCSV": "Download CSV", "CatalogList.downloadPDF": "Download PDF", "CatalogList.downloadingMessage": "Download started", + "CatalogList.goToWebsite": "Go to source website", + "CatalogList.moreOptions": "More options", "CatalogList.resultsText": "{count, plural,\n =1 {# result found}\n other {# results found}}", "CatalogList.selectAll": "Select all", "CatalogList.selectChannels": "Download a summary of selected channels", + "CatalogList.title": "Kolibri library", + "CatalogList.viewContent": "View channel on Kolibri", "CategoryOptions.noCategoryFoundText": "Category not found", "ChangePasswordForm.cancelAction": "Cancel", "ChangePasswordForm.changePasswordHeader": "Change password", @@ -224,9 +227,6 @@ "ChannelCatalogFrontPage.languagesHeading": "Languages", "ChannelCatalogFrontPage.numberOfChannels": "{ num } channels", "ChannelCatalogFrontPage.subtitlesIncludedText": "Captions or subtitles", - "ChannelDeletedError.backToHomeAction": "Back to home", - "ChannelDeletedError.channelDeletedDetails": "This channel does not exist or may have been removed. Please contact us at content@learningequality.org if you think this is a mistake.", - "ChannelDeletedError.channelDeletedHeader": "Channel not found", "ChannelDetailsModal.downloadButton": "Download channel summary", "ChannelDetailsModal.downloadCSV": "Download CSV", "ChannelDetailsModal.downloadPDF": "Download PDF", @@ -263,23 +263,17 @@ "ChannelInvitation.editText": "{sender} has invited you to edit {channel}", "ChannelInvitation.goToChannelSnackbarAction": "Go to channel", "ChannelInvitation.viewText": "{sender} has invited you to view {channel}", - "ChannelItem.cancel": "Cancel", "ChannelItem.channelDeletedSnackbar": "Channel deleted", "ChannelItem.channelLanguageNotSetIndicator": "No language set", "ChannelItem.channelRemovedSnackbar": "Channel removed", "ChannelItem.copyToken": "Copy channel token", "ChannelItem.deleteChannel": "Delete channel", - "ChannelItem.deletePrompt": "This channel will be permanently deleted. This cannot be undone.", - "ChannelItem.deleteTitle": "Delete this channel", "ChannelItem.details": "Details", "ChannelItem.editChannel": "Edit channel details", "ChannelItem.goToWebsite": "Go to source website", "ChannelItem.lastPublished": "Published {last_published}", "ChannelItem.lastUpdated": "Updated {updated}", - "ChannelItem.removeBtn": "Remove", - "ChannelItem.removeChannel": "Remove from channel list", - "ChannelItem.removePrompt": "You have view-only access to this channel. Confirm that you want to remove it from your list of channels.", - "ChannelItem.removeTitle": "Remove from channel list", + "ChannelItem.removeChannel": "Remove channel", "ChannelItem.resourceCount": "{count, plural,\n =1 {# resource}\n other {# resources}}", "ChannelItem.unpublishedText": "Unpublished", "ChannelItem.versionText": "Version {version}", @@ -289,10 +283,9 @@ "ChannelList.noChannelsFound": "No channels found", "ChannelList.noMatchingChannels": "There are no matching channels", "ChannelListAppError.channelPermissionsErrorDetails": "Sign in or ask the owner of this channel to give you permission to edit or view", - "ChannelListIndex.catalog": "Content Library", + "ChannelListIndex.catalog": "Kolibri Library", "ChannelListIndex.channelSets": "Collections", "ChannelListIndex.frequentlyAskedQuestions": "Frequently asked questions", - "ChannelListIndex.invitations": "You have {count, plural,\n =1 {# invitation}\n other {# invitations}}", "ChannelListIndex.libraryTitle": "Kolibri Content Library Catalog", "ChannelModal.APIText": "Channels generated automatically are not editable.", "ChannelModal.changesSaved": "Changes saved", @@ -316,25 +309,6 @@ "ChannelNotFoundError.channelNotFoundHeader": "Channel not found", "ChannelSelectionList.noChannelsFound": "No channels found", "ChannelSelectionList.searchText": "Search for a channel", - "ChannelSetItem.cancel": "Cancel", - "ChannelSetItem.delete": "Delete collection", - "ChannelSetItem.deleteChannelSetText": "Are you sure you want to delete this collection?", - "ChannelSetItem.deleteChannelSetTitle": "Delete collection", - "ChannelSetItem.edit": "Edit collection", - "ChannelSetItem.options": "Options", - "ChannelSetItem.saving": "Saving", - "ChannelSetList.aboutChannelSets": "About collections", - "ChannelSetList.aboutChannelSetsLink": "Learn about collections", - "ChannelSetList.addChannelSetTitle": "New collection", - "ChannelSetList.cancelButtonLabel": "Close", - "ChannelSetList.channelNumber": "Number of channels", - "ChannelSetList.channelSetsDescriptionText": "A collection contains multiple Kolibri Studio channels that can be imported at one time to Kolibri with a single collection token.", - "ChannelSetList.channelSetsDisclaimer": "You will need Kolibri version 0.12.0 or higher to import channel collections", - "ChannelSetList.channelSetsInstructionsText": "You can make a collection by selecting the channels you want to be imported together.", - "ChannelSetList.noChannelSetsFound": "You can package together multiple channels to create a collection. The entire collection can then be imported to Kolibri at once by using a collection token.", - "ChannelSetList.options": "Options", - "ChannelSetList.title": "Collection name", - "ChannelSetList.token": "Token ID", "ChannelSetModal.bookmark": "Starred", "ChannelSetModal.channelAdded": "Channel added", "ChannelSetModal.channelCountText": "{channelCount, plural, =0 {No published channels in your collection} =1 {# channel} other {# channels}}", @@ -533,6 +507,163 @@ "CommonMetadataStrings.webDesign": "Web design", "CommonMetadataStrings.work": "Work", "CommonMetadataStrings.writing": "Writing", + "CommonStrings.backAction": "Back", + "CommonStrings.channelDetailsLabel": "Channel Details", + "CommonStrings.clearAction": "Clear", + "CommonStrings.closeAction": "Close", + "CommonStrings.copyChannelTokenAction": "Copy channel token", + "CommonStrings.dismissAction": "Dismiss", + "CommonStrings.genericErrorMessage": "Sorry! Something went wrong, please try again.", + "CommonStrings.previewAction": "Preview", + "CommonStrings.seeAllAction": "See all", + "CommonStrings.seeLessAction": "See less", + "CommunityChannelsStrings.aboutCommunityLibraryDescription": "Community library contains channels submitted by the community and approved for discovery in Studio.", + "CommunityChannelsStrings.aboutCommunityLibraryTitle": "About Community Library", + "CommunityChannelsStrings.activityHistoryLabel": "Activity history", + "CommunityChannelsStrings.adminLabel": "Admin", + "CommunityChannelsStrings.allLicensesCompatible": "All licenses are compatible with Community Library.", + "CommunityChannelsStrings.allNotificationsLabel": "All Notifications", + "CommunityChannelsStrings.alreadySubmittedWarningDescription": "Please wait for the channel to be reviewed. You will see a notification in your Studio account after the review is complete.", + "CommunityChannelsStrings.alreadySubmittedWarningTitle": "This version of the channel has already been submitted to the Community Library.", + "CommunityChannelsStrings.approvedNotification": "{author} ({userType}) approved {channelVersion}", + "CommunityChannelsStrings.approvedPrimaryInfo": "A previous version is live in the Community Library. Reviewers will see the latest submission first.", + "CommunityChannelsStrings.approvedStatus": "Approved", + "CommunityChannelsStrings.availableStatus": "Available in Community Library", + "CommunityChannelsStrings.cancelAction": "Cancel", + "CommunityChannelsStrings.categoriesLabel": "Categories", + "CommunityChannelsStrings.channelCannotBeDistributed": "This channel cannot be distributed via Kolibri.", + "CommunityChannelsStrings.channelFitAttributionLabel": "Attribution", + "CommunityChannelsStrings.channelFitChannelInfoLabel": "Channel information", + "CommunityChannelsStrings.channelFitChecklistAttribution": "Does each resource have an author listed?", + "CommunityChannelsStrings.channelFitChecklistChannelInfo": "Is your channel's basic information filled in — title, description, thumbnail, language, category, and level?", + "CommunityChannelsStrings.channelFitChecklistIntro": "Criteria for submitting to the Community Library", + "CommunityChannelsStrings.channelFitChecklistLicense": "Are the resources in your channel openly licensed or in the public domain?", + "CommunityChannelsStrings.channelFitChecklistOfflineUse": "Do the resources in your channel work without an Internet connection?", + "CommunityChannelsStrings.channelFitChecklistQuality": "Has anyone on your team or in your organization reviewed the channel?", + "CommunityChannelsStrings.channelFitLicenseLabel": "License", + "CommunityChannelsStrings.channelFitOfflineUseLabel": "Offline use", + "CommunityChannelsStrings.channelFitQualityLabel": "Quality", + "CommunityChannelsStrings.channelTokenDescription": "You can use this token to import and preview the draft channel in Kolibri. Please note that the token for the final published channel will be different.", + "CommunityChannelsStrings.channelVersion": "{name} v{version}", + "CommunityChannelsStrings.channelVersionTokenLabel": "Channel version token", + "CommunityChannelsStrings.clearAll": "Clear all", + "CommunityChannelsStrings.clearAllAction": "Clear all", + "CommunityChannelsStrings.communityLibraryCTADescription": "Have a channel worth sharing with other educators and learners? Submit it for review through the Share menu so it can be discovered in Studio.", + "CommunityChannelsStrings.communityLibraryCTATitle": "Help grow the Community Library", + "CommunityChannelsStrings.communityLibraryDescription": "Browse community-submitted channels approved for discovery in Studio. Copy a token to use a channel in Kolibri.", + "CommunityChannelsStrings.communityLibraryLabel": "Community Library", + "CommunityChannelsStrings.communityLibrarySubmissionLabel": "Community Library submission", + "CommunityChannelsStrings.compatibleLicensesDescription": "{licenseNames} - All licenses are compatible with Community Library.", + "CommunityChannelsStrings.confirmDistributionRights": "Please confirm you have the right to distribute these resources via Kolibri.", + "CommunityChannelsStrings.confirmReplacementText": "I understand this will replace my earlier submission on the review queue", + "CommunityChannelsStrings.countriesInfoText": "Select one or more countries to label your channel submission with. For example, if your channel contains materials aligned to a national curriculum or regionally-specific content, selecting the relevant countries will help users find it. Otherwise, leave this blank.", + "CommunityChannelsStrings.countryLabel": "Countries", + "CommunityChannelsStrings.descriptionLabel": "Describe what's included in this submission", + "CommunityChannelsStrings.dismissAction": "Dismiss", + "CommunityChannelsStrings.draftBeingPublishedNotice": "Draft version is being published", + "CommunityChannelsStrings.draftPublishedNotice": "Draft published successfully", + "CommunityChannelsStrings.draftTokenLabel": "Draft token", + "CommunityChannelsStrings.editorLabel": "Editor", + "CommunityChannelsStrings.emptyNotificationsNotice": "You have no notifications at this time.", + "CommunityChannelsStrings.emptyNotificationsWithFiltersNotice": "No notifications match the applied filters.", + "CommunityChannelsStrings.errorLoadingVersions": "Unable to load version history", + "CommunityChannelsStrings.errorSnackbar": "There was an error submitting the channel", + "CommunityChannelsStrings.feedbackNotesLabel": "Notes from the reviewer", + "CommunityChannelsStrings.filterByDateLabel": "Filter by date", + "CommunityChannelsStrings.filterByStatusLabel": "Filter by status", + "CommunityChannelsStrings.filterLabel": "Filter", + "CommunityChannelsStrings.fixLicensingBeforeSubmission": "Please fix licensing before submitting a new version.", + "CommunityChannelsStrings.flaggedNotification": "Changes required for version {channelVersion}", + "CommunityChannelsStrings.flaggedStatus": "Needs changes", + "CommunityChannelsStrings.getDraftTokenAction": "Copy token for draft channel", + "CommunityChannelsStrings.goToMyChannelsAction": "Go to My channels", + "CommunityChannelsStrings.hideCriteriaAction": "Hide criteria", + "CommunityChannelsStrings.hideVersions": "Hide versions", + "CommunityChannelsStrings.incompatibleLicensesDescription": "\"{licenseNames}\" - this channel cannot be distributed via Kolibri. If you cannot change the license, remove all resources with \"{licenseNames}\" before submitting again.", + "CommunityChannelsStrings.incompatibleLicensesDetected": "Incompatible licenses detected.", + "CommunityChannelsStrings.incompleteResourcesDescription1": "Incomplete resources will not be published and made available for download in Kolibri.", + "CommunityChannelsStrings.incompleteResourcesWarning": "{count, number} {count, plural, one {incomplete resource} other {incomplete resources}}", + "CommunityChannelsStrings.internalNotesLabel": "Admin notes (internal use only)", + "CommunityChannelsStrings.invalidLicensingReason": "Invalid or non-compliant licenses", + "CommunityChannelsStrings.invalidMetadataReason": "Invalid or missing metadata", + "CommunityChannelsStrings.languageLabel": "Language", + "CommunityChannelsStrings.languagesLabel": "Languages", + "CommunityChannelsStrings.lessDetailsButton": "Hide details", + "CommunityChannelsStrings.licenseCheckPassed": "License check passed.", + "CommunityChannelsStrings.licensesLabel": "Licenses", + "CommunityChannelsStrings.loadError": "There was an error loading channels.", + "CommunityChannelsStrings.loadingVersionHistory": "Loading version history", + "CommunityChannelsStrings.moreDetails": "After your submission is approved, the channel will be available to other Kolibri Studio users on the 'Community Library' page.", + "CommunityChannelsStrings.moreDetailsButton": "More details", + "CommunityChannelsStrings.needKolibriVersionToImport": "You will need Kolibri version 0.19.4 or higher to import channels from the Community Library.", + "CommunityChannelsStrings.needsChangesPrimaryInfo": "Your previously submitted version needs changes. Make sure you have addressed all comments before resubmitting.", + "CommunityChannelsStrings.newLabel": "New", + "CommunityChannelsStrings.newNotificationsNotice": "New notifications available.", + "CommunityChannelsStrings.nextPageAction": "Next", + "CommunityChannelsStrings.noCommunityChannels": "No channels have been published to the Community Library yet.", + "CommunityChannelsStrings.noResultsWithFilters": "No channels match the selected filters.", + "CommunityChannelsStrings.noVersionsAvailable": "No version history available", + "CommunityChannelsStrings.nonePrimaryInfo": "We're inviting members of the Kolibri community to submit channels they've created for offline learning in low-resource settings. ", + "CommunityChannelsStrings.notPublishedWarningDescription": "Publish to Studio first, then submit to the Community Library.", + "CommunityChannelsStrings.notPublishedWarningTitle": "This channel isn't published to Kolibri Studio yet", + "CommunityChannelsStrings.notificationsLabel": "Notifications", + "CommunityChannelsStrings.otherIssuesReason": "Other issues", + "CommunityChannelsStrings.pageIndicator": "{currentPage} of {totalPages}", + "CommunityChannelsStrings.pendingStatus": "Submitted", + "CommunityChannelsStrings.portabilityIssuesReason": "Portability problems", + "CommunityChannelsStrings.previewYourDraftTitle": "Preview your draft channel in Kolibri", + "CommunityChannelsStrings.previousPageAction": "Previous", + "CommunityChannelsStrings.publicWarningDescription": "It is not possible to submit public channels to the Community Library.", + "CommunityChannelsStrings.publicWarningTitle": "This channel is currently public in the Kolibri Library.", + "CommunityChannelsStrings.publishAction": "Publish", + "CommunityChannelsStrings.publishChannel": "Publish channel", + "CommunityChannelsStrings.publishChannelDescription": "To see your channel in Kolibri, import using the channel token.", + "CommunityChannelsStrings.publishChannelMode": "Publish channel", + "CommunityChannelsStrings.publishDraftDescription": "Your channel will be saved as a draft, allowing you to review and do quality checks on the draft, without altering the current version available for Kolibri users. To see this draft channel in Kolibri, import using the draft channel token.", + "CommunityChannelsStrings.publishDraftMode": "Publish draft channel", + "CommunityChannelsStrings.publishedVersionLabel": "Published version:", + "CommunityChannelsStrings.publishingInfo": "You're publishing: Version {version}", + "CommunityChannelsStrings.publishingMessage": "Channel is being published", + "CommunityChannelsStrings.qualityAssuranceReason": "Quality assurance issues", + "CommunityChannelsStrings.reasonLabel": "Reason: {reason}", + "CommunityChannelsStrings.resubmitAction": "Resubmit", + "CommunityChannelsStrings.resubmitModalBodyFirst": "{channelName} v{version} is also published to the Community Library.", + "CommunityChannelsStrings.resubmitModalBodySecond": "Would you like to resubmit this version with your changes for Community Library review?", + "CommunityChannelsStrings.resubmitModalTitle": "Resubmit channel for Community library review?", + "CommunityChannelsStrings.resultsText": "{count, plural, =1 {# result found} other {# results found}}", + "CommunityChannelsStrings.retry": "Retry", + "CommunityChannelsStrings.reviewAction": "Review", + "CommunityChannelsStrings.saveDraft": "Save draft", + "CommunityChannelsStrings.searchLabel": "Search", + "CommunityChannelsStrings.searchNotificationsLabel": "Search notifications", + "CommunityChannelsStrings.seeAllVersions": "See all versions", + "CommunityChannelsStrings.showMore": "Show more", + "CommunityChannelsStrings.showOlderAction": "Show older", + "CommunityChannelsStrings.specialPermissionsDetected": "Special Permissions licenses detected", + "CommunityChannelsStrings.submissionCreationNotification": "Your submission to the Community Library was successful and is now under review.", + "CommunityChannelsStrings.submissionNotesLabel": "Submission notes", + "CommunityChannelsStrings.submissionNotification": "{author} ({userType}) submitted {channelVersion}", + "CommunityChannelsStrings.submitButton": "Submit for review", + "CommunityChannelsStrings.submitToCommunityLibrary": "Submit to Community Library", + "CommunityChannelsStrings.submittedPrimaryInfo": "A previous version is still pending review. Reviewers will see the most recent submission by default.", + "CommunityChannelsStrings.submittedSnackbar": "Channel submitted to Community Library", + "CommunityChannelsStrings.submittingSnackbar": "Submitting channel to Community Library...", + "CommunityChannelsStrings.supersededStatus": "Superseded", + "CommunityChannelsStrings.thisMonthLabel": "This month", + "CommunityChannelsStrings.thisWeekLabel": "This week", + "CommunityChannelsStrings.thisYearLabel": "This year", + "CommunityChannelsStrings.todayLabel": "Today", + "CommunityChannelsStrings.unreadNotificationsLabel": "Unread", + "CommunityChannelsStrings.versionDescriptionLabel": "Version description", + "CommunityChannelsStrings.versionLabel": "Version {version}", + "CommunityChannelsStrings.versionNotesLabel": "Describe what's included in this channel version", + "CommunityChannelsStrings.viewCriteriaAction": "View criteria", + "CommunityChannelsStrings.viewMoreAction": "View more", + "CommunityChannelsStrings.whatCanYouDoHere": "What you can do here", + "CommunityChannelsStrings.whatCanYouDoHereItem1": "Browse channels by country, category, and language", + "CommunityChannelsStrings.whatCanYouDoHereItem2": "Copy a channel token to use in Kolibri", + "CommunityChannelsStrings.whatCanYouDoHereItem3": "View channel details including description and metadata", + "CommunityChannelsStrings.whatIsCommunityLibrary": "What is the Community Library?", "CommunityStandardsModal.communityStandardsHeader": "Community Standards", "CommunityStandardsModal.coreValuesLink": "Learn more about Learning Equality's core values", "CommunityStandardsModal.description": "Learning Equality is a nonprofit organization dedicated to enabling equitable access to quality educational experiences. Along with our statement of Core Values, these Community Standards are intended to foster a supportive and inclusive environment for our users.", @@ -613,7 +744,7 @@ "ConstantStrings.perseus": "Perseus Exercise", "ConstantStrings.perseus_question": "Khan Academy question", "ConstantStrings.png": "PNG image", - "ConstantStrings.public": "Content library", + "ConstantStrings.public": "Kolibri library", "ConstantStrings.single_selection": "Single choice", "ConstantStrings.slideshow": "Slideshow", "ConstantStrings.svg": "SVG image", @@ -819,40 +950,11 @@ "DeleteAccountForm.emailInvalidText": "Email does not match your account email", "DeleteAccountForm.fieldRequired": "Field is required", "DeleteAccountForm.ok": "OK", - "DetailsPanel.AVERAGE": "Average", - "DetailsPanel.LARGE": "Large", - "DetailsPanel.SMALL": "Small", - "DetailsPanel.VERY_LARGE": "Very large", - "DetailsPanel.VERY_SMALL": "Very small", - "DetailsPanel.aggregatorToolTip": "Website or organization hosting the content collection but not necessarily the creator or copyright holder", - "DetailsPanel.aggregatorsLabel": "Aggregators", - "DetailsPanel.assessmentsIncludedText": "Assessments", - "DetailsPanel.authorToolTip": "Person or organization who created this content", - "DetailsPanel.authorsLabel": "Authors", - "DetailsPanel.categoriesHeading": "Categories", - "DetailsPanel.coachDescription": "Resources for coaches are only visible to coaches in Kolibri", - "DetailsPanel.coachHeading": "Resources for coaches", - "DetailsPanel.containsContentHeading": "Contains content from", - "DetailsPanel.containsHeading": "Contains", - "DetailsPanel.copyrightHoldersLabel": "Copyright holders", - "DetailsPanel.creationHeading": "Created on", - "DetailsPanel.currentVersionHeading": "Published version", - "DetailsPanel.languagesHeading": "Languages", - "DetailsPanel.levelsHeading": "Levels", - "DetailsPanel.licensesLabel": "Licenses", - "DetailsPanel.primaryLanguageHeading": "Primary language", - "DetailsPanel.providerToolTip": "Organization that commissioned or is distributing the content", - "DetailsPanel.providersLabel": "Providers", - "DetailsPanel.publishedHeading": "Published on", - "DetailsPanel.resourceHeading": "Total resources", - "DetailsPanel.sampleFromChannelHeading": "Sample content from this channel", - "DetailsPanel.sampleFromTopicHeading": "Sample content from this topic", - "DetailsPanel.sizeHeading": "Channel size", - "DetailsPanel.sizeText": "{text} ({size})", - "DetailsPanel.subtitlesHeading": "Captions and subtitles", - "DetailsPanel.tagsHeading": "Common tags", - "DetailsPanel.tokenHeading": "Channel token", - "DetailsPanel.unpublishedText": "Unpublished", + "DeleteChannelModal.cancel": "Cancel", + "DeleteChannelModal.channelDeletedSnackbar": "Channel deleted", + "DeleteChannelModal.deleteChannel": "Delete channel", + "DeleteChannelModal.deletePrompt": "This channel will be permanently deleted. This cannot be undone.", + "DeleteChannelModal.deleteTitle": "Delete this channel", "DetailsTabView.aggregatorLabel": "Aggregator", "DetailsTabView.aggregatorToolTip": "Website or org hosting the content collection but not necessarily the creator or copyright holder", "DetailsTabView.assessmentOptionsLabel": "Assessment options", @@ -1008,8 +1110,6 @@ "ForgotPassword.forgotPasswordPrompt": "Please enter your email address to receive instructions for resetting your password", "ForgotPassword.forgotPasswordTitle": "Reset your password", "ForgotPassword.submitButton": "Submit", - "FormulasMenu.btnLabelInsert": "Insert", - "FormulasMenu.formulasMenuTitle": "Special characters", "FormulasStrings.addition": "Addition", "FormulasStrings.advancedCategory": "Advanced", "FormulasStrings.alpha": "alpha", @@ -1204,16 +1304,6 @@ "HintsEditor.newHintBtnLabel": "New hint", "HintsEditor.noHintsPlaceholder": "Question has no hints", "ImageOnlyThumbnail.thumbnail": "{title} thumbnail", - "ImagesMenu.acceptsText": "Supported file types: {acceptedFormats}", - "ImagesMenu.altTextHint": "The image description is necessary to enable visually impaired learners to answer questions, and it also displays when the image fails to load", - "ImagesMenu.altTextLabel": "Image description", - "ImagesMenu.btnLabelCancel": "Cancel", - "ImagesMenu.btnLabelInsert": "Insert", - "ImagesMenu.currentImageDefaultText": "Current image", - "ImagesMenu.defaultDropText": "Drag and drop an image here, or upload manually", - "ImagesMenu.imageHeader": "Upload image", - "ImagesMenu.selectFile": "Select file", - "ImagesMenu.selectFileButton": "Select file", "ImportFromChannelsModal.addButton": "Add", "ImportFromChannelsModal.addedText": "Added", "ImportFromChannelsModal.importAction": "Import", @@ -1223,6 +1313,7 @@ "ImportFromChannelsModal.reviewAction": "Review", "ImportFromChannelsModal.reviewTitle": "Resource selection", "InfoModal.close": "Close", + "InfoModal.open": "More information about licenses", "InheritAncestorMetadataModal.applyResourceDetailsTitle": "Apply details from the folder '{folder}'", "InheritAncestorMetadataModal.cancelAction": "Cancel", "InheritAncestorMetadataModal.categories": "Categories: {categories}", @@ -1255,17 +1346,48 @@ "MainNavigationDrawer.helpLink": "Help and support", "MainNavigationDrawer.logoutLink": "Sign out", "MainNavigationDrawer.settingsLink": "Settings", - "MarkdownEditor.bold": "Bold (Ctrl+B)", - "MarkdownEditor.formulas": "Insert formula (Ctrl+F)", - "MarkdownEditor.image": "Insert image (Ctrl+P)", - "MarkdownEditor.italic": "Italic (Ctrl+I)", - "MarkdownEditor.minimize": "Minimize (Ctrl+M)", - "MarkdownImageField.editImageOption": "Edit", - "MarkdownImageField.removeImageOption": "Remove", - "MarkdownImageField.resizeImageOption": "Resize", "MasteryCriteriaGoal.labelText": "Goal", "MasteryCriteriaMofNFields.mHint": "Correct answers needed", "MasteryCriteriaMofNFields.nHint": "Recent answers", + "MathLiveA11yStrings.accented": "accented", + "MathLiveA11yStrings.array": "array", + "MathLiveA11yStrings.box": "box", + "MathLiveA11yStrings.chemicalFormula": "chemical formula", + "MathLiveA11yStrings.crossOut": "cross out", + "MathLiveA11yStrings.deleted": "deleted: ", + "MathLiveA11yStrings.delimiter": "delimiter", + "MathLiveA11yStrings.denominator": "denominator", + "MathLiveA11yStrings.endOf": "{spokenText}; end of {relationName}", + "MathLiveA11yStrings.endOfMathfield": "{spokenText}; end of mathfield", + "MathLiveA11yStrings.error": "error", + "MathLiveA11yStrings.extensibleSymbol": "extensible symbol", + "MathLiveA11yStrings.first": "first", + "MathLiveA11yStrings.fraction": "fraction", + "MathLiveA11yStrings.group": "group", + "MathLiveA11yStrings.index": "index", + "MathLiveA11yStrings.latex": "LaTeX", + "MathLiveA11yStrings.line": "line", + "MathLiveA11yStrings.mathField": "math field", + "MathLiveA11yStrings.mathfield": "math field", + "MathLiveA11yStrings.numerator": "numerator", + "MathLiveA11yStrings.operator": "operator", + "MathLiveA11yStrings.outOf": "out of {relationName};", + "MathLiveA11yStrings.overUnder": "over-under", + "MathLiveA11yStrings.parent": "parent", + "MathLiveA11yStrings.placeholder": "placeholder", + "MathLiveA11yStrings.prompt": "prompt", + "MathLiveA11yStrings.radicand": "radicand", + "MathLiveA11yStrings.rule": "rule", + "MathLiveA11yStrings.selected": "selected: ", + "MathLiveA11yStrings.space": "space", + "MathLiveA11yStrings.spacing": "spacing", + "MathLiveA11yStrings.squareRoot": "square root", + "MathLiveA11yStrings.startOf": "start of {relationName}: ", + "MathLiveA11yStrings.subscript": "subscript", + "MathLiveA11yStrings.subscriptSuperscript": "subscript-superscript", + "MathLiveA11yStrings.superscript": "superscript", + "MathLiveA11yStrings.superscriptAndSubscript": "superscript and subscript", + "MathLiveA11yStrings.text": "text", "MessageLayout.backToLogin": "Continue to sign-in page", "MoveModal.addTopic": "Add new folder", "MoveModal.cancel": "Cancel", @@ -1296,7 +1418,7 @@ "PasswordField.passwordLabel": "Password", "PasswordInstructionsSent.passwordInstructionsHeader": "Instructions sent. Thank you!", "PasswordInstructionsSent.passwordInstructionsText": "If there is already an account with the email address provided, you should receive the instructions shortly. If you don't see an email from us, please check your spam folder.", - "PermissionsError.goToHomePageAction": "Go to home page", + "PermissionsError.backToHome": "Back to home", "PermissionsError.permissionDeniedHeader": "Did you forget to sign in?", "PoliciesModal.checkboxText": "I have agreed to the above terms", "PoliciesModal.closeButton": "Close", @@ -1306,6 +1428,7 @@ "PrivacyPolicyModal.updatedPrivacyHeader": "Updated privacy policy", "ProgressBar.progressText": "{percent}%", "ProgressModal.defaultErrorText": "Last attempt to publish failed", + "ProgressModal.draftHeader": "Saving draft...", "ProgressModal.lastPublished": "Published {last_published}", "ProgressModal.publishHeader": "Publishing channel", "ProgressModal.syncError": "Last attempt to sync failed", @@ -1346,6 +1469,19 @@ "RelatedResourcesTab.showPreviewBtnLabel": "Show me", "RelatedResourcesTab.tooManyNextStepsWarning": "Limit the number of next steps to create a more guided learning experience", "RelatedResourcesTab.tooManyPreviousStepsWarning": "Limit the number of previous steps to create a more guided learning experience", + "RemoveChannelFromListModal.cancel": "Cancel", + "RemoveChannelFromListModal.channelRemovedSnackbar": "Channel removed", + "RemoveChannelFromListModal.removeBtn": "Remove", + "RemoveChannelFromListModal.removePrompt": "You have view-only access to this channel. Confirm that you want to remove it from your list of channels.", + "RemoveChannelFromListModal.removeTitle": "Remove from channel list", + "RemoveChannelModal.cancel": "Cancel", + "RemoveChannelModal.deleteChannel": "Delete channel", + "RemoveChannelModal.deleteChannelWithCLWarning": "This channel has been shared with the Community Library. Deleting it here will not remove it from the Community Library — it may still be approved or remain available there.", + "RemoveChannelModal.deletePrompt": "This channel will be permanently deleted. This cannot be undone.", + "RemoveChannelModal.deleteTitle": "Delete this channel", + "RemoveChannelModal.removeBtn": "Remove", + "RemoveChannelModal.removePrompt": "You have view-only access to this channel. Confirm that you want to remove it from your list of channels.", + "RemoveChannelModal.removeTitle": "Remove from channel list", "ReportErrorModal.closeAction": "Close", "ReportErrorModal.emailDescription": "Contact the support team with your error details and we’ll do our best to help.", "ReportErrorModal.emailPrompt": "Send an email to the developers", @@ -1533,8 +1669,11 @@ "SearchRecommendationsStrings.tooAdvancedForLearnersLabel": "Too advanced for the knowledge level of learners I'm looking for", "SearchRecommendationsStrings.tooBasicForLearnersLabel": "Too basic for the knowledge level of learners I'm looking for", "SearchRecommendationsStrings.tryAgainLink": "Try again", + "SearchRecommendationsStrings.trySearchRecommendationsHeader": "Try our new 'Recommendations' feature!", + "SearchRecommendationsStrings.trySearchRecommendationsText": "Based on the title and description of the folder you are working on, we'll suggest relevant resources from the Kolibri Library. Choose 'Import from channels' in any folder of your channels to see the recommendations.", "SearchRecommendationsStrings.undoAction": "Undo", "SearchRecommendationsStrings.viewMoreLink": "View more", + "SearchRecommendationsStrings.viewRecommendationsButton": "View recommendations", "SearchResultsList.failedToLoad": "Failed to load search results", "SearchResultsList.resultsPerPageLabel": "Results per page", "SearchResultsList.saveSearchAction": "Save search", @@ -1588,10 +1727,121 @@ "Storage.spaceUsedOfMax": "{qty} of {max}", "Storage.storagePercentageUsed": "{qty}% storage used", "Storage.videoFiles": "Video files should be compressed to maximize storage space and ensure smooth offline distribution and playback. Once compressed, the total storage required may be less than what you originally estimated.", + "StudioChannelCard.channelLanguageNotSetIndicator": "No language set", + "StudioChannelCard.details": "Details", + "StudioChannelCard.lastPublished": "Published {last_published}", + "StudioChannelCard.lastUpdated": "Updated {updated}", + "StudioChannelCard.multipleCountries": "Multiple countries", + "StudioChannelCard.resourceCount": "{count, plural,\n =1 {# resource}\n other {# resources}}", + "StudioChannelCard.selectChannel": "Select {name}", + "StudioChannelCard.unpublishedText": "Unpublished", + "StudioChannelsPage.invitations": "You have {count, plural,\n =1 {# invitation}\n other {# invitations}}", + "StudioChannelsPage.noChannelsFound": "No channels found", + "StudioCollectionsTable.aboutChannelSets": "About collections", + "StudioCollectionsTable.aboutChannelSetsLink": "Learn more about collections", + "StudioCollectionsTable.addChannelSetTitle": "New collection", + "StudioCollectionsTable.cancel": "Cancel", + "StudioCollectionsTable.cancelButtonLabel": "Close", + "StudioCollectionsTable.channelNumber": "Number of channels", + "StudioCollectionsTable.channelSetsDescriptionText": "A collection contains multiple Kolibri Studio channels that can be imported at one time to Kolibri with a single collection token.", + "StudioCollectionsTable.channelSetsDisclaimer": "You will need Kolibri version 0.12.0 or higher to import channel collections", + "StudioCollectionsTable.channelSetsInstructionsText": "You can make a collection by selecting the channels you want to be imported together.", + "StudioCollectionsTable.collectionDeleted": "Collection deleted", + "StudioCollectionsTable.copiedTokenId": "Token copied", + "StudioCollectionsTable.copyFailed": "Copy failed", + "StudioCollectionsTable.copyToken": "Copy token", + "StudioCollectionsTable.delete": "Delete collection", + "StudioCollectionsTable.deleteChannelSetText": "Are you sure you want to delete this collection?", + "StudioCollectionsTable.deleteChannelSetTitle": "Delete collection", + "StudioCollectionsTable.deleteError": "Error deleting collection", + "StudioCollectionsTable.edit": "Edit collection", + "StudioCollectionsTable.noChannelSetsFound": "You can package together multiple channels to create a collection. The entire collection can then be imported to Kolibri at once by using a collection token.", + "StudioCollectionsTable.options": "Options", + "StudioCollectionsTable.pageTitle": "Collections", + "StudioCollectionsTable.saving": "Saving", + "StudioCollectionsTable.tableCaption": "List of collections", + "StudioCollectionsTable.title": "Collection name", + "StudioCollectionsTable.token": "Token ID", + "StudioCopyToken.copiedTokenId": "Token copied", + "StudioCopyToken.copyFailed": "Copy failed", + "StudioCopyToken.token": "Token", + "StudioCopyToken.tooltipText": "Copy token to import channel into Kolibri", + "StudioDetailsPanel.AVERAGE": "Average", + "StudioDetailsPanel.LARGE": "Large", + "StudioDetailsPanel.SMALL": "Small", + "StudioDetailsPanel.VERY_LARGE": "Very large", + "StudioDetailsPanel.VERY_SMALL": "Very small", + "StudioDetailsPanel.aggregatorToolTip": "Website or organization hosting the content collection but not necessarily the creator or copyright holder", + "StudioDetailsPanel.aggregatorsLabel": "Aggregators", + "StudioDetailsPanel.assessmentsIncludedText": "Assessments", + "StudioDetailsPanel.authorToolTip": "Person or organization who created this content", + "StudioDetailsPanel.authorsLabel": "Authors", + "StudioDetailsPanel.categoriesHeading": "Categories", + "StudioDetailsPanel.coachDescription": "Resources for coaches are only visible to coaches in Kolibri", + "StudioDetailsPanel.coachHeading": "Resources for coaches", + "StudioDetailsPanel.containsContentHeading": "Contains content from", + "StudioDetailsPanel.containsHeading": "Contains", + "StudioDetailsPanel.copyrightHoldersLabel": "Copyright holders", + "StudioDetailsPanel.creationHeading": "Created on", + "StudioDetailsPanel.currentVersionHeading": "Published version", + "StudioDetailsPanel.languagesHeading": "Languages", + "StudioDetailsPanel.levelsHeading": "Levels", + "StudioDetailsPanel.licensesLabel": "Licenses", + "StudioDetailsPanel.primaryLanguageHeading": "Primary language", + "StudioDetailsPanel.providerToolTip": "Organization that commissioned or is distributing the content", + "StudioDetailsPanel.providersLabel": "Providers", + "StudioDetailsPanel.publishedHeading": "Published on", + "StudioDetailsPanel.resourceHeading": "Total resources", + "StudioDetailsPanel.sampleFromChannelHeading": "Sample content from this channel", + "StudioDetailsPanel.sizeHeading": "Channel size", + "StudioDetailsPanel.sizeText": "{text} ({size})", + "StudioDetailsPanel.subtitlesHeading": "Captions and subtitles", + "StudioDetailsPanel.tagsHeading": "Common tags", + "StudioDetailsPanel.tokenHeading": "Channel token", + "StudioDetailsPanel.unpublishedText": "Unpublished", + "StudioImmersiveModal.close": "Close", + "StudioMessageLayout.backToLogin": "Continue to sign-in page", + "StudioMyChannels.copyToken": "Copy channel token", + "StudioMyChannels.deleteChannel": "Delete channel", + "StudioMyChannels.editChannel": "Edit channel details", + "StudioMyChannels.goToWebsite": "Go to source website", + "StudioMyChannels.moreOptions": "More options", + "StudioMyChannels.newChannel": "New channel", + "StudioMyChannels.title": "My channels", + "StudioMyChannels.viewContent": "View channel on Kolibri", "StudioOfflineAlert.offlineText": "You seem to be offline. Your changes will be saved once your connection is back.", "StudioOfflineAlert.onlineText": "You are back online.", + "StudioStarredChannels.copyToken": "Copy channel token", + "StudioStarredChannels.deleteChannel": "Delete channel", + "StudioStarredChannels.editChannel": "Edit channel details", + "StudioStarredChannels.goToWebsite": "Go to source website", + "StudioStarredChannels.moreOptions": "More options", + "StudioStarredChannels.removeChannel": "Remove channel", + "StudioStarredChannels.title": "Starred channels", + "StudioStarredChannels.viewContent": "View channel on Kolibri", "StudioTree.missingTitle": "Missing title", "StudioTree.optionsTooltip": "Options", + "StudioViewOnlyChannels.copyToken": "Copy channel token", + "StudioViewOnlyChannels.goToWebsite": "Go to source website", + "StudioViewOnlyChannels.moreOptions": "More options", + "StudioViewOnlyChannels.removeChannel": "Remove channel", + "StudioViewOnlyChannels.title": "View-only channels", + "StudioViewOnlyChannels.viewContent": "View channel on Kolibri", + "SubscriptionCard.annualPrice": "${price, number}/year", + "SubscriptionCard.cancelNotice": "Your subscription will expire on {date, date, medium}. Storage will be removed after that.", + "SubscriptionCard.dismiss": "Dismiss", + "SubscriptionCard.genericError": "There was a problem connecting to the payment provider. Please try again.", + "SubscriptionCard.instantUpgrade": "Upgrade storage now", + "SubscriptionCard.manageSubscription": "Manage subscription", + "SubscriptionCard.renewalNotice": "Your subscription will automatically renew on {date, date, medium}.", + "SubscriptionCard.storageAmount": "Storage (GB)", + "SubscriptionCard.storageIncluded": "{size} included in your subscription", + "SubscriptionCard.storageRange": "Enter a value between 1 and 50", + "SubscriptionCard.subscriptionActive": "Storage subscription active", + "SubscriptionCard.subscriptionCanceling": "Subscription cancelled", + "SubscriptionCard.upgradeDescription": "Purchase additional storage at $15/GB per year.", + "SubscriptionCard.upgradeNow": "Upgrade now", + "SubscriptionCard.upgradeSuccess": "Storage increased to {size}", "SubtitlesList.acceptedFormatsTooltip": "Supported formats: {extensions}", "SubtitlesList.addSubtitleText": "Add captions", "SubtitlesList.subtitlesHeader": "Captions and subtitles", @@ -1715,10 +1965,15 @@ "TextArea.fieldRequiredMessage": "Field is required", "TextField.fieldRequiredMessage": "Field is required", "Thumbnail.thumbnail": "{title} thumbnail", + "ThumbnailGenerator.closeButtonLabel": "OK", "ThumbnailGenerator.generatedDefaultFilename": "Generated thumbnail", "ThumbnailGenerator.thumbnailGenerationFailedHeader": "Unable to generate thumbnail", "ThumbnailGenerator.thumbnailGenerationFailedText": "There was a problem generating a thumbnail", + "TipTapEditorStrings.TipTapEditorLabel": "text editor - Press Enter to start editing", + "TipTapEditorStrings.TipTapViewerLabel": "text editor content", "TipTapEditorStrings.addLink": "Add link", + "TipTapEditorStrings.alignLeft": "Align left", + "TipTapEditorStrings.alignRight": "Align right", "TipTapEditorStrings.altTextDescription": "Alt text is necessary to enable visually impaired learners to answer questions, and it also displays when the image fails to load", "TipTapEditorStrings.altTextLabel": "Alt text (Optional)", "TipTapEditorStrings.altTextPlaceholder": "Describe your image...", @@ -1726,17 +1981,23 @@ "TipTapEditorStrings.bulletList": "Bullet list", "TipTapEditorStrings.cancel": "Cancel", "TipTapEditorStrings.cancelLoading": "Cancel loading", + "TipTapEditorStrings.clearFormatting": "Clear formatting", "TipTapEditorStrings.clipboardAccessFailed": "Clipboard access failed. Try copying again.", "TipTapEditorStrings.close": "Close", "TipTapEditorStrings.closeModal": "Close modal", "TipTapEditorStrings.codeBlock": "Code block", + "TipTapEditorStrings.collapseFormattingBar": "Collapse formatting bar", "TipTapEditorStrings.copy": "Copy", "TipTapEditorStrings.copyAndPasteActions": "Copy and paste actions", "TipTapEditorStrings.copyLink": "Copy link", + "TipTapEditorStrings.decreaseFormatSize": "Decrease format size", "TipTapEditorStrings.defaultImageName": "Image", "TipTapEditorStrings.edit": "Edit", "TipTapEditorStrings.editImage": "Edit image", "TipTapEditorStrings.editLink": "Edit link", + "TipTapEditorStrings.editorControls": "Editor controls", + "TipTapEditorStrings.errorUploadingImage": "Error uploading image", + "TipTapEditorStrings.expandFormattingBar": "Expand formatting bar", "TipTapEditorStrings.failedToProcessImage": "Failed to process the image file.", "TipTapEditorStrings.fileSizeUnit": "MB.", "TipTapEditorStrings.fileTooLarge": "File is too large. Maximum size is ", @@ -1745,13 +2006,18 @@ "TipTapEditorStrings.formatHeader3": "Header 3", "TipTapEditorStrings.formatNormal": "Normal", "TipTapEditorStrings.formatOptions": "Format options", + "TipTapEditorStrings.formatSize": "Format size", "TipTapEditorStrings.formatSmall": "Small", "TipTapEditorStrings.formulasMenuTitle": "Special Characters", "TipTapEditorStrings.goToLink": "Go to link", "TipTapEditorStrings.historyActions": "History actions", "TipTapEditorStrings.imageDropZoneText": "Drag and drop an image here or upload manually", "TipTapEditorStrings.imagePreview": "Image preview", + "TipTapEditorStrings.increaseFormatSize": "Increase format size", "TipTapEditorStrings.insert": "Insert", + "TipTapEditorStrings.insertContent": "Insert content", + "TipTapEditorStrings.insertContentMenu": "Insert content menu", + "TipTapEditorStrings.insertContentOption": "Insert content option", "TipTapEditorStrings.insertImage": "Insert image", "TipTapEditorStrings.insertLink": "Insert link", "TipTapEditorStrings.insertTools": "Insert tools", @@ -1760,8 +2026,11 @@ "TipTapEditorStrings.link": "Link", "TipTapEditorStrings.linkActions": "Link actions", "TipTapEditorStrings.listFormatting": "List formatting", + "TipTapEditorStrings.loadingFormulas": "Loading math editor", "TipTapEditorStrings.mathFormula": "Math formula", + "TipTapEditorStrings.moreButtonText": "More", "TipTapEditorStrings.multipleFilesDroppedWarning": "Multiple files were dropped. Only the first file has been selected.", + "TipTapEditorStrings.noEnoughStorageSpace": "Not enough storage space available. File size exceeds remaining storage.", "TipTapEditorStrings.noFileProvided": "No file provided.", "TipTapEditorStrings.numberedList": "Numbered list", "TipTapEditorStrings.opensInNewTab": "(opens in new tab)", @@ -1782,7 +2051,7 @@ "TipTapEditorStrings.strikethrough": "Strikethrough", "TipTapEditorStrings.subscript": "Subscript", "TipTapEditorStrings.superscript": "Superscript", - "TipTapEditorStrings.supportedFileTypes": "Supported file types: png, jpg, jpeg, svg, webp", + "TipTapEditorStrings.supportedFileTypes": "Supported file types: { extensions }", "TipTapEditorStrings.text": "Text", "TipTapEditorStrings.textFormatOptions": "Text format options", "TipTapEditorStrings.textFormattingOptions": "Text formatting options", @@ -1814,25 +2083,26 @@ "TreeView.showSidebar": "Show sidebar", "TreeView.updatedResourcesReadyForReview": "Updated resources are ready for review", "TreeViewBase.apiGenerated": "Generated by API", - "TreeViewBase.cancel": "Cancel", "TreeViewBase.channelDeletedSnackbar": "Channel deleted", "TreeViewBase.channelDetails": "View channel details", "TreeViewBase.deleteChannel": "Delete channel", - "TreeViewBase.deleteChannelButton": "Delete channel", - "TreeViewBase.deletePrompt": "This channel will be permanently deleted. This cannot be undone.", - "TreeViewBase.deleteTitle": "Delete this channel", "TreeViewBase.editChannel": "Edit channel details", "TreeViewBase.emptyChannelTooltip": "You cannot publish an empty channel", "TreeViewBase.getToken": "Get token", "TreeViewBase.incompleteDescendantsText": "{count, number, integer} {count, plural, one {resource is incomplete and cannot be published} other {resources are incomplete and cannot be published}}", + "TreeViewBase.inviteCollaborators": "Invite collaborators", "TreeViewBase.noChangesText": "No changes found in channel", "TreeViewBase.noLanguageSetError": "Channel language is required", "TreeViewBase.openTrash": "Open trash", "TreeViewBase.publishButton": "Publish", "TreeViewBase.publishButtonTitle": "Make this channel available for import into Kolibri", "TreeViewBase.shareChannel": "Share channel", + "TreeViewBase.shareMenuButton": "Share", + "TreeViewBase.shareToken": "Share token", + "TreeViewBase.submitToCommunityLibrary": "Submit to Community Library", "TreeViewBase.syncChannel": "Sync resources", "TreeViewBase.viewOnly": "View-only", + "Uploader.closeButtonLabel": "OK", "Uploader.listDelimiter": ", ", "Uploader.maxFileSizeText": "{count, plural,\n =1 {# file will not be uploaded.}\n other {# files will not be uploaded.}} File size must be under {size}", "Uploader.noStorageHeader": "Not enough space", @@ -1906,4 +2176,4 @@ "sharedVue.masteryModelRequired": "Mastery is required", "sharedVue.shortActivityLteThirty": "Value must be equal or less than 30", "sharedVue.titleRequired": "Title is required" -} +} \ No newline at end of file diff --git a/contentcuration/locale/es_ES/LC_MESSAGES/contentcuration-messages.json b/contentcuration/locale/es_ES/LC_MESSAGES/contentcuration-messages.json index 6edc18959e..a48f7e8d2e 100644 --- a/contentcuration/locale/es_ES/LC_MESSAGES/contentcuration-messages.json +++ b/contentcuration/locale/es_ES/LC_MESSAGES/contentcuration-messages.json @@ -20,7 +20,7 @@ "Account.exportFailed": "Los datos no se pudieron exportar. Por favor, inténtelo de nuevo.", "Account.exportStartedHeader": "Exportación de datos iniciada", "Account.fullNameLabel": "Nombre completo", - "Account.handleChannelsBeforeAccount": "No se pueden eliminar las cuentas que tienen canales asociados. Primero hay que eliminar estos canales manualmente para poder eliminar la cuenta.", + "Account.handleChannelsBeforeAccount": "No se pueden eliminar las cuentas que tienen canales. Primero hay que eliminar estos canales manualmente para poder eliminar la cuenta.", "Account.passwordLabel": "Contraseña", "Account.unableToDeleteAdminAccount": "No se puede eliminar una cuenta de administrador", "Account.usernameLabel": "Nombre de usuario", @@ -53,9 +53,9 @@ "ActivityDuration.minutesRequired": "Minutos", "ActivityDuration.notOptionalLabel": "Tiempo requerido para que el recurso se marque como completado. Este valor no se mostrará a los estudiantes.", "ActivityDuration.optionalLabel": "(Opcional) Tiempo requerido para que el recurso se marque como completado. Este valor no se mostrará a los estudiantes.", - "AddNextStepsPage.addedNextStepSnackbar": "Se ha añadido el paso siguiente", + "AddNextStepsPage.addedNextStepSnackbar": "Paso siguiente añadido", "AddNextStepsPage.toolbarTitle": "Añadir paso siguiente", - "AddPreviousStepsPage.addedPreviousStepSnackbar": "Se ha añadido el paso anterior", + "AddPreviousStepsPage.addedPreviousStepSnackbar": "Paso anterior añadido", "AddPreviousStepsPage.toolbarTitle": "Añadir paso anterior", "AddRelatedResourcesModal.addStepBtnLabel": "Añadir", "AddRelatedResourcesModal.cancelBtnLabel": "Cancelar", @@ -67,8 +67,6 @@ "AdministrationAppError.unauthorizedDetails": "Necesita tener permisos de administrador en Studio para ver esta página", "AdministrationIndex.channelsLabel": "Canales", "AdministrationIndex.usersLabel": "Usuarios", - "Alert.closeButtonLabel": "Aceptar", - "Alert.dontShowAgain": "No volver a mostrar este mensaje", "AnswersEditor.answersLabel": "Respuestas", "AnswersEditor.newAnswerBtnLabel": "Nueva respuesta", "AnswersEditor.noAnswersPlaceholder": "La pregunta no tiene opciones de respuesta", @@ -96,7 +94,7 @@ "AssessmentItemEditor.questionTypeLabel": "Tipo de respuesta", "AssessmentItemPreview.answersLabel": "Respuestas", "AssessmentItemPreview.hintsToggleLabelHide": "Ocultar pistas", - "AssessmentItemPreview.hintsToggleLabelShow": "Mostrar {hintsCount} {hintsCount, plural, one {pista} other {pistas}}", + "AssessmentItemPreview.hintsToggleLabelShow": "Muestra {hintsCount} {hintsCount, plural, one {pista} other {pistas}}", "AssessmentItemPreview.noAnswersPlaceholder": "La pregunta no tiene opciones de respuesta", "AssessmentItemToolbar.toolbarLabelAddAbove": "Añadir {itemLabel} arriba", "AssessmentItemToolbar.toolbarLabelAddBelow": "Añadir {itemLabel} abajo", @@ -121,7 +119,7 @@ "BytesForHumansStrings.fileSizeInTerabytes": "{n, number, integer} TB", "CatalogFAQ.KolibriAnswer": "Kolibri es una plataforma de código abierto diseñada para las comunidades con bajos recursos, enfocada en:", "CatalogFAQ.KolibriAnswerItem1": "Superar las barreras infraestructurales que impiden acceso equitativo a una educación de calidad para los estudiantes en contextos de bajos recursos y baja conectividad", - "CatalogFAQ.KolibriAnswerItem2": "Incrementar la disponibilidad de materiales de aprendizaje abiertos adecuados para una variedad de situaciones, planes y metas de aprendizaje", + "CatalogFAQ.KolibriAnswerItem2": "Incrementar la disponibilidad de materiales de aprendizaje abiertos adecuados para una variedad de currículos, metas de aprendizaje y situaciones", "CatalogFAQ.KolibriAnswerItem3": "Fomentar una pedagogía innovadora y resultados de aprendizaje efectivos", "CatalogFAQ.KolibriQuestion": "¿Qué es Kolibri?", "CatalogFAQ.aboutHeader": "¡Bienvenida al Catálogo de la Biblioteca de Contenidos de Kolibri! ", @@ -166,8 +164,8 @@ "CatalogFAQ.selectionQuestion": "¿De qué manera Learning Equality determina qué es lo que se incluye en esta biblioteca?", "CatalogFAQ.usingContentAnswer": "¡Genial! Todos estos recursos han sido especialmente ensamblados para su uso en Kolibri, nuestra plataforma de código abierto para aprender sin conexión, así que por favor revise cómo empezar con Kolibri primero, luego siga las instrucciones para importar materiales.", "CatalogFAQ.usingContentQuestion": "He encontrado algo que me interesa y me gustaría empezar a usar. ¿Qué debo hacer?", - "CatalogFAQ.usingKolibriAnswerP1": "Aprender más sobre el uso de Kolibri:", - "CatalogFAQ.usingKolibriAnswerP2": "Consultar la documentación de Kolibri para obtener más información.", + "CatalogFAQ.usingKolibriAnswerP1": "Puedes aprender más sobre el uso de Kolibri haciendo cualquiera de los siguientes:", + "CatalogFAQ.usingKolibriAnswerP2": "Le invitamos a consultar la documentación de Kolibri para obtener más información.", "CatalogFAQ.usingKolibriItem1": "Visitar el sitio web de Learning Equality", "CatalogFAQ.usingKolibriItem2": "Ver una demostración de la plataforma", "CatalogFAQ.usingKolibriItem3": "Descargar el programa", @@ -184,26 +182,31 @@ "CatalogFilterBar.keywords": "\"{text}\"", "CatalogFilterBar.starred": "Destacados", "CatalogFilterBar.subtitles": "Subtítulos", - "CatalogFilters.coachDescription": "Los recursos para tutores son visibles solo a los tutores en Kolibri", - "CatalogFilters.coachLabel": "Recursos para tutores", - "CatalogFilters.copyright": "© {year} Learning Equality", - "CatalogFilters.formatLabel": "Formatos", - "CatalogFilters.frequentlyAskedQuestionsLink": "Preguntas más frecuentes", - "CatalogFilters.includesLabel": "Mostrar solo canales con", - "CatalogFilters.licenseLabel": "Licencias", - "CatalogFilters.searchLabel": "Palabras clave", - "CatalogFilters.searchText": "Buscar", - "CatalogFilters.starredLabel": "Destacados", - "CatalogFilters.subtitlesLabel": "Subtítulos", + "CatalogFilterPanelContent.coachDescription": "Los recursos para tutores son visibles solo a los tutores en Kolibri", + "CatalogFilterPanelContent.coachLabel": "Recursos para tutores", + "CatalogFilterPanelContent.copyright": "© {year} Learning Equality", + "CatalogFilterPanelContent.formatLabel": "Formatos", + "CatalogFilterPanelContent.frequentlyAskedQuestionsLink": "Preguntas más frecuentes", + "CatalogFilterPanelContent.includesLabel": "Mostrar solo canales con", + "CatalogFilterPanelContent.licenseLabel": "Licencias", + "CatalogFilterPanelContent.searchLabel": "Palabras clave", + "CatalogFilterPanelContent.starredLabel": "Destacados", + "CatalogFilterPanelContent.subtitlesLabel": "Subtítulos", + "CatalogFilters.filterLabel": "Filtrar", "CatalogList.cancelButton": "Cancelar", "CatalogList.channelSelectionCount": "{count, plural,\n =1 {# canal seleccionado}\n other {# canales seleccionados}}", + "CatalogList.copyToken": "Copiar el token del canal", "CatalogList.downloadButton": "Descargar", "CatalogList.downloadCSV": "Descargar CSV", "CatalogList.downloadPDF": "Descargar PDF", "CatalogList.downloadingMessage": "Descarga iniciada", + "CatalogList.goToWebsite": "Ir a la página web de origen", + "CatalogList.moreOptions": "Más opciones", "CatalogList.resultsText": "{count, plural,\n =1 {# resultado encontrado}\n other {# resultados encontrados}}", "CatalogList.selectAll": "Seleccionar todo", "CatalogList.selectChannels": "Descargar un resumen de los canales seleccionados", + "CatalogList.title": "Biblioteca de Kolibri", + "CatalogList.viewContent": "Ver canal en Kolibri", "CategoryOptions.noCategoryFoundText": "Categoría no encontrada", "ChangePasswordForm.cancelAction": "Cancelar", "ChangePasswordForm.changePasswordHeader": "Cambiar contraseña", @@ -222,11 +225,8 @@ "ChannelCatalogFrontPage.exported": "Exportado", "ChannelCatalogFrontPage.formatsHeading": "Formatos", "ChannelCatalogFrontPage.languagesHeading": "Idiomas", - "ChannelCatalogFrontPage.numberOfChannels": "{ num } Canales", + "ChannelCatalogFrontPage.numberOfChannels": "{ num } canales", "ChannelCatalogFrontPage.subtitlesIncludedText": "Subtítulos", - "ChannelDeletedError.backToHomeAction": "Volver al inicio", - "ChannelDeletedError.channelDeletedDetails": "Este canal no existe o puede haber sido eliminado. Por favor, contacte con content@learningequality.org si cree que esto es un error.", - "ChannelDeletedError.channelDeletedHeader": "Canal no encontrado", "ChannelDetailsModal.downloadButton": "Descargar resumen del canal", "ChannelDetailsModal.downloadCSV": "Descargar CSV", "ChannelDetailsModal.downloadPDF": "Descargar PDF", @@ -237,7 +237,7 @@ "ChannelExportStrings.containsContentFrom": "Contiene contenidos de", "ChannelExportStrings.copyrightHolders": "Titulares de derechos de autor", "ChannelExportStrings.description": "Descripción", - "ChannelExportStrings.downloadFilename": "{year}_{month}_Biblioteca_Contenido_Kolibri", + "ChannelExportStrings.downloadFilename": "{year}_{month}_Libreria_Contenido_Kolibri", "ChannelExportStrings.id": "ID del canal", "ChannelExportStrings.language": "Idioma", "ChannelExportStrings.languages": "Idiomas incluidos", @@ -263,23 +263,17 @@ "ChannelInvitation.editText": "{sender} le ha invitado a editar {channel}", "ChannelInvitation.goToChannelSnackbarAction": "Ir al canal", "ChannelInvitation.viewText": "{sender} le ha invitado a ver {channel}", - "ChannelItem.cancel": "Cancelar", "ChannelItem.channelDeletedSnackbar": "Canal eliminado", "ChannelItem.channelLanguageNotSetIndicator": "Idioma no establecido", "ChannelItem.channelRemovedSnackbar": "Canal eliminado", "ChannelItem.copyToken": "Copiar el token del canal", "ChannelItem.deleteChannel": "Eliminar canal", - "ChannelItem.deletePrompt": "Este canal se eliminará permanentemente. Esto no se puede deshacer.", - "ChannelItem.deleteTitle": "Eliminar este canal", "ChannelItem.details": "Detalles", "ChannelItem.editChannel": "Editar detalles del canal", "ChannelItem.goToWebsite": "Ir a la página web de origen", "ChannelItem.lastPublished": "Publicado {last_published}", "ChannelItem.lastUpdated": "Actualizado {updated}", - "ChannelItem.removeBtn": "Eliminar", - "ChannelItem.removeChannel": "Eliminar de la lista de canales", - "ChannelItem.removePrompt": "Tiene permisos de solo lectura para este canal. Confirmar que quiere eliminarlo de su lista de canales.", - "ChannelItem.removeTitle": "Eliminar de la lista de canales", + "ChannelItem.removeChannel": "Eliminar canal", "ChannelItem.resourceCount": "{count, plural,\n =1 {# recurso}\n other {# recursos}}", "ChannelItem.unpublishedText": "No publicado", "ChannelItem.versionText": "Versión {version}", @@ -289,10 +283,9 @@ "ChannelList.noChannelsFound": "No se han encontrado canales", "ChannelList.noMatchingChannels": "No hay canales que coincidan", "ChannelListAppError.channelPermissionsErrorDetails": "Inicie sesión o pida al propietario de este canal que le dé permiso para editar o ver", - "ChannelListIndex.catalog": "Biblioteca de contenido", + "ChannelListIndex.catalog": "Biblioteca de Kolibri", "ChannelListIndex.channelSets": "Colecciones", "ChannelListIndex.frequentlyAskedQuestions": "Preguntas más frecuentes", - "ChannelListIndex.invitations": "Tiene {count, plural, one {}\n =1 {# invitación}\n other {# invitaciones}}", "ChannelListIndex.libraryTitle": "Catálogo de la Biblioteca de Contenidos de Kolibri", "ChannelModal.APIText": "Los canales generados automáticamente no son editables.", "ChannelModal.changesSaved": "Cambios guardados", @@ -316,25 +309,6 @@ "ChannelNotFoundError.channelNotFoundHeader": "Canal no encontrado", "ChannelSelectionList.noChannelsFound": "No se han encontrado canales", "ChannelSelectionList.searchText": "Buscar canal", - "ChannelSetItem.cancel": "Cancelar", - "ChannelSetItem.delete": "Eliminar colección", - "ChannelSetItem.deleteChannelSetText": "Por favor, confirme que desea eliminar esta colección.", - "ChannelSetItem.deleteChannelSetTitle": "Eliminar colección", - "ChannelSetItem.edit": "Editar colección", - "ChannelSetItem.options": "Opciones", - "ChannelSetItem.saving": "Guardando", - "ChannelSetList.aboutChannelSets": "Acerca de las colecciones", - "ChannelSetList.aboutChannelSetsLink": "Aprender más sobre las colecciones", - "ChannelSetList.addChannelSetTitle": "Nueva colección", - "ChannelSetList.cancelButtonLabel": "Cerrar", - "ChannelSetList.channelNumber": "Número de canales", - "ChannelSetList.channelSetsDescriptionText": "Una colección contiene múltiples canales de Kolibri Studio que pueden ser importados a Kolibri con solo el token de la colección.", - "ChannelSetList.channelSetsDisclaimer": "Se necesita Kolibri con versión 0.12.0 o superior para importar colecciones de canales", - "ChannelSetList.channelSetsInstructionsText": "Puede hacer una colección seleccionando los canales que desea importar juntos.", - "ChannelSetList.noChannelSetsFound": "Puede juntar varios canales para crear una colección. La colección completa puede ser importada a Kolibri a la vez usando un token de colección.", - "ChannelSetList.options": "Opciones", - "ChannelSetList.title": "Nombre de la colección", - "ChannelSetList.token": "Token ID", "ChannelSetModal.bookmark": "Destacados", "ChannelSetModal.channelAdded": "Canal añadido", "ChannelSetModal.channelCountText": "{channelCount, plural, one {} =0 {No hay canales publicados en tu colección} =1 {# canal} other {# canales}}", @@ -348,7 +322,7 @@ "ChannelSetModal.edit": "Mis canales", "ChannelSetModal.finish": "Finalizar", "ChannelSetModal.public": "Públicos", - "ChannelSetModal.publishedChannelsOnlyText": "Solo los canales publicados están disponibles para la selección", + "ChannelSetModal.publishedChannelsOnlyText": "Sólo los canales publicados están disponibles para la selección", "ChannelSetModal.removeText": "Eliminar", "ChannelSetModal.saveButton": "Guardar y cerrar", "ChannelSetModal.selectChannelsHeader": "Seleccionar canales", @@ -357,7 +331,7 @@ "ChannelSetModal.token": "Token de la colección", "ChannelSetModal.tokenPrompt": "Copiar este token en Kolibri para importar esta colección a su dispositivo.", "ChannelSetModal.unsavedChangesHeader": "Cambios sin guardar", - "ChannelSetModal.unsavedChangesText": "Se perderán los cambios sin guardar. Confirmar que desea salir.", + "ChannelSetModal.unsavedChangesText": "Se perderán los cambios sin guardar. Por favor, confirme que desea salir.", "ChannelSetModal.view": "Solo modo de lectura", "ChannelSharing.alreadyHasAccessError": "El usuario ya tiene acceso a este canal", "ChannelSharing.alreadyInvitedError": "Usuario ya invitado", @@ -369,14 +343,14 @@ "ChannelSharing.invitationSentMessage": "Invitación enviada", "ChannelSharing.inviteButton": "Enviar invitación", "ChannelSharing.inviteSubheading": "Invitar colaboradores", - "ChannelSharing.validEmailMessage": "Ingresar un correo electrónico válido", + "ChannelSharing.validEmailMessage": "Por favor ingrese un correo electrónico válido", "ChannelSharingTable.cancelButton": "Cancelar", "ChannelSharingTable.currentUserText": "{first_name} {last_name} (usted)", "ChannelSharingTable.deleteInvitation": "Eliminar invitación", "ChannelSharingTable.deleteInvitationConfirm": "Eliminar invitación", "ChannelSharingTable.deleteInvitationHeader": "Eliminar invitación", - "ChannelSharingTable.deleteInvitationText": "Confirmar que desea borrar la invitación para {email}", - "ChannelSharingTable.editPermissionsGrantedMessage": "Asignados los permisos para editar", + "ChannelSharingTable.deleteInvitationText": "Por favor, confirme de que desea borrar la invitación para {email}", + "ChannelSharingTable.editPermissionsGrantedMessage": "Permisos para editar concedidos", "ChannelSharingTable.editorsSubheading": "{count, plural,\n =1 {# usuario que puede editar}\n other {# usuarios que pueden editar}}", "ChannelSharingTable.guestText": "Invitado", "ChannelSharingTable.invitationDeletedMessage": "Invitación eliminada", @@ -386,13 +360,13 @@ "ChannelSharingTable.makeEditor": "Conceder permisos de edición", "ChannelSharingTable.makeEditorConfirm": "Sí, conceder permisos", "ChannelSharingTable.makeEditorHeader": "Conceder permisos de edición", - "ChannelSharingTable.makeEditorText": "Confirmar que desea conceder los permisos de edición a {first_name} {last_name}", + "ChannelSharingTable.makeEditorText": "Por favor, confirme de que desea conceder los permisos de edición a {first_name} {last_name}", "ChannelSharingTable.noUsersText": "No se han encontrado usuarios", "ChannelSharingTable.optionsDropdown": "Opciones", "ChannelSharingTable.removeViewer": "Revocar permisos de ver", "ChannelSharingTable.removeViewerConfirm": "Si, revocar", "ChannelSharingTable.removeViewerHeader": "Revocar permisos de ver", - "ChannelSharingTable.removeViewerText": "Confirmar que desea anular los permisos de ver para {first_name} {last_name}", + "ChannelSharingTable.removeViewerText": "Por favor, confirme que desea anular los permisos de ver para {first_name} {last_name}", "ChannelSharingTable.resendInvitation": "Reenviar la invitación", "ChannelSharingTable.userRemovedMessage": "Usuario eliminado", "ChannelSharingTable.viewersSubheading": "{count, plural,\n =1 {# usuario que puede ver}\n other {# usuarios que pueden ver}}", @@ -404,7 +378,7 @@ "ChannelThumbnail.crop": "Recortar", "ChannelThumbnail.croppingPrompt": "Arrastrar la imagen para colocar", "ChannelThumbnail.defaultFilename": "Fichero", - "ChannelThumbnail.noThumbnail": "No hay miniaturas", + "ChannelThumbnail.noThumbnail": "No hay miniaturas.", "ChannelThumbnail.remove": "Eliminar", "ChannelThumbnail.retryUpload": "Volver a subir", "ChannelThumbnail.save": "Guardar", @@ -470,7 +444,7 @@ "CommonMetadataStrings.goal": "Cuando se alcanza el objetivo", "CommonMetadataStrings.guides": "Guías", "CommonMetadataStrings.highContrast": "Incluye texto de alto contraste para los estudiantes con baja visión", - "CommonMetadataStrings.history": "Historia", + "CommonMetadataStrings.history": "Historial", "CommonMetadataStrings.industryAndSectorSpecific": "Industria y sectores específicos", "CommonMetadataStrings.languageLearning": "Aprendizaje de idiomas", "CommonMetadataStrings.learningActivity": "Actividad de aprendizaje", @@ -480,7 +454,7 @@ "CommonMetadataStrings.listen": "Escuchar", "CommonMetadataStrings.literacy": "Alfabetización", "CommonMetadataStrings.literature": "Literatura", - "CommonMetadataStrings.logicAndCriticalThinking": "Lógica y pensamiento crítico", + "CommonMetadataStrings.logicAndCriticalThinking": "Pensamiento lógico y crítico", "CommonMetadataStrings.longActivity": "Actividad larga", "CommonMetadataStrings.lowerPrimary": "Primaria inferior", "CommonMetadataStrings.lowerSecondary": "Secundaria inferior", @@ -533,6 +507,163 @@ "CommonMetadataStrings.webDesign": "Diseño web", "CommonMetadataStrings.work": "Trabajo", "CommonMetadataStrings.writing": "Escritura", + "CommonStrings.backAction": "Atrás", + "CommonStrings.channelDetailsLabel": "Detalles del canal", + "CommonStrings.clearAction": "Borrar", + "CommonStrings.closeAction": "Cerrar", + "CommonStrings.copyChannelTokenAction": "Copiar el token del canal", + "CommonStrings.dismissAction": "Descartar", + "CommonStrings.genericErrorMessage": "Lo sentimos, algo salió mal. Por favor inténtelo de nuevo.", + "CommonStrings.previewAction": "Vista previa", + "CommonStrings.seeAllAction": "Ver todo", + "CommonStrings.seeLessAction": "Ver menos", + "CommunityChannelsStrings.aboutCommunityLibraryDescription": "La Biblioteca comunitaria contiene canales compartidos por la comunidad y aprobados para su descubrimiento en Studio.", + "CommunityChannelsStrings.aboutCommunityLibraryTitle": "Acerca de la Biblioteca comunitaria", + "CommunityChannelsStrings.activityHistoryLabel": "Historial de actividad", + "CommunityChannelsStrings.adminLabel": "Administrador", + "CommunityChannelsStrings.allLicensesCompatible": "Todas las licencias son compatibles con Biblioteca comunitaria.", + "CommunityChannelsStrings.allNotificationsLabel": "Todas las notificaciones", + "CommunityChannelsStrings.alreadySubmittedWarningDescription": "Por favor, espere a que el canal sea revisado. Verá una notificación en su cuenta de Studio después de que la revisión se haya completado.", + "CommunityChannelsStrings.alreadySubmittedWarningTitle": "Esta versión del canal ya ha sido enviada a la Biblioteca comunitaria.", + "CommunityChannelsStrings.approvedNotification": "{author} ({userType}) ha aprobado {channelVersion}", + "CommunityChannelsStrings.approvedPrimaryInfo": "Una versión anterior está ya en la Biblioteca comunitaria. Los revisores verán la última versión primero.", + "CommunityChannelsStrings.approvedStatus": "Aprobado", + "CommunityChannelsStrings.availableStatus": "Disponible en la Biblioteca comunitaria", + "CommunityChannelsStrings.cancelAction": "Cancelar", + "CommunityChannelsStrings.categoriesLabel": "Categorías", + "CommunityChannelsStrings.channelCannotBeDistributed": "Este canal no puede ser distribuido a través de Kolibri.", + "CommunityChannelsStrings.channelFitAttributionLabel": "Atribución", + "CommunityChannelsStrings.channelFitChannelInfoLabel": "Información del canal", + "CommunityChannelsStrings.channelFitChecklistAttribution": "¿Cada recurso tiene indicado su autor?", + "CommunityChannelsStrings.channelFitChecklistChannelInfo": "¿Su canal tiene toda la información básica — título, descripción, miniatura, idioma, categoría y nivel?", + "CommunityChannelsStrings.channelFitChecklistIntro": "Criterios para enviar canal a la Biblioteca comunitaria", + "CommunityChannelsStrings.channelFitChecklistLicense": "¿Los recursos de su canal tienen la licencia abierta o son de dominio público?", + "CommunityChannelsStrings.channelFitChecklistOfflineUse": "¿Todos los recursos en su canal funcionan sin conexión a Internet?", + "CommunityChannelsStrings.channelFitChecklistQuality": "¿Alguien de su equipo u organización ha revisado el canal?", + "CommunityChannelsStrings.channelFitLicenseLabel": "Licencia", + "CommunityChannelsStrings.channelFitOfflineUseLabel": "Uso sin conexión", + "CommunityChannelsStrings.channelFitQualityLabel": "Calidad", + "CommunityChannelsStrings.channelTokenDescription": "Puede utilizar este token para importar y previsualizar el borrador del canal en Kolibri. Tenga en cuenta que el token para el canal público final será diferente.", + "CommunityChannelsStrings.channelVersion": "{name} v{version}", + "CommunityChannelsStrings.channelVersionTokenLabel": "Token de la versión de canal", + "CommunityChannelsStrings.clearAll": "Eliminar filtros", + "CommunityChannelsStrings.clearAllAction": "Eliminar filtros", + "CommunityChannelsStrings.communityLibraryCTADescription": "¿Tiene un canal que valga la pena compartir con otros educadores y estudiantes? Envíelo para la revisión a través del menú Compartir, para que pueda ser accesible en Studio.", + "CommunityChannelsStrings.communityLibraryCTATitle": "Ayúdanos a hacer crecer la Biblioteca comunitaria", + "CommunityChannelsStrings.communityLibraryDescription": "Explorar los canales enviados por la comunidad y aprobados para ser descubiertos en Studio. Copiar los tokens para usarlos en Kolibri.", + "CommunityChannelsStrings.communityLibraryLabel": "Biblioteca comunitaria", + "CommunityChannelsStrings.communityLibrarySubmissionLabel": "Envío a la Biblioteca comunitaria", + "CommunityChannelsStrings.compatibleLicensesDescription": "{licenseNames} - Todas las licencias son compatibles con la Biblioteca comunitaria.", + "CommunityChannelsStrings.confirmDistributionRights": "Por favor, confirme que tiene derecho a distribuir estos recursos a través de Kolibri.", + "CommunityChannelsStrings.confirmReplacementText": "Entiendo que esto reemplazará mi anterior envío en la cola de revisión", + "CommunityChannelsStrings.countriesInfoText": "Seleccione uno o más países con los que etiquetar el canal que está enviando. Por ejemplo, si su canal contiene materiales alineados a un currículo nacional o contenido regional específico, la selección de los países pertinentes ayudará a los usuarios a encontrarlo. De lo contrario, déje el campo en blanco.", + "CommunityChannelsStrings.countryLabel": "Países", + "CommunityChannelsStrings.descriptionLabel": "Describir qué hay de nuevo en este envío", + "CommunityChannelsStrings.dismissAction": "Descartar", + "CommunityChannelsStrings.draftBeingPublishedNotice": "Se está publicando el borrador de la versión", + "CommunityChannelsStrings.draftPublishedNotice": "Borrador publicado correctamente", + "CommunityChannelsStrings.draftTokenLabel": "Token del borrador", + "CommunityChannelsStrings.editorLabel": "Editor", + "CommunityChannelsStrings.emptyNotificationsNotice": "No tiene notificaciones en este momento.", + "CommunityChannelsStrings.emptyNotificationsWithFiltersNotice": "No hay notificaciones que coincidan con los filtros seleccionados.", + "CommunityChannelsStrings.errorLoadingVersions": "No se puede cargar el historial de versiones", + "CommunityChannelsStrings.errorSnackbar": "Hubo un error al enviar el canal", + "CommunityChannelsStrings.feedbackNotesLabel": "Notas del revisor", + "CommunityChannelsStrings.filterByDateLabel": "Filtrar por fecha", + "CommunityChannelsStrings.filterByStatusLabel": "Filtrar por estado", + "CommunityChannelsStrings.filterLabel": "Filtrar", + "CommunityChannelsStrings.fixLicensingBeforeSubmission": "Por favor, corrija la licencia antes de enviar una nueva versión.", + "CommunityChannelsStrings.flaggedNotification": "Versión {channelVersion} necesita cambios", + "CommunityChannelsStrings.flaggedStatus": "Necesita cambios", + "CommunityChannelsStrings.getDraftTokenAction": "Copiar token para el borrador del canal", + "CommunityChannelsStrings.goToMyChannelsAction": "Ir a Mis canales", + "CommunityChannelsStrings.hideCriteriaAction": "Ocultar criterios", + "CommunityChannelsStrings.hideVersions": "Ocultar versiones", + "CommunityChannelsStrings.incompatibleLicensesDescription": "\"{licenseNames}\" - este canal no puede ser distribuido a través de Kolibri. Si no puede cambiar la licencia, elimine todos los recursos con \"{licenseNames}\" antes de volver a enviar.", + "CommunityChannelsStrings.incompatibleLicensesDetected": "Se han detectado licencias incompatibles.", + "CommunityChannelsStrings.incompleteResourcesDescription1": "Los recursos incompletos no serán publicados ni disponibles para la descarga en Kolibri.", + "CommunityChannelsStrings.incompleteResourcesWarning": "{count, number} {count, plural, one {recurso incompleto} other {recursos incompletos}}", + "CommunityChannelsStrings.internalNotesLabel": "Notas del administrador (solo uso interno)", + "CommunityChannelsStrings.invalidLicensingReason": "Licencias no válidas o incompatibles", + "CommunityChannelsStrings.invalidMetadataReason": "Metadatos no válidos o ausentes", + "CommunityChannelsStrings.languageLabel": "Idioma", + "CommunityChannelsStrings.languagesLabel": "Idiomas", + "CommunityChannelsStrings.lessDetailsButton": "Ocultar detalles", + "CommunityChannelsStrings.licenseCheckPassed": "Verificación de licencia completada", + "CommunityChannelsStrings.licensesLabel": "Licencias", + "CommunityChannelsStrings.loadError": "Hubo un error al cargar los canales.", + "CommunityChannelsStrings.loadingVersionHistory": "Cargando historial de versiones", + "CommunityChannelsStrings.moreDetails": "Después de que su envío sea aprobado, el canal estará disponible para otros usuarios de Kolibri Studio en la página 'Biblioteca comunitaria'.", + "CommunityChannelsStrings.moreDetailsButton": "Más detalles", + "CommunityChannelsStrings.needKolibriVersionToImport": "Necesitará la versión 0.19.4 o superior de Kolibri para importar canales comunitarios.", + "CommunityChannelsStrings.needsChangesPrimaryInfo": "Su versión previamente enviada necesita cambios. Asegúrese de revisar todos los comentarios antes de volver a enviar.", + "CommunityChannelsStrings.newLabel": "Nuevo", + "CommunityChannelsStrings.newNotificationsNotice": "Nuevas notificaciones disponibles.", + "CommunityChannelsStrings.nextPageAction": "Siguiente", + "CommunityChannelsStrings.noCommunityChannels": "Todavía no hay canales en la Biblioteca comunitaria.", + "CommunityChannelsStrings.noResultsWithFilters": "Ningún canal coincide con los filtros seleccionados.", + "CommunityChannelsStrings.noVersionsAvailable": "No hay historial de versiones disponible", + "CommunityChannelsStrings.nonePrimaryInfo": "Invitamos a los miembros de la comunidad de Kolibri a enviar canales que han creado para el aprendizaje sin conexión en los contextos de recursos bajos. ", + "CommunityChannelsStrings.notPublishedWarningDescription": "Publicar primero en Studio y luego enviar a la Biblioteca comunitaria.", + "CommunityChannelsStrings.notPublishedWarningTitle": "Este canal aún no está publicado en Kolibri Studio", + "CommunityChannelsStrings.notificationsLabel": "Notificaciones", + "CommunityChannelsStrings.otherIssuesReason": "Otras incidencias", + "CommunityChannelsStrings.pageIndicator": "{currentPage} de {totalPages}", + "CommunityChannelsStrings.pendingStatus": "Enviado", + "CommunityChannelsStrings.portabilityIssuesReason": "Problemas de portabilidad", + "CommunityChannelsStrings.previewYourDraftTitle": "Ver el borrador de canal en Kolibri", + "CommunityChannelsStrings.previousPageAction": "Anterior", + "CommunityChannelsStrings.publicWarningDescription": "No es posible enviar canales públicos a la Biblioteca comunitaria.", + "CommunityChannelsStrings.publicWarningTitle": "Este canal es actualmente público en la Biblioteca Kolibri.", + "CommunityChannelsStrings.publishAction": "Publicar", + "CommunityChannelsStrings.publishChannel": "Publicar canal", + "CommunityChannelsStrings.publishChannelDescription": "Para ver el canal en Kolibri, usar el token de canal para importarlo.", + "CommunityChannelsStrings.publishChannelMode": "Publicar canal", + "CommunityChannelsStrings.publishDraftDescription": "Su canal se guardará como el borrador, permitiendo revisar la calidad en el borrador, sin alterar la versión actual pública disponible para los usuarios de Kolibri. Para ver este borrador del canal en Kolibri, hay que importarlo usando su propio token.", + "CommunityChannelsStrings.publishDraftMode": "Publicar borrador del canal", + "CommunityChannelsStrings.publishedVersionLabel": "Versión publicada:", + "CommunityChannelsStrings.publishingInfo": "Está publicando: Versión {version}", + "CommunityChannelsStrings.publishingMessage": "El canal se está publicando", + "CommunityChannelsStrings.qualityAssuranceReason": "Incidencias de control de calidad", + "CommunityChannelsStrings.reasonLabel": "Razón: {reason}", + "CommunityChannelsStrings.resubmitAction": "Reenviar", + "CommunityChannelsStrings.resubmitModalBodyFirst": "{channelName} v{version} también está publicado en la Biblioteca comunitaria.", + "CommunityChannelsStrings.resubmitModalBodySecond": "¿Le gustaría volver a enviar esta versión con sus cambios para la revisión en la Biblioteca comunitaria?", + "CommunityChannelsStrings.resubmitModalTitle": "¿Reenviar canal para revisión en la Biblioteca comunitaria?", + "CommunityChannelsStrings.resultsText": "{count, plural, =1 {# resultado encontrado} other {# resultados encontrados}}", + "CommunityChannelsStrings.retry": "Volver a intentar", + "CommunityChannelsStrings.reviewAction": "Revisar", + "CommunityChannelsStrings.saveDraft": "Guardar borrador", + "CommunityChannelsStrings.searchLabel": "Buscar", + "CommunityChannelsStrings.searchNotificationsLabel": "Buscar notificaciones", + "CommunityChannelsStrings.seeAllVersions": "Ver todas las versiones", + "CommunityChannelsStrings.showMore": "Mostrar más", + "CommunityChannelsStrings.showOlderAction": "Ver anteriores", + "CommunityChannelsStrings.specialPermissionsDetected": "Licencias de permisos especiales detectadas", + "CommunityChannelsStrings.submissionCreationNotification": "Su envío a la Biblioteca comunitaria fue exitoso y ahora está siendo revisado.", + "CommunityChannelsStrings.submissionNotesLabel": "Notas de envío", + "CommunityChannelsStrings.submissionNotification": "{author} ({userType}) ha enviado {channelVersion}", + "CommunityChannelsStrings.submitButton": "Enviar para revisión", + "CommunityChannelsStrings.submitToCommunityLibrary": "Enviar a la Biblioteca comunitaria", + "CommunityChannelsStrings.submittedPrimaryInfo": "Una versión anterior todavía está pendiente de revisión. Los revisores verán el envío más reciente por defecto.", + "CommunityChannelsStrings.submittedSnackbar": "Canal enviado a la Biblioteca comunitaria", + "CommunityChannelsStrings.submittingSnackbar": "Enviando canal a la Biblioteca comunitaria...", + "CommunityChannelsStrings.supersededStatus": "Reemplazado", + "CommunityChannelsStrings.thisMonthLabel": "Este mes", + "CommunityChannelsStrings.thisWeekLabel": "Esta semana", + "CommunityChannelsStrings.thisYearLabel": "Este año", + "CommunityChannelsStrings.todayLabel": "Hoy", + "CommunityChannelsStrings.unreadNotificationsLabel": "No leídas", + "CommunityChannelsStrings.versionDescriptionLabel": "Descripción de la versión", + "CommunityChannelsStrings.versionLabel": "Versión {version}", + "CommunityChannelsStrings.versionNotesLabel": "Describir las novedades en esta versión de canal", + "CommunityChannelsStrings.viewCriteriaAction": "Ver criterios", + "CommunityChannelsStrings.viewMoreAction": "Ver más", + "CommunityChannelsStrings.whatCanYouDoHere": "Lo que puede hacer aquí", + "CommunityChannelsStrings.whatCanYouDoHereItem1": "Navegar canales por país, categoría e idioma", + "CommunityChannelsStrings.whatCanYouDoHereItem2": "Copiar un token de canal para importarlo en Kolibri", + "CommunityChannelsStrings.whatCanYouDoHereItem3": "Ver detalles del canal como la descripción y metadatos", + "CommunityChannelsStrings.whatIsCommunityLibrary": "¿Qué es la Biblioteca comunitaria?", "CommunityStandardsModal.communityStandardsHeader": "Normas de la comunidad", "CommunityStandardsModal.coreValuesLink": "Aprenda más sobre los valores fundamentales de Learning Equality", "CommunityStandardsModal.description": "Learning Equality es una organización sin fines de lucro dedicada a facilitar un acceso equitativo a experiencias educativas de calidad. Junto con nuestra declaración de valores fundamentales, estas Normas Comunitarias pretenden fomentar un entorno de apoyo e inclusivo para nuestros usuarios.", @@ -601,20 +732,20 @@ "ConstantStrings.mp4": "Video MP4", "ConstantStrings.multiple_selection": "Selección múltiple", "ConstantStrings.nthCopy": "Copia {n, number, integer} de {title}", - "ConstantStrings.num_correct_in_a_row_10": "Objetivo: 10 aciertos seguidos", + "ConstantStrings.num_correct_in_a_row_10": "Objetivo: 10 seguidos", "ConstantStrings.num_correct_in_a_row_10_description": "Estudiante tiene que responder correctamente a 10 preguntas seguidas", - "ConstantStrings.num_correct_in_a_row_2": "Objetivo: 2 aciertos seguidos", + "ConstantStrings.num_correct_in_a_row_2": "Objetivo: 2 seguidos", "ConstantStrings.num_correct_in_a_row_2_description": "Estudiante tiene que responder correctamente a 2 preguntas seguidas", - "ConstantStrings.num_correct_in_a_row_3": "Objetivo: 3 aciertos seguidos", + "ConstantStrings.num_correct_in_a_row_3": "Objetivo: 3 seguidos", "ConstantStrings.num_correct_in_a_row_3_description": "Estudiante tiene que responder correctamente a 3 preguntas seguidas", - "ConstantStrings.num_correct_in_a_row_5": "Objetivo: 5 aciertos seguidos", + "ConstantStrings.num_correct_in_a_row_5": "Objetivo: 5 seguidos", "ConstantStrings.num_correct_in_a_row_5_description": "Estudiante tiene que responder correctamente a 5 preguntas seguidas", "ConstantStrings.pdf": "Documento PDF", "ConstantStrings.perseus": "Ejercicio Perseus", "ConstantStrings.perseus_question": "Pregunta de Khan Academy", "ConstantStrings.png": "Imagen PNG", - "ConstantStrings.public": "Biblioteca de contenido", - "ConstantStrings.single_selection": "Respuesta única", + "ConstantStrings.public": "Biblioteca de Kolibri", + "ConstantStrings.single_selection": "Selección única", "ConstantStrings.slideshow": "Presentación", "ConstantStrings.svg": "Imagen SVG", "ConstantStrings.topic": "Carpeta", @@ -633,7 +764,7 @@ "ContentDefaults.author": "Autor", "ContentDefaults.copyrightHolder": "Titular de derechos de autor", "ContentDefaults.defaultsSubTitle": "Los nuevos recursos tendrán estos valores por defecto", - "ContentDefaults.defaultsTitle": "Configuración por defecto de propiedad intelectual para los nuevos recursos (opcional)", + "ContentDefaults.defaultsTitle": "Configuración por defecto de propriedad intelectual para los nuevos recursos (opcional)", "ContentDefaults.documents": "Documentos", "ContentDefaults.html5": "Aplicaciones HTML5", "ContentDefaults.license": "Licencia", @@ -707,7 +838,7 @@ "ContentNodeThumbnail.defaultFilename": "Fichero", "ContentNodeThumbnail.generate": "Generar desde fichero", "ContentNodeThumbnail.generatingThumbnail": "Generando desde fichero", - "ContentNodeThumbnail.noThumbnail": "No hay miniaturas", + "ContentNodeThumbnail.noThumbnail": "No hay miniaturas.", "ContentNodeThumbnail.remove": "Eliminar", "ContentNodeThumbnail.retryUpload": "Volver a subir", "ContentNodeThumbnail.save": "Guardar", @@ -716,11 +847,11 @@ "ContentNodeThumbnail.uploadingThumbnail": "Cargando", "ContentNodeThumbnail.zoomIn": "Aumentar tamaño", "ContentNodeThumbnail.zoomOut": "Reducir tamaño", - "ContentNodeValidator.allIncompleteDescendantsText": "{count, plural, one {{count, number, integer} recurso está incompleto y no puede ser publicado} other {{count, number, integer} recursos están incompletos y no pueden ser publicados}}", - "ContentNodeValidator.incompleteDescendantsText": "{count, number, integer} {count, plural, one {recurso está incompleto} other {recursos están incompletos}}", + "ContentNodeValidator.allIncompleteDescendantsText": "{count, plural, one {{count, number, integer} el recurso está incompleto y no puede ser publicado} other {todos {count, number, integer} recursos están incompletos y no pueden ser publicados}}", + "ContentNodeValidator.incompleteDescendantsText": "{count, number, integer} {count, plural, one {el recurso está incompleto} other {los recursos están incompletos}}", "ContentNodeValidator.incompleteText": "Incompleto", "ContentNodeValidator.missingTitle": "Falta el título", - "ContentRenderer.noFileText": "Seleccionar un fichero para previsualizar", + "ContentRenderer.noFileText": "Seleccionar un fichero para la vista previa", "ContentRenderer.previewNotSupported": "Vista previa no disponible", "ContentTreeList.allChannelsLabel": "Canales", "ContentTreeList.noResourcesOrTopics": "No hay recursos ni carpetas aquí", @@ -728,21 +859,21 @@ "CopyToken.copiedTokenId": "Token copiado", "CopyToken.copyFailed": "Hubo un fallo al copiar", "CopyToken.copyPrompt": "Copiar token para importar el canal en Kolibri", - "CountryField.locationLabel": "Seleccionar todo lo que corresponda", + "CountryField.locationLabel": "Seleccione todo lo que corresponda", "CountryField.locationRequiredMessage": "Este campo es obligatorio", "CountryField.noCountriesFound": "No se han encontrado países", - "Create.ToSRequiredMessage": "Por favor, aceptar nuestra política y términos de servicio", + "Create.ToSRequiredMessage": "Por favor, acepte nuestra política y términos de servicio", "Create.agreement": "He leído y estoy de acuerdo con los términos de servicio y la política de privacidad", "Create.backToLoginButton": "Iniciar sesión", "Create.basicInformationHeader": "Información básica", "Create.conferenceSourceOption": "Conferencia", "Create.conferenceSourcePlaceholder": "Nombre de la conferencia", "Create.confirmPasswordLabel": "Confirmar la contraseña", - "Create.contactMessage": "¿Tiene preguntas o inquietudes? Envíenos un correo electrónico a content@learningequality.org", + "Create.contactMessage": "¿Tiene preguntas o inquietudes? Por favor envíenos un correo electrónico a content@learningequality.org", "Create.conversationSourceOption": "Conversación con Learning Equality", "Create.createAnAccountTitle": "Crear cuenta", "Create.creatingExercisesUsageOption": "Crear ejercicios", - "Create.emailExistsMessage": "Ya existe una cuenta asociada a este correo electrónico", + "Create.emailExistsMessage": "Ya existe una cuenta asociada a este correo electrónico.", "Create.errorsMessage": "Por favor, corregir los siguientes errores", "Create.findingUsageOption": "Buscar y agregar fuentes de contenido adicionales", "Create.finishButton": "Finalizar", @@ -763,7 +894,7 @@ "Create.passwordMatchMessage": "Las contraseñas no coinciden", "Create.passwordValidationMessage": "La contraseña debe tener al menos 8 caracteres", "Create.personalDemoSourceOption": "Demo personal", - "Create.registrationFailed": "Hubo un error al crear la cuenta. Por favor, inténtelo nuevamente", + "Create.registrationFailed": "Hubo un error al crear la cuenta. Por favor, inténtelo nuevamente.", "Create.registrationFailedOffline": "Parece que no tiene conexión. Por favor, conéctese a Internet para crear una cuenta.", "Create.sequencingUsageOption": "Utilizar requisitos previos para poner materiales en una secuencia", "Create.sharingUsageOption": "Compartir materiales públicamente", @@ -819,40 +950,11 @@ "DeleteAccountForm.emailInvalidText": "El correo electrónico no coincide con el correo de su cuenta", "DeleteAccountForm.fieldRequired": "Este campo es obligatorio", "DeleteAccountForm.ok": "Aceptar", - "DetailsPanel.AVERAGE": "Medio", - "DetailsPanel.LARGE": "Grande", - "DetailsPanel.SMALL": "Pequeño", - "DetailsPanel.VERY_LARGE": "Muy grande", - "DetailsPanel.VERY_SMALL": "Muy pequeño", - "DetailsPanel.aggregatorToolTip": "Sitio web o la organización que aloja la colección de contenido, pero no necesariamente el creador o titular de derechos de autor", - "DetailsPanel.aggregatorsLabel": "Agregadores", - "DetailsPanel.assessmentsIncludedText": "Evaluaciones", - "DetailsPanel.authorToolTip": "Persona u organización que ha creado este contenido", - "DetailsPanel.authorsLabel": "Autores", - "DetailsPanel.categoriesHeading": "Categorías", - "DetailsPanel.coachDescription": "Los recursos para tutores son visibles sólo a los tutores en Kolibri", - "DetailsPanel.coachHeading": "Recursos para tutores", - "DetailsPanel.containsContentHeading": "Contiene contenidos de", - "DetailsPanel.containsHeading": "Contiene", - "DetailsPanel.copyrightHoldersLabel": "Titulares de derechos de autor", - "DetailsPanel.creationHeading": "Creado el", - "DetailsPanel.currentVersionHeading": "Versión publicada", - "DetailsPanel.languagesHeading": "Idiomas", - "DetailsPanel.levelsHeading": "Niveles", - "DetailsPanel.licensesLabel": "Licencias", - "DetailsPanel.primaryLanguageHeading": "Idioma principal", - "DetailsPanel.providerToolTip": "Organización que ha encargado o está distribuyendo el contenido", - "DetailsPanel.providersLabel": "Proveedores", - "DetailsPanel.publishedHeading": "Publicado el", - "DetailsPanel.resourceHeading": "El total de recursos", - "DetailsPanel.sampleFromChannelHeading": "Contenido de ejemplo de este canal", - "DetailsPanel.sampleFromTopicHeading": "Contenido de ejemplo de este tema", - "DetailsPanel.sizeHeading": "Tamaño del canal", - "DetailsPanel.sizeText": "{text} ({size})", - "DetailsPanel.subtitlesHeading": "Subtítulos", - "DetailsPanel.tagsHeading": "Etiquetas comunes", - "DetailsPanel.tokenHeading": "Token del canal", - "DetailsPanel.unpublishedText": "No publicado", + "DeleteChannelModal.cancel": "Cancelar", + "DeleteChannelModal.channelDeletedSnackbar": "Canal eliminado", + "DeleteChannelModal.deleteChannel": "Eliminar canal", + "DeleteChannelModal.deletePrompt": "Este canal se eliminará permanentemente. Esto no se puede deshacer.", + "DeleteChannelModal.deleteTitle": "Eliminar este canal", "DetailsTabView.aggregatorLabel": "Agregador", "DetailsTabView.aggregatorToolTip": "Sitio web o la organización que aloja la colección de contenido, pero no necesariamente el creador o titular de derechos de autor", "DetailsTabView.assessmentOptionsLabel": "Opciones de evaluación", @@ -860,7 +962,7 @@ "DetailsTabView.authorLabel": "Autor", "DetailsTabView.authorToolTip": "Persona u organización que ha creado este contenido", "DetailsTabView.basicInfoHeader": "Información básica", - "DetailsTabView.completionLabel": "Finalización", + "DetailsTabView.completionLabel": "Tipo de finalización", "DetailsTabView.copyrightHolderLabel": "Titular de derechos de autor", "DetailsTabView.descriptionLabel": "Descripción", "DetailsTabView.detectedImportText": "{count, plural,\n =1 {# recurso solo tiene permiso de vista}\n other {# recursos solo tienen permiso de vista}}", @@ -1008,8 +1110,6 @@ "ForgotPassword.forgotPasswordPrompt": "Introduzca su dirección de correo electrónico para recibir instrucciones para restablecer su contraseña", "ForgotPassword.forgotPasswordTitle": "Restablecer la contraseña", "ForgotPassword.submitButton": "Enviar", - "FormulasMenu.btnLabelInsert": "Insertar", - "FormulasMenu.formulasMenuTitle": "Caracteres especiales", "FormulasStrings.addition": "Suma", "FormulasStrings.advancedCategory": "Más detalles", "FormulasStrings.alpha": "alfa", @@ -1204,16 +1304,6 @@ "HintsEditor.newHintBtnLabel": "Nueva pista", "HintsEditor.noHintsPlaceholder": "La pregunta no tiene pistas", "ImageOnlyThumbnail.thumbnail": "{title} miniatura", - "ImagesMenu.acceptsText": "Tipos de ficheros permitidos: {acceptedFormats}", - "ImagesMenu.altTextHint": "La descripción de la imagen es necesaria para permitir que los estudiantes con discapacidad visual respondan a las preguntas, y también se muestra cuando la imagen no se carga", - "ImagesMenu.altTextLabel": "Descripción de la imagen", - "ImagesMenu.btnLabelCancel": "Cancelar", - "ImagesMenu.btnLabelInsert": "Insertar", - "ImagesMenu.currentImageDefaultText": "Imagen actual", - "ImagesMenu.defaultDropText": "Arrastrar y soltar una imagen aquí, o subirla manualmente", - "ImagesMenu.imageHeader": "Subir imagen", - "ImagesMenu.selectFile": "Seleccionar fichero", - "ImagesMenu.selectFileButton": "Seleccionar fichero", "ImportFromChannelsModal.addButton": "Añadir", "ImportFromChannelsModal.addedText": "Añadido", "ImportFromChannelsModal.importAction": "Importar", @@ -1223,6 +1313,7 @@ "ImportFromChannelsModal.reviewAction": "Revisar", "ImportFromChannelsModal.reviewTitle": "Selección de recursos", "InfoModal.close": "Cerrar", + "InfoModal.open": "Más información sobre licencias", "InheritAncestorMetadataModal.applyResourceDetailsTitle": "Aplicar detalles desde la carpeta '{folder}'", "InheritAncestorMetadataModal.cancelAction": "Cancelar", "InheritAncestorMetadataModal.categories": "Categorías: {categories}", @@ -1255,17 +1346,48 @@ "MainNavigationDrawer.helpLink": "Ayuda y soporte", "MainNavigationDrawer.logoutLink": "Cerrar sesión", "MainNavigationDrawer.settingsLink": "Configuración", - "MarkdownEditor.bold": "Negrita (Ctrl+B)", - "MarkdownEditor.formulas": "Insertar fórmula (Ctrl+F)", - "MarkdownEditor.image": "Insertar imagen (Ctrl+P)", - "MarkdownEditor.italic": "Cursiva (Ctrl+I)", - "MarkdownEditor.minimize": "Minimizar (Ctrl+M)", - "MarkdownImageField.editImageOption": "Editar", - "MarkdownImageField.removeImageOption": "Eliminar", - "MarkdownImageField.resizeImageOption": "Cambiar tamaño", "MasteryCriteriaGoal.labelText": "Objetivo", "MasteryCriteriaMofNFields.mHint": "Hay que especificar respuestas correctas", "MasteryCriteriaMofNFields.nHint": "Respuestas recientes", + "MathLiveA11yStrings.accented": "acentuado", + "MathLiveA11yStrings.array": "matriz", + "MathLiveA11yStrings.box": "caja", + "MathLiveA11yStrings.chemicalFormula": "fórmula química", + "MathLiveA11yStrings.crossOut": "tachar", + "MathLiveA11yStrings.deleted": "eliminada: ", + "MathLiveA11yStrings.delimiter": "delimitador", + "MathLiveA11yStrings.denominator": "denominador", + "MathLiveA11yStrings.endOf": "{spokenText}; fin de {relationName}", + "MathLiveA11yStrings.endOfMathfield": "{spokenText}; fin de la entrada de matemáticas", + "MathLiveA11yStrings.error": "error", + "MathLiveA11yStrings.extensibleSymbol": "símbolo extensible", + "MathLiveA11yStrings.first": "primero", + "MathLiveA11yStrings.fraction": "fracción", + "MathLiveA11yStrings.group": "grupo", + "MathLiveA11yStrings.index": "índice", + "MathLiveA11yStrings.latex": "LaTeX", + "MathLiveA11yStrings.line": "línea", + "MathLiveA11yStrings.mathField": "campo matemático", + "MathLiveA11yStrings.mathfield": "campo matemático", + "MathLiveA11yStrings.numerator": "numerador", + "MathLiveA11yStrings.operator": "operador", + "MathLiveA11yStrings.outOf": "fin de {relationName};", + "MathLiveA11yStrings.overUnder": "sobre-debajo", + "MathLiveA11yStrings.parent": "padre", + "MathLiveA11yStrings.placeholder": "marcador de posición", + "MathLiveA11yStrings.prompt": "prompt", + "MathLiveA11yStrings.radicand": "radicando", + "MathLiveA11yStrings.rule": "regla", + "MathLiveA11yStrings.selected": "seleccionado: ", + "MathLiveA11yStrings.space": "espacio", + "MathLiveA11yStrings.spacing": "espaciado", + "MathLiveA11yStrings.squareRoot": "raíz cuadrada", + "MathLiveA11yStrings.startOf": "inicio de {relationName}: ", + "MathLiveA11yStrings.subscript": "subíndice", + "MathLiveA11yStrings.subscriptSuperscript": "subíndice-superíndice", + "MathLiveA11yStrings.superscript": "superíndice", + "MathLiveA11yStrings.superscriptAndSubscript": "superíndice y subíndice", + "MathLiveA11yStrings.text": "texto", "MessageLayout.backToLogin": "Continuar a la página de inicio de sesión", "MoveModal.addTopic": "Añadir nueva carpeta", "MoveModal.cancel": "Cancelar", @@ -1296,7 +1418,7 @@ "PasswordField.passwordLabel": "Contraseña", "PasswordInstructionsSent.passwordInstructionsHeader": "Instrucciones enviadas. ¡Gracias!", "PasswordInstructionsSent.passwordInstructionsText": "Si ya existe una cuenta con la dirección de correo electrónico proporcionada, recibirá las instrucciones en breve. Si no ve un correo electrónico nuestro, por favor revise su carpeta de correo basura.", - "PermissionsError.goToHomePageAction": "Ir a la página de inicio", + "PermissionsError.backToHome": "Volver al inicio", "PermissionsError.permissionDeniedHeader": "¿Ha olvidado iniciar la sesión?", "PoliciesModal.checkboxText": "He leído y estoy de acuerdo con los términos anteriores", "PoliciesModal.closeButton": "Cerrar", @@ -1306,6 +1428,7 @@ "PrivacyPolicyModal.updatedPrivacyHeader": "Política de privacidad actualizada", "ProgressBar.progressText": "{percent}%", "ProgressModal.defaultErrorText": "Último intento de publicación fallido", + "ProgressModal.draftHeader": "Guardando borrador…", "ProgressModal.lastPublished": "Publicado {last_published}", "ProgressModal.publishHeader": "Publicando el canal", "ProgressModal.syncError": "Último intento de sincronización fallido", @@ -1346,6 +1469,19 @@ "RelatedResourcesTab.showPreviewBtnLabel": "Mostrar", "RelatedResourcesTab.tooManyNextStepsWarning": "Limita el número de pasos siguientes para crear una experiencia de aprendizaje más guiada", "RelatedResourcesTab.tooManyPreviousStepsWarning": "Limita el número de pasos anteriores para crear una experiencia de aprendizaje más guiada", + "RemoveChannelFromListModal.cancel": "Cancelar", + "RemoveChannelFromListModal.channelRemovedSnackbar": "Canal eliminado", + "RemoveChannelFromListModal.removeBtn": "Eliminar", + "RemoveChannelFromListModal.removePrompt": "Tiene permisos de solo lectura para este canal. Confirmar que quiere eliminarlo de su lista de canales.", + "RemoveChannelFromListModal.removeTitle": "Eliminar de la lista de canales", + "RemoveChannelModal.cancel": "Cancelar", + "RemoveChannelModal.deleteChannel": "Eliminar canal", + "RemoveChannelModal.deleteChannelWithCLWarning": "Este canal se ha compartido en la Biblioteca comunitaria. Si se elimina aquí no verá eliminado de la Biblioteca comunitaria - todavía puede ser aprobado o seguir estando disponible allí.", + "RemoveChannelModal.deletePrompt": "Este canal se eliminará permanentemente. Esto no se puede deshacer.", + "RemoveChannelModal.deleteTitle": "Eliminar este canal", + "RemoveChannelModal.removeBtn": "Eliminar", + "RemoveChannelModal.removePrompt": "Tiene permisos de solo lectura para este canal. Confirmar que quiere eliminarlo de su lista de canales.", + "RemoveChannelModal.removeTitle": "Eliminar de la lista de canales", "ReportErrorModal.closeAction": "Cerrar", "ReportErrorModal.emailDescription": "Contactar el equipo de soporte con los datos del error y haremos lo posible para ayudar.", "ReportErrorModal.emailPrompt": "Enviar un correo electrónico al equipo técnico", @@ -1427,7 +1563,7 @@ "ResourcePanel.description": "Descripción", "ResourcePanel.details": "Detalles", "ResourcePanel.fileSize": "Tamaño", - "ResourcePanel.files": "Ficheros", + "ResourcePanel.files": "Archivos", "ResourcePanel.incompleteQuestionError": "{count, plural, one {# pregunta incompleta} other {# preguntas incompletas}}", "ResourcePanel.language": "Idioma", "ResourcePanel.license": "Licencia", @@ -1435,7 +1571,7 @@ "ResourcePanel.noCompletionCriteriaError": "Los criterios de finalización son obligatorios", "ResourcePanel.noCopyrightHolderError": "Hay que especificar el titular de los derechos de autor", "ResourcePanel.noDurationError": "Este campo es obligatorio", - "ResourcePanel.noFilesError": "El fichero es requerido", + "ResourcePanel.noFilesError": "El archivo es requerido", "ResourcePanel.noLearningActivityError": "Este campo es obligatorio", "ResourcePanel.noLicenseDescriptionError": "La descripción de la licencia es obligatoria", "ResourcePanel.noLicenseError": "Se requiere licencia", @@ -1485,7 +1621,7 @@ "SearchFilters.channelTypeLabel": "Tipo de canal", "SearchFilters.channelsHeader": "Canales", "SearchFilters.coachContentLabel": "Mostrar recursos para tutores", - "SearchFilters.filtersHeader": "Opciones de filtro", + "SearchFilters.filtersHeader": "Opciones de filtro ", "SearchFilters.hideTopicsLabel": "Ocultar carpetas", "SearchFilters.kindLabel": "Formato", "SearchFilters.licensesLabel": "Licencia", @@ -1533,8 +1669,11 @@ "SearchRecommendationsStrings.tooAdvancedForLearnersLabel": "Demasiado básico para el nivel de conocimiento de los estudiantes que estoy buscando", "SearchRecommendationsStrings.tooBasicForLearnersLabel": "Demasiado básico para el nivel de conocimiento de los estudiantes que estoy buscando", "SearchRecommendationsStrings.tryAgainLink": "Intenta de nuevo", + "SearchRecommendationsStrings.trySearchRecommendationsHeader": "¡Prueba la nueva opción de \"Recomendaciones\"!", + "SearchRecommendationsStrings.trySearchRecommendationsText": "Basándonos en el título y la descripción de la carpeta en la que está trabajando, le sugeriremos recursos relevantes de la Biblioteca Kolibri. Seleccione 'Importar de los canales' en cualquier carpeta de sus canales para ver las recomendaciones.", "SearchRecommendationsStrings.undoAction": "Deshacer", "SearchRecommendationsStrings.viewMoreLink": "Ver más", + "SearchRecommendationsStrings.viewRecommendationsButton": "Ver recomendaciones", "SearchResultsList.failedToLoad": "Error al cargar los resultados de búsqueda", "SearchResultsList.resultsPerPageLabel": "Resultados por página", "SearchResultsList.saveSearchAction": "Guardar búsqueda", @@ -1544,7 +1683,7 @@ "SettingsIndex.settingsTitle": "Configuración", "SettingsIndex.storageLabel": "Almacenamiento", "SettingsIndex.usingStudioLabel": "Acerca de Studio", - "StagingTreePage.backToViewing": "Volver a navegar", + "StagingTreePage.backToViewing": "Volver a la navegación", "StagingTreePage.cancelDeployBtn": "Cancelar", "StagingTreePage.cancelPublishDraftBtn": "Cancelar", "StagingTreePage.channelDeployed": "El canal ha sido desplegado", @@ -1588,10 +1727,121 @@ "Storage.spaceUsedOfMax": "{qty} de {max}", "Storage.storagePercentageUsed": "{qty}% de almacenamiento usado", "Storage.videoFiles": "Los ficheros de vídeo deben ser comprimidos para maximizar el espacio de almacenamiento y asegurar una distribución y reproducción sin conexión. Una vez comprimidos, el almacenamiento total requerido puede ser inferior a lo que originalmente estimaba.", + "StudioChannelCard.channelLanguageNotSetIndicator": "Idioma no establecido", + "StudioChannelCard.details": "Detalles", + "StudioChannelCard.lastPublished": "Publicado {last_published}", + "StudioChannelCard.lastUpdated": "Actualizado {updated}", + "StudioChannelCard.multipleCountries": "Múltiples países", + "StudioChannelCard.resourceCount": "{count, plural,\n =1 {# recurso}\n other {# recursos}}", + "StudioChannelCard.selectChannel": "Seleccionar {name}", + "StudioChannelCard.unpublishedText": "No publicado", + "StudioChannelsPage.invitations": "Tiene {count, plural, one {}\n =1 {# invitación}\n other {# invitaciones}}", + "StudioChannelsPage.noChannelsFound": "No se han encontrado canales", + "StudioCollectionsTable.aboutChannelSets": "Acerca de las colecciones", + "StudioCollectionsTable.aboutChannelSetsLink": "Más información sobre colecciones", + "StudioCollectionsTable.addChannelSetTitle": "Nueva colección", + "StudioCollectionsTable.cancel": "Cancelar", + "StudioCollectionsTable.cancelButtonLabel": "Cerrar", + "StudioCollectionsTable.channelNumber": "Número de canales", + "StudioCollectionsTable.channelSetsDescriptionText": "Una colección contiene múltiples canales de Kolibri Studio que pueden ser importados a Kolibri con solo el token de la colección.", + "StudioCollectionsTable.channelSetsDisclaimer": "Se necesita Kolibri con versión 0.12.0 o superior para importar colecciones de canales", + "StudioCollectionsTable.channelSetsInstructionsText": "Puede hacer una colección seleccionando los canales que desea importar juntos.", + "StudioCollectionsTable.collectionDeleted": "Colección eliminada", + "StudioCollectionsTable.copiedTokenId": "Token copiado", + "StudioCollectionsTable.copyFailed": "Hubo un fallo al copiar", + "StudioCollectionsTable.copyToken": "Copiar token", + "StudioCollectionsTable.delete": "Eliminar colección", + "StudioCollectionsTable.deleteChannelSetText": "Por favor, confirme que desea eliminar esta colección.", + "StudioCollectionsTable.deleteChannelSetTitle": "Eliminar colección", + "StudioCollectionsTable.deleteError": "Error al eliminar la colección", + "StudioCollectionsTable.edit": "Editar colección", + "StudioCollectionsTable.noChannelSetsFound": "Puede juntar varios canales para crear una colección. La colección completa puede ser importada a Kolibri a la vez usando un token de colección.", + "StudioCollectionsTable.options": "Opciones", + "StudioCollectionsTable.pageTitle": "Colecciones", + "StudioCollectionsTable.saving": "Guardando", + "StudioCollectionsTable.tableCaption": "Lista de colecciones", + "StudioCollectionsTable.title": "Nombre de la colección", + "StudioCollectionsTable.token": "Token ID", + "StudioCopyToken.copiedTokenId": "Token copiado", + "StudioCopyToken.copyFailed": "Hubo un fallo al copiar", + "StudioCopyToken.token": "Token", + "StudioCopyToken.tooltipText": "Copiar token para importar el canal en Kolibri", + "StudioDetailsPanel.AVERAGE": "Medio", + "StudioDetailsPanel.LARGE": "Grande", + "StudioDetailsPanel.SMALL": "Pequeño", + "StudioDetailsPanel.VERY_LARGE": "Muy grande", + "StudioDetailsPanel.VERY_SMALL": "Muy pequeño", + "StudioDetailsPanel.aggregatorToolTip": "Sitio web o la organización que aloja la colección de contenido, pero no necesariamente el creador o titular de derechos de autor", + "StudioDetailsPanel.aggregatorsLabel": "Agregadores", + "StudioDetailsPanel.assessmentsIncludedText": "Evaluaciones", + "StudioDetailsPanel.authorToolTip": "Persona u organización que ha creado este contenido", + "StudioDetailsPanel.authorsLabel": "Autores", + "StudioDetailsPanel.categoriesHeading": "Categorías", + "StudioDetailsPanel.coachDescription": "Los recursos para tutores son visibles solo a los tutores en Kolibri", + "StudioDetailsPanel.coachHeading": "Recursos para tutores", + "StudioDetailsPanel.containsContentHeading": "Contiene contenidos de", + "StudioDetailsPanel.containsHeading": "Contiene", + "StudioDetailsPanel.copyrightHoldersLabel": "Titulares de derechos de autor", + "StudioDetailsPanel.creationHeading": "Creado el", + "StudioDetailsPanel.currentVersionHeading": "Versión publicada", + "StudioDetailsPanel.languagesHeading": "Idiomas", + "StudioDetailsPanel.levelsHeading": "Niveles", + "StudioDetailsPanel.licensesLabel": "Licencias", + "StudioDetailsPanel.primaryLanguageHeading": "Idioma principal", + "StudioDetailsPanel.providerToolTip": "Organización que ha encargado o está distribuyendo el contenido", + "StudioDetailsPanel.providersLabel": "Proveedores", + "StudioDetailsPanel.publishedHeading": "Publicado el", + "StudioDetailsPanel.resourceHeading": "El total de recursos", + "StudioDetailsPanel.sampleFromChannelHeading": "Contenido de ejemplo de este canal", + "StudioDetailsPanel.sizeHeading": "Tamaño del canal", + "StudioDetailsPanel.sizeText": "{text} ({size})", + "StudioDetailsPanel.subtitlesHeading": "Subtítulos", + "StudioDetailsPanel.tagsHeading": "Etiquetas comunes", + "StudioDetailsPanel.tokenHeading": "Token del canal", + "StudioDetailsPanel.unpublishedText": "No publicado", + "StudioImmersiveModal.close": "Cerrar", + "StudioMessageLayout.backToLogin": "Continuar a la página de inicio de sesión", + "StudioMyChannels.copyToken": "Copiar el token del canal", + "StudioMyChannels.deleteChannel": "Eliminar canal", + "StudioMyChannels.editChannel": "Editar detalles del canal", + "StudioMyChannels.goToWebsite": "Ir a la página web de origen", + "StudioMyChannels.moreOptions": "Más opciones", + "StudioMyChannels.newChannel": "Nuevo canal", + "StudioMyChannels.title": "Mis canales", + "StudioMyChannels.viewContent": "Ver canal en Kolibri", "StudioOfflineAlert.offlineText": "Parece que no tiene conexión. Los cambios se guardarán una vez que la conexión se haya restablecido.", "StudioOfflineAlert.onlineText": "Vuelve a estar en línea.", + "StudioStarredChannels.copyToken": "Copiar el token del canal", + "StudioStarredChannels.deleteChannel": "Eliminar canal", + "StudioStarredChannels.editChannel": "Editar detalles del canal", + "StudioStarredChannels.goToWebsite": "Ir a la página web de origen", + "StudioStarredChannels.moreOptions": "Más opciones", + "StudioStarredChannels.removeChannel": "Eliminar canal", + "StudioStarredChannels.title": "Canales favoritos", + "StudioStarredChannels.viewContent": "Ver canal en Kolibri", "StudioTree.missingTitle": "Falta el título", "StudioTree.optionsTooltip": "Opciones", + "StudioViewOnlyChannels.copyToken": "Copiar el token del canal", + "StudioViewOnlyChannels.goToWebsite": "Ir a la página web de origen", + "StudioViewOnlyChannels.moreOptions": "Más opciones", + "StudioViewOnlyChannels.removeChannel": "Eliminar canal", + "StudioViewOnlyChannels.title": "Solo modo de lectura", + "StudioViewOnlyChannels.viewContent": "Ver canal en Kolibri", + "SubscriptionCard.annualPrice": "${price, number}/año", + "SubscriptionCard.cancelNotice": "La suscripción expirará el {date, date, medium}. El almacenamiento se eliminará después de esa fecha.", + "SubscriptionCard.dismiss": "Descartar", + "SubscriptionCard.genericError": "Hubo un problema al conectar con el proveedor de pagos. Por favor, inténtelo de nuevo.", + "SubscriptionCard.instantUpgrade": "Aumentar el almacenamiento (inmediato)", + "SubscriptionCard.manageSubscription": "Gestionar suscripción", + "SubscriptionCard.renewalNotice": "La suscripción se renovará automáticamente el {date, date, medium}.", + "SubscriptionCard.storageAmount": "Almacenamiento (GB)", + "SubscriptionCard.storageIncluded": "{size} incluidos con su suscripción", + "SubscriptionCard.storageRange": "Introduzca un valor entre 1 y 50", + "SubscriptionCard.subscriptionActive": "Suscripción de almacenamiento activa", + "SubscriptionCard.subscriptionCanceling": "Suscripción cancelada", + "SubscriptionCard.upgradeDescription": "Comprar almacenamiento adicional a $15/GB por año.", + "SubscriptionCard.upgradeNow": "Actualizar ahora", + "SubscriptionCard.upgradeSuccess": "Almacenamiento aumentado a {size}", "SubtitlesList.acceptedFormatsTooltip": "Formatos soportados: {extensions}", "SubtitlesList.addSubtitleText": "Añadir subtítulos", "SubtitlesList.subtitlesHeader": "Subtítulos", @@ -1608,8 +1858,8 @@ "SyncResourcesModal.syncButtonLabel": "Sincronizar", "SyncResourcesModal.syncExercisesExplainer": "Actualizar preguntas, respuestas y sugerencias en ejercicios y cuestionarios", "SyncResourcesModal.syncExercisesTitle": "Detalles de los ejercicios", - "SyncResourcesModal.syncFilesExplainer": "Actualizar todos los ficheros, incluyendo miniaturas y subtítulos", - "SyncResourcesModal.syncFilesTitle": "Ficheros", + "SyncResourcesModal.syncFilesExplainer": "Actualizar todos los archivos, incluyendo miniaturas y subtítulos", + "SyncResourcesModal.syncFilesTitle": "Archivos", "SyncResourcesModal.syncModalExplainer": "Sincronizar recursos en Kolibri Studio actualiza los recursos copiados o importados en este canal con cualquier cambio en los ficheros de recursos originales.", "SyncResourcesModal.syncModalSelectAttributes": "Seleccionar lo que se va a sincronizar:", "SyncResourcesModal.syncModalTitle": "Sincronizar recursos", @@ -1653,11 +1903,11 @@ "TermsOfServiceModal.communicationsP1": "Para propósitos contractuales, usted (1) da su consentimiento para recibir comunicaciones de nosotros en forma electrónica a través de la dirección de correo electrónico que ha regristrado o a través del Servicio; y (2) aceptar que todos los Términos de Servicio, acuerdos, avisos, revelaciones, y otras comunicaciones que le proporcionamos electrónicamente satisfacen cualquier requisito legal que dichas comunicaciones satisfagan si estuvieran en papel. Esta sección no afecta a sus derechos no renunciables.", "TermsOfServiceModal.communityStandardsHeader": "Normas de la comunidad", "TermsOfServiceModal.communityStandardsLink": "Aprende más sobre los estándares comunitarios de Studio", - "TermsOfServiceModal.communityStandardsP1": "Para obtener más información sobre el uso previsto del Servicio y las normas en torno a los Contenidos, consulte nuestra página de Normas Comunitarias.", + "TermsOfServiceModal.communityStandardsP1": "Para obtener más información sobre el uso previsto del Servicio y las normas en torno a los Contenidos, consulte nuestra página de Normas Comunitarias .", "TermsOfServiceModal.definitionsHeader": "Definiciones", "TermsOfServiceModal.definitionsP1": "Estos son los Términos de la aplicación web alojados en https://studio.learningequality.org/, junto con cualquier API u otras interfaces que proporciona (el \"Servicio\"), controlada y operada por Learning Equality (\"Learning Equality\", \"nosotros\", \"nosotros\" y \"nuestro\"). Estamos registrados como una organización sin fines de lucro en California, Estados Unidos bajo EIN 46-2676188, y tener nuestra sede social en 9700 Gilman Dr, PMB 323, La Jolla, CA 92093.", "TermsOfServiceModal.definitionsP2": "Estos Términos describen nuestros compromisos con usted, y sus derechos y responsabilidades al usar el Servicio. Si viola cualquiera de estos Términos, su derecho de acceso y uso del Servicio se rescindirá. Por favor, léalos detenidamente y comuníquenos si tiene alguna pregunta.", - "TermsOfServiceModal.definitionsP3": "\"Contenido\" se refiere a ficheros multimedia (como vídeos, archivos de audio, contenido HTML5, u otros materiales) que estén alojados en el Servicio, junto con sus metadatos descriptivos asociados.", + "TermsOfServiceModal.definitionsP3": "\"Content\" refers to media files (such as videos, audio files, HTML5 content, or other materials) that are hosted on the Service, along with their associated descriptive metadata.", "TermsOfServiceModal.definitionsP4": "A lo largo de estos términos, \"usted\" se aplica tanto a individuos como a entidades que acceden o utilicen el Servicio. Si usted es una persona que utiliza el Servicio en nombre de una entidad, usted representa y garantiza que usted tiene la autoridad para vincular a esa entidad al Acuerdo y eso mediante nuestro Servicio, acepta el Acuerdo en nombre de dicha entidad.", "TermsOfServiceModal.disclaimerP1": "Antes de utilizar este sitio web, debe leer la siguiente información importante relacionada con el mismo. Estos Términos de Servicio (\"Términos\") rigen su uso de este sitio web y forman un acuerdo legalmente vinculante entre usted y nosotros con respecto a su uso de nuestro sitio web.", "TermsOfServiceModal.disclaimerP2": "Si, por alguna razón, no puede o no desea aceptar todos estos términos, por favor interrumpa inmediatamente el uso del servicio.", @@ -1672,13 +1922,13 @@ "TermsOfServiceModal.jurisdictionHeader": "Jurisdicción y Ley Aplicable", "TermsOfServiceModal.jurisdictionP1": "Excepto en la medida en que cualquier ley aplicable establece lo contrario, el Acuerdo y cualquier acceso o uso del Servicio se regirán por las leyes del estado de California, E.U.A., excluyendo su conflicto de disposiciones legales. El lugar adecuado para cualquier controversia que surja o esté relacionado con el Acuerdo y cualquier acceso o uso del Servicio será los tribunales estatales y federales ubicados en el Condado de San Diego, California.", "TermsOfServiceModal.liabilityHeader": "Limitación de responsabilidad", - "TermsOfServiceModal.liabilityP1": "En la medida en que legalmente lo permita la ley aplicable, Learning Equality no será responsable de ninguna pérdida o daño para usted, sus clientes o terceros causados por la falta de funcionamiento del sitio web. En ningún caso Learning Equality será responsable de cualquier daño especial, consecuente, incidental o indirecto (incluyendo, sin limitación, los resultantes de la pérdida de beneficios, coste de bienes o servicios sustitutivos pérdida de datos o interrupción del negocio) en relación con el uso del sitio web o del Servicio en relación con cualquier otra reclamación que surja de estas Condiciones de Servicio. La responsabilidad agregada de Learning Equality surge de o relacionada con estos Términos y el Servicio, independientemente de la forma de acción o reclamación (contrato, o de otro tipo) e incluso si se le ha avisado de la posibilidad de tales daños no excederá de la cantidad pagada por usted durante el periodo de doce (12) meses previo a la causa de la acción. Nada de lo dispuesto en estas Condiciones limitará o excluirá la responsabilidad de Learning Equality por negligencia grave o por muerte o lesiones personales. La ley aplicable puede no permitir la exclusión o limitación de daños incidentales o consecuentes, por lo que la limitación o exclusión anterior puede no aplicarse a usted.", + "TermsOfServiceModal.liabilityP1": "To the extent legally permitted under the applicable law, Learning Equality shall not be responsible for any loss or damage to you, your customers or third parties caused by failure of the website to function. In no event will Learning Equality be liable for any special, consequential, incidental, or indirect damages (including, without limitation, those resulting from lost profits, cost of substitute goods or services, lost data or business interruption) in connection with the use of the website or Service of in connection with any other claim arising from these Terms of Service. The aggregate liability of Learning Equality arising from or relating to these Terms and the Service, regardless of the form of action or claim (contract, tort or otherwise) and even if you have been advised of the possibility of such damages shall not exceed the amount paid by you during the twelve (12) month period prior to the cause of action. Nothing in these Terms shall limit or exclude Learning Equality liability for gross negligence or for death or personal injury. Applicable law may not allow the exclusion or limitation of incidental or consequential damages, so the above limitation or exclusion may not apply to you.", "TermsOfServiceModal.licensingHeader": "Licencias y derechos de autor", "TermsOfServiceModal.licensingList1Item1": "La propiedad de derechos de autor sobre el contenido es conservada por el titular original del copyright y debe ser indicada, y la información de la licencia debe ser marcada para reflejar con precisión las intenciones del titular de los derechos de autor acerca de la distribución y el uso de dicho Contenido.", "TermsOfServiceModal.licensingList1Item2": "Si no eres el titular de los derechos de autor, debes tener los derechos de distribuir el contenido subido, ya sea a través de un permiso escrito explícito del titular del derecho de autor, o según lo permitido por los términos de la licencia bajo la cual se ha liberado el Contenido.", "TermsOfServiceModal.licensingList1Item3": "Si usted es el titular de los derechos de autor del contenido subido, entonces marcando el contenido que carga con una licencia en particular, usted está de acuerdo en que el Contenido se distribuya y se utilice bajo los términos de esa licencia en autoridad.", - "TermsOfServiceModal.licensingList2Item1": "Metadatos descriptivos: Esto incluye metadatos principales asociados con una sola pieza de contenido, por ejemplo, títulos, descripciones, así como otros elementos que constituyen una parte definitiva del Contenido, independientemente del sistema en el que aparezca. Estos elementos de metadatos estarán bajo los mismos derechos de autor y licencias que el propio Contenido.", - "TermsOfServiceModal.licensingList2Item2": "Metadatos de organización: Definen cómo se puede utilizar un elemento de contenido, ayudan su descubrimiento, y lo coloca dentro de una estructura más amplia de las relaciones en el Servicio, por ejemplo, etiquetas, curación de carpetas (incluyendo los títulos de estas carpetas), y otros elementos pertenecientes a la visualización y ordenación de Contenido en el propio sistema. Al utilizar el Servicio, usted acepta que el trabajo que realiza para generar elementos de metadatos organizacionales se publique como el dominio público, y puede ser puestos a disposición de otros para su uso, sin ninguna reclamación sobre derechos de autor o licencias restringidas. También podemos compartir, aprovechar y distribuir estos metadatos organizacionales. Esto se establece para que podamos beneficiar a otros y mejorar el impacto de nuestras plataformas.", + "TermsOfServiceModal.licensingList2Item1": "Metadatos descriptivos: Esto incluye metadatos principales asociados con una sola pieza de contenido, por ejemplo, títulos, , así como otros elementos que constituyen una parte definitiva del Contenido, independientemente del sistema en el que aparezca. Estos elementos de metadatos estarán bajo los mismos derechos de autor y licencias que el propio Contenido.", + "TermsOfServiceModal.licensingList2Item2": "Organizational metadata: This defines how a piece of content may be used, aids with discovery, and places it within some broader structure of relations on the Service, for example, tags, curation into folders (including the titles of those folders), and other elements pertaining to the display and ordering of Content on the system itself. By using the Service, you agree that work you do to generate organizational metadata elements are released into the Public Domain, and may be made available for others to use, without any claim to copyright or restricted licensing. We may also share, leverage and distribute this organizational metadata. This is so that we can benefit others and improve the impact of our platforms.", "TermsOfServiceModal.licensingP1": "El servicio le permite cargar y distribuir contenidos. Cuando lo haga, se aplican los siguientes términos:", "TermsOfServiceModal.licensingP2": "Seguimos una política de hacer que el contenido, incluyendo sus metadatos asociados, sea lo más abierto posible mientras seguimos las leyes de derechos de autor apropiadas. Con esto en mente, distinguimos entre el:", "TermsOfServiceModal.miscellaneousHeader": "Miscellaneous", @@ -1707,7 +1957,7 @@ "TermsOfServiceModal.userContentP2": "Tampoco hemos revisado, ni podemos revisar, todo el material disponible a través de los sitios web y páginas web que enlazan o están enlazadas desde el Servicio. Por ejemplo:", "TermsOfServiceModal.userContentP3": "Nos reservamos el derecho de eliminar cualquier Contenido que viole nuestros Términos o por cualquier otra razón.", "TermsOfServiceModal.userContentP4": "Tenga en cuenta que no podemos:", - "TermsOfServiceModal.warrantyHeader": "Exención de garantías", + "TermsOfServiceModal.warrantyHeader": "Exención de garantías ", "TermsOfServiceModal.warrantyHeaderP1": "Usted reconoce que el sitio web y el Servicio se proporcionan \"tal cual\" y \"según disponibilidad\", con todos los defectos y sin garantía de ningún tipo, y aquí declinamos todas las garantías y condiciones con respecto al sitio web y al Servicio, ya sea expresamente, implícito o estatutario, incluyendo, pero no limitado a, cualquier garantía implícita y/o condiciones de comerciabilidad, de calidad satisfactoria, de aptitud para un fin particular, de exactitud, y de no violación de los derechos de terceros. Cualquier uso del Servicio y del sitio web es bajo su propio riesgo. Algunas jurisdicciones no permiten la exclusión de garantías implícitas, por lo que es posible que las limitaciones anteriores no se apliquen a usted.", "TermsOfServiceModal.yourPrivacyHeader": "Su privacidad", "TermsOfServiceModal.yourPrivacyLink": "Más información sobre la política de privacidad de Studio", @@ -1715,79 +1965,98 @@ "TextArea.fieldRequiredMessage": "Este campo es obligatorio", "TextField.fieldRequiredMessage": "Este campo es obligatorio", "Thumbnail.thumbnail": "{title} miniatura", + "ThumbnailGenerator.closeButtonLabel": "Aceptar", "ThumbnailGenerator.generatedDefaultFilename": "Miniatura generada", "ThumbnailGenerator.thumbnailGenerationFailedHeader": "No se puede generar la miniatura", "ThumbnailGenerator.thumbnailGenerationFailedText": "Hubo un problema al generar una miniatura", - "TipTapEditorStrings.addLink": "Add link", - "TipTapEditorStrings.altTextDescription": "Alt text is necessary to enable visually impaired learners to answer questions, and it also displays when the image fails to load", - "TipTapEditorStrings.altTextLabel": "Alt text (Optional)", - "TipTapEditorStrings.altTextPlaceholder": "Describe your image...", - "TipTapEditorStrings.bold": "Strong", - "TipTapEditorStrings.bulletList": "Bullet list", + "TipTapEditorStrings.TipTapEditorLabel": "editor de texto - Pulse Enter para empezar a editar", + "TipTapEditorStrings.TipTapViewerLabel": "contenido del editor de texto", + "TipTapEditorStrings.addLink": "Añadir enlace", + "TipTapEditorStrings.alignLeft": "Alinear a la izquierda", + "TipTapEditorStrings.alignRight": "Alinear a la derecha", + "TipTapEditorStrings.altTextDescription": "La descripción textual (Alt text - texto alternativo) es necesario para permitir que los estudiantes con discapacidad visual respondan a las preguntas, y también se muestra cuando la imagen no se carga", + "TipTapEditorStrings.altTextLabel": "Descripción textual (opcional)", + "TipTapEditorStrings.altTextPlaceholder": "Describir la imagen...", + "TipTapEditorStrings.bold": "Negrita", + "TipTapEditorStrings.bulletList": "Lista sin ordenar", "TipTapEditorStrings.cancel": "Cancelar", - "TipTapEditorStrings.cancelLoading": "Cancel loading", - "TipTapEditorStrings.clipboardAccessFailed": "Clipboard access failed. Try copying again.", + "TipTapEditorStrings.cancelLoading": "Cancelar la carga", + "TipTapEditorStrings.clearFormatting": "Borrar formato", + "TipTapEditorStrings.clipboardAccessFailed": "Error al acceder al portapapeles. Intente copiarlo de nuevo.", "TipTapEditorStrings.close": "Cerrar", - "TipTapEditorStrings.closeModal": "Close modal", - "TipTapEditorStrings.codeBlock": "Code block", + "TipTapEditorStrings.closeModal": "Cerrar ventana", + "TipTapEditorStrings.codeBlock": "Bloque de código", + "TipTapEditorStrings.collapseFormattingBar": "Colapsar barra de formato", "TipTapEditorStrings.copy": "Copiar", - "TipTapEditorStrings.copyAndPasteActions": "Copy and paste actions", - "TipTapEditorStrings.copyLink": "Copy link", + "TipTapEditorStrings.copyAndPasteActions": "Copiar y pegar", + "TipTapEditorStrings.copyLink": "Copiar enlace", + "TipTapEditorStrings.decreaseFormatSize": "Reducir tamaño", "TipTapEditorStrings.defaultImageName": "Imagen", "TipTapEditorStrings.edit": "Editar", - "TipTapEditorStrings.editImage": "Edit image", - "TipTapEditorStrings.editLink": "Edit link", - "TipTapEditorStrings.failedToProcessImage": "Failed to process the image file.", + "TipTapEditorStrings.editImage": "Editar imagen", + "TipTapEditorStrings.editLink": "Editar enlace", + "TipTapEditorStrings.editorControls": "Controles del editor", + "TipTapEditorStrings.errorUploadingImage": "Error al subir la imagen", + "TipTapEditorStrings.expandFormattingBar": "Expandir barra de formato", + "TipTapEditorStrings.failedToProcessImage": "Error al procesar el archivo de imagen.", "TipTapEditorStrings.fileSizeUnit": "MB.", - "TipTapEditorStrings.fileTooLarge": "File is too large. Maximum size is ", - "TipTapEditorStrings.formatHeader1": "Header 1", - "TipTapEditorStrings.formatHeader2": "Header 2", - "TipTapEditorStrings.formatHeader3": "Header 3", + "TipTapEditorStrings.fileTooLarge": "El archivo es demasiado grande. El tamaño máximo es ", + "TipTapEditorStrings.formatHeader1": "Encabezado nivel 1", + "TipTapEditorStrings.formatHeader2": "Encabezado nivel 2", + "TipTapEditorStrings.formatHeader3": "Encabezado nivel 3", "TipTapEditorStrings.formatNormal": "Normal", - "TipTapEditorStrings.formatOptions": "Format options", + "TipTapEditorStrings.formatOptions": "Opciones de formato", + "TipTapEditorStrings.formatSize": "Tamaño", "TipTapEditorStrings.formatSmall": "Pequeño", - "TipTapEditorStrings.formulasMenuTitle": "Special Characters", - "TipTapEditorStrings.goToLink": "Go to link", - "TipTapEditorStrings.historyActions": "History actions", - "TipTapEditorStrings.imageDropZoneText": "Drag and drop an image here or upload manually", - "TipTapEditorStrings.imagePreview": "Image preview", + "TipTapEditorStrings.formulasMenuTitle": "Caracteres especiales", + "TipTapEditorStrings.goToLink": "Ir al enlace", + "TipTapEditorStrings.historyActions": "Historial de acciones", + "TipTapEditorStrings.imageDropZoneText": "Arrastrar y soltar una imagen aquí, o subirla manualmente", + "TipTapEditorStrings.imagePreview": "Vista previa de imagen", + "TipTapEditorStrings.increaseFormatSize": "Aumentar tamaño", "TipTapEditorStrings.insert": "Insertar", - "TipTapEditorStrings.insertImage": "Insert image", - "TipTapEditorStrings.insertLink": "Insert link", - "TipTapEditorStrings.insertTools": "Insert tools", - "TipTapEditorStrings.invalidFileType": "Invalid file type. Please use: ", + "TipTapEditorStrings.insertContent": "Insertar", + "TipTapEditorStrings.insertContentMenu": "Menú insertar", + "TipTapEditorStrings.insertContentOption": "Insertar", + "TipTapEditorStrings.insertImage": "Insertar imagen", + "TipTapEditorStrings.insertLink": "Insertar enlace", + "TipTapEditorStrings.insertTools": "Herramientas para insertar", + "TipTapEditorStrings.invalidFileType": "Tipo de archivo no válido. Por favor usar: ", "TipTapEditorStrings.italic": "Cursiva", - "TipTapEditorStrings.link": "Link", - "TipTapEditorStrings.linkActions": "Link actions", - "TipTapEditorStrings.listFormatting": "List formatting", - "TipTapEditorStrings.mathFormula": "Math formula", - "TipTapEditorStrings.multipleFilesDroppedWarning": "Multiple files were dropped. Only the first file has been selected.", - "TipTapEditorStrings.noFileProvided": "No file provided.", - "TipTapEditorStrings.numberedList": "Numbered list", - "TipTapEditorStrings.opensInNewTab": "(opens in new tab)", - "TipTapEditorStrings.paste": "Paste", - "TipTapEditorStrings.pasteOptions": "Paste options", - "TipTapEditorStrings.pasteOptionsMenu": "Paste options menu", - "TipTapEditorStrings.pasteWithoutFormatting": "Paste without formatting", + "TipTapEditorStrings.link": "Enlace", + "TipTapEditorStrings.linkActions": "Acciones de enlaces", + "TipTapEditorStrings.listFormatting": "Formato de lista", + "TipTapEditorStrings.loadingFormulas": "Cargando editor de matemáticas", + "TipTapEditorStrings.mathFormula": "Fórmula matemática", + "TipTapEditorStrings.moreButtonText": "Más", + "TipTapEditorStrings.multipleFilesDroppedWarning": "Múltiples archivos fueron eliminados. Solo el primer archivo ha sido seleccionado.", + "TipTapEditorStrings.noEnoughStorageSpace": "No hay suficiente espacio de almacenamiento disponible. El tamaño del archivo excede el límite de almacenamiento.", + "TipTapEditorStrings.noFileProvided": "No se ha proporcionado ningún archivo", + "TipTapEditorStrings.numberedList": "Lista numerada", + "TipTapEditorStrings.opensInNewTab": "(se abre en una nueva pestaña)", + "TipTapEditorStrings.paste": "Pegar", + "TipTapEditorStrings.pasteOptions": "Opciones de pegar", + "TipTapEditorStrings.pasteOptionsMenu": "Menú de opciones de pegado", + "TipTapEditorStrings.pasteWithoutFormatting": "Pegar sin formato", "TipTapEditorStrings.redo": "Rehacer", "TipTapEditorStrings.remove": "Eliminar", - "TipTapEditorStrings.removeImage": "Remove image", - "TipTapEditorStrings.removeLink": "Remove link", - "TipTapEditorStrings.replaceFile": "Replace file", + "TipTapEditorStrings.removeImage": "Eliminar imagen", + "TipTapEditorStrings.removeLink": "Eliminar enlace", + "TipTapEditorStrings.replaceFile": "Sustituir fichero", "TipTapEditorStrings.save": "Guardar", "TipTapEditorStrings.saveChanges": "Guardar cambios", - "TipTapEditorStrings.scriptFormatting": "Script formatting", + "TipTapEditorStrings.scriptFormatting": "Formato de script", "TipTapEditorStrings.selectFile": "Seleccionar fichero", - "TipTapEditorStrings.selectFileToUpload": "Select file to upload", - "TipTapEditorStrings.strikethrough": "Strikethrough", + "TipTapEditorStrings.selectFileToUpload": "Seleccionar archivo a cargar", + "TipTapEditorStrings.strikethrough": "Tachado", "TipTapEditorStrings.subscript": "Subíndice", "TipTapEditorStrings.superscript": "Superíndice", - "TipTapEditorStrings.supportedFileTypes": "Supported file types: png, jpg, jpeg, svg, webp", - "TipTapEditorStrings.text": "Text", - "TipTapEditorStrings.textFormatOptions": "Text format options", - "TipTapEditorStrings.textFormattingOptions": "Text formatting options", - "TipTapEditorStrings.textFormattingToolbar": "Text formatting toolbar", - "TipTapEditorStrings.textStyleFormatting": "Text style formatting", + "TipTapEditorStrings.supportedFileTypes": "Tipos de ficheros permitidos: { extensions }", + "TipTapEditorStrings.text": "Texto", + "TipTapEditorStrings.textFormatOptions": "Opciones de formato de texto", + "TipTapEditorStrings.textFormattingOptions": "Opciones de formato de texto", + "TipTapEditorStrings.textFormattingToolbar": "Barra de herramientas de formato de texto", + "TipTapEditorStrings.textStyleFormatting": "Formato de estilo de texto", "TipTapEditorStrings.underline": "Subrayar", "TipTapEditorStrings.undo": "Deshacer", "TipTapEditorStrings.uploadImage": "Subir imagen", @@ -1814,25 +2083,26 @@ "TreeView.showSidebar": "Show sidebar", "TreeView.updatedResourcesReadyForReview": "Los recursos actualizados están listos para su revisión", "TreeViewBase.apiGenerated": "Generado con la API", - "TreeViewBase.cancel": "Cancelar", "TreeViewBase.channelDeletedSnackbar": "Canal eliminado", "TreeViewBase.channelDetails": "Ver detalles del canal", "TreeViewBase.deleteChannel": "Eliminar canal", - "TreeViewBase.deleteChannelButton": "Eliminar canal", - "TreeViewBase.deletePrompt": "Este canal se eliminará permanentemente. Esto no se puede deshacer.", - "TreeViewBase.deleteTitle": "Eliminar este canal", "TreeViewBase.editChannel": "Editar detalles del canal", "TreeViewBase.emptyChannelTooltip": "No se puede publicar un canal vacío", "TreeViewBase.getToken": "Obtener el token", "TreeViewBase.incompleteDescendantsText": "{count, number, integer} {count, plural, one {el recurso está incompleto y no se puede publicar} other {los recursos están incompletos y no se pueden publicar}}", + "TreeViewBase.inviteCollaborators": "Invitar colaboradores", "TreeViewBase.noChangesText": "No se encontraron cambios en el canal", "TreeViewBase.noLanguageSetError": "El idioma del canal es obligatorio", "TreeViewBase.openTrash": "Abrir papelera", "TreeViewBase.publishButton": "Publicar", "TreeViewBase.publishButtonTitle": "Hacer este canal disponible para importar en Kolibri", "TreeViewBase.shareChannel": "Compartir canal", + "TreeViewBase.shareMenuButton": "Compartir", + "TreeViewBase.shareToken": "Compartir token", + "TreeViewBase.submitToCommunityLibrary": "Enviar a la Biblioteca comunitaria", "TreeViewBase.syncChannel": "Sincronizar recursos", "TreeViewBase.viewOnly": "Solo modo de lectura", + "Uploader.closeButtonLabel": "Aceptar", "Uploader.listDelimiter": ", ", "Uploader.maxFileSizeText": "{count, plural,\n =1 {# fichero no será subido.}\n other {# fichero no serán subidos.}} El tamaño del fichero debe ser inferior a {size}", "Uploader.noStorageHeader": "No hay suficiente espacio", @@ -1876,7 +2146,7 @@ "channelEditVue.questionTypeInput": "Entrada numérica", "channelEditVue.questionTypeMultipleSelection": "Selección múltiple", "channelEditVue.questionTypePerseus": "Perseus", - "channelEditVue.questionTypeSingleSelection": "Respuesta única", + "channelEditVue.questionTypeSingleSelection": "Selección única", "channelEditVue.questionTypeTrueFalse": "Verdadero/Falso", "channelEditVue.selectionCount": "{topicCount, plural, =0 {} one {# carpeta, } other {# carpetas, }}{resourceCount, plural, one {# recurso} other {# recursos}}", "channelEditVue.true": "Verdadero", @@ -1906,4 +2176,4 @@ "sharedVue.masteryModelRequired": "Tiene que seleccionar el criterio de dominio", "sharedVue.shortActivityLteThirty": "El valor debe ser igual o menor que 30", "sharedVue.titleRequired": "Este campo es obligatorio" -} +} \ No newline at end of file diff --git a/contentcuration/locale/es_ES/LC_MESSAGES/django.po b/contentcuration/locale/es_ES/LC_MESSAGES/django.po index 3073c45a4b..76fe11bfea 100644 --- a/contentcuration/locale/es_ES/LC_MESSAGES/django.po +++ b/contentcuration/locale/es_ES/LC_MESSAGES/django.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: kolibri-studio\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-09-03 19:45+0000\n" -"PO-Revision-Date: 2025-09-12 13:59\n" +"POT-Creation-Date: 2026-04-16 00:57+0000\n" +"PO-Revision-Date: 2026-04-20 19:33\n" "Last-Translator: \n" "Language-Team: Spanish\n" "Language: es_ES\n" @@ -14,8 +14,8 @@ msgstr "" "X-Crowdin-Project: kolibri-studio\n" "X-Crowdin-Project-ID: 286000\n" "X-Crowdin-Language: es-ES\n" -"X-Crowdin-File: /search-recommendations-closed-beta/django.po\n" -"X-Crowdin-File-ID: 4886\n" +"X-Crowdin-File: /unstable/django.po\n" +"X-Crowdin-File-ID: 4322\n" #: contentcuration/catalog_settings.py:4 contentcuration/sandbox_settings.py:8 #: contentcuration/settings.py:271 @@ -24,27 +24,27 @@ msgstr "Árabe" #: contentcuration/middleware/db_readonly.py:25 msgid "The site is currently in read-only mode. Please try again later." -msgstr "La plataforma está actualmente en modo de solo lectura. Por favor, inténtelo más tarde." +msgstr "El sitio está actualmente en modo de solo lectura. Por favor, inténtelo más tarde." -#: contentcuration/models.py:342 +#: contentcuration/models.py:356 msgid "Not enough space. Check your storage under Settings page." msgstr "No hay suficiente espacio. Compruebe el almacenamiento disponible en la página de Configuración." -#: contentcuration/models.py:374 contentcuration/models.py:386 +#: contentcuration/models.py:440 contentcuration/models.py:452 msgid "Out of storage! Request more space under Settings > Storage." msgstr "El espacio de almacenamiento es insuficiente. Solicite más espacio en Configuración > Almacenamiento." -#: contentcuration/models.py:2155 +#: contentcuration/models.py:2531 msgid " (Original)" msgstr " (Original)" -#: contentcuration/models.py:3297 +#: contentcuration/models.py:3848 msgid "Created DateTime" -msgstr "Fecha y hora de creación" +msgstr "" -#: contentcuration/models.py:3299 +#: contentcuration/models.py:3850 msgid "Datetime field when the custom_metadata for task was created in UTC" -msgstr "Campo de fecha cuando el custom_metadata para la tarea fue creado en UTC" +msgstr "" #: contentcuration/settings.py:269 msgid "English" @@ -653,11 +653,11 @@ msgstr "Tipo" #: contentcuration/utils/csv_writer.py:89 msgid "Filename" -msgstr "Nombre del archivo" +msgstr "Nombre del fichero" #: contentcuration/utils/csv_writer.py:90 msgid "File Size" -msgstr "Tamaño del archivo" +msgstr "Tamaño del fichero" #: contentcuration/utils/csv_writer.py:91 msgid "URL" @@ -719,7 +719,7 @@ msgstr "Hubo un problema con un servicio externo. Esto significa que no es posib msgid "We are encountering issues with our data center. This means you may encounter networking problems while using Studio. We appreciate your patience while these issues are being resolved. To check the status of this service, please visit here" msgstr "Estamos encontrando problemas con nuestro centro de datos. Esto significa que puede encontrar problemas de red mientras utiliza Studio. Agradecemos su paciencia mientras se resuelvenn estos problemas. Para comprobar el estado de este servicio visite este enlace" -#: contentcuration/utils/publish.py:101 +#: contentcuration/utils/publish.py:103 msgid "Kolibri Studio Channel Published" msgstr "Canal publicado en Kolibri Studio" @@ -731,14 +731,15 @@ msgstr "Informe de incidencias de Kolibri Studio" msgid "Kolibri Studio account deleted" msgstr "Cuenta de Kolibri Studio eliminada" -#: kolibri_public/views.py:223 +#: kolibri_public/views.py:323 msgid "Resource" msgstr "Recurso" -#: kolibri_public/views_v1.py:79 kolibri_public/views_v1.py:94 +#: kolibri_public/views_v1.py:152 kolibri_public/views_v1.py:167 msgid "API version is unavailable" msgstr "La versión API no está disponible" -#: kolibri_public/views_v1.py:97 +#: kolibri_public/views_v1.py:170 msgid "No channel matching {} found" msgstr "Ningún canal con {} encontrado" + diff --git a/contentcuration/locale/fr_FR/LC_MESSAGES/contentcuration-messages.json b/contentcuration/locale/fr_FR/LC_MESSAGES/contentcuration-messages.json index 23aa4b3f1b..f34068733c 100644 --- a/contentcuration/locale/fr_FR/LC_MESSAGES/contentcuration-messages.json +++ b/contentcuration/locale/fr_FR/LC_MESSAGES/contentcuration-messages.json @@ -67,8 +67,6 @@ "AdministrationAppError.unauthorizedDetails": "Il faut être administrateur de Studio pour voir cette page", "AdministrationIndex.channelsLabel": "Chaînes", "AdministrationIndex.usersLabel": "Utilisateurs", - "Alert.closeButtonLabel": "OK", - "Alert.dontShowAgain": "Ne plus afficher ce message", "AnswersEditor.answersLabel": "Réponses", "AnswersEditor.newAnswerBtnLabel": "Nouvelle réponse", "AnswersEditor.noAnswersPlaceholder": "La question ne comporte pas d'options de réponse", @@ -184,26 +182,31 @@ "CatalogFilterBar.keywords": "\"{text}\"", "CatalogFilterBar.starred": "Favorites", "CatalogFilterBar.subtitles": "Sous-titres", - "CatalogFilters.coachDescription": "Sur Kolibri, les ressources destinées aux éducateurs ne sont visibles que par eux", - "CatalogFilters.coachLabel": "Ressources pour les éducateurs", - "CatalogFilters.copyright": "© {year} Learning Equality", - "CatalogFilters.formatLabel": "Formats", - "CatalogFilters.frequentlyAskedQuestionsLink": "Foire aux questions", - "CatalogFilters.includesLabel": "N'afficher que les chaînes avec", - "CatalogFilters.licenseLabel": "Licences", - "CatalogFilters.searchLabel": "Mots clés", - "CatalogFilters.searchText": "Rechercher", - "CatalogFilters.starredLabel": "Favorites", - "CatalogFilters.subtitlesLabel": "Légendes ou sous-titres", + "CatalogFilterPanelContent.coachDescription": "Sur Kolibri, les ressources destinées aux éducateurs ne sont visibles que par eux", + "CatalogFilterPanelContent.coachLabel": "Ressources pour les éducateurs", + "CatalogFilterPanelContent.copyright": "© {year} Learning Equality", + "CatalogFilterPanelContent.formatLabel": "Formats", + "CatalogFilterPanelContent.frequentlyAskedQuestionsLink": "Foire aux questions", + "CatalogFilterPanelContent.includesLabel": "N'afficher que les chaînes avec", + "CatalogFilterPanelContent.licenseLabel": "Licences", + "CatalogFilterPanelContent.searchLabel": "Mots clés", + "CatalogFilterPanelContent.starredLabel": "Favorite", + "CatalogFilterPanelContent.subtitlesLabel": "Légendes ou sous-titres", + "CatalogFilters.filterLabel": "Filtrer", "CatalogList.cancelButton": "Annuler", "CatalogList.channelSelectionCount": "{count, plural, one {}\n =1 {# chaîne sélectionnée}\n other {# chaînes sélectionnées}}", + "CatalogList.copyToken": "Copier le jeton de la chaîne", "CatalogList.downloadButton": "Télécharger", "CatalogList.downloadCSV": "Télécharger au format CSV", "CatalogList.downloadPDF": "Télécharger au format PDF", "CatalogList.downloadingMessage": "Téléchargement en cours", + "CatalogList.goToWebsite": "Aller sur le site web source", + "CatalogList.moreOptions": "Plus d'options", "CatalogList.resultsText": "{count, plural, one {}\n =1 {# résultat obtenu}\n other {# résultats obtenus}}", "CatalogList.selectAll": "Tout sélectionner", "CatalogList.selectChannels": "Télécharger un résumé des chaînes sélectionnées", + "CatalogList.title": "Bibliothèque de contenus", + "CatalogList.viewContent": "Afficher la chaîne sur Kolibri", "CategoryOptions.noCategoryFoundText": "Catégorie introuvable", "ChangePasswordForm.cancelAction": "Annuler", "ChangePasswordForm.changePasswordHeader": "Changer de mot de passe", @@ -224,9 +227,6 @@ "ChannelCatalogFrontPage.languagesHeading": "Langues", "ChannelCatalogFrontPage.numberOfChannels": "{ num } chaînes", "ChannelCatalogFrontPage.subtitlesIncludedText": "Légendes ou sous-titres", - "ChannelDeletedError.backToHomeAction": "Retour à l'accueil", - "ChannelDeletedError.channelDeletedDetails": "Cette chaîne n'existe pas ou peut avoir été supprimée. Si vous croyez qu'il s'agit d'une erreur, veuillez nous contacter à l'adresse content@learningequality.org.", - "ChannelDeletedError.channelDeletedHeader": "Chaîne introuvable", "ChannelDetailsModal.downloadButton": "Télécharger le résumé de la chaîne", "ChannelDetailsModal.downloadCSV": "Télécharger au format CSV", "ChannelDetailsModal.downloadPDF": "Télécharger au format PDF", @@ -263,23 +263,17 @@ "ChannelInvitation.editText": "{sender} vous a invité à modifier {channel}", "ChannelInvitation.goToChannelSnackbarAction": "Aller sur la chaîne", "ChannelInvitation.viewText": "{sender} vous a invité à visualiser {channel}", - "ChannelItem.cancel": "Annuler", "ChannelItem.channelDeletedSnackbar": "Chaîne supprimée", "ChannelItem.channelLanguageNotSetIndicator": "Aucune langue définie", "ChannelItem.channelRemovedSnackbar": "Chaîne supprimée", "ChannelItem.copyToken": "Copier le jeton de la chaîne", "ChannelItem.deleteChannel": "Supprimer la chaîne", - "ChannelItem.deletePrompt": "Cette chaîne sera définitivement supprimée. Cette action est irréversible.", - "ChannelItem.deleteTitle": "Supprimer cette chaîne", "ChannelItem.details": "Détails", "ChannelItem.editChannel": "Modifier les détails de la chaîne", "ChannelItem.goToWebsite": "Aller sur le site web source", "ChannelItem.lastPublished": "Publié le {last_published}", "ChannelItem.lastUpdated": "Mis à jour {updated}", - "ChannelItem.removeBtn": "Supprimer", - "ChannelItem.removeChannel": "Retirer de la liste des chaînes", - "ChannelItem.removePrompt": "Vous disposez d’un accès en lecture seule à cette chaîne. Confirmez que vous souhaitez la retirer de votre liste de chaînes.", - "ChannelItem.removeTitle": "Retirer de la liste de chaînes", + "ChannelItem.removeChannel": "Remove channel", "ChannelItem.resourceCount": "{count, plural, one {}\n =1 {# ressource}\n other {# ressources}}", "ChannelItem.unpublishedText": "Non publié", "ChannelItem.versionText": "Version {version}", @@ -292,7 +286,6 @@ "ChannelListIndex.catalog": "Bibliothèque de contenus", "ChannelListIndex.channelSets": "Recueils", "ChannelListIndex.frequentlyAskedQuestions": "Foire aux questions", - "ChannelListIndex.invitations": "Vous avez {count, plural, one {}\n =1 {# invitation}\n other {# invitations}}", "ChannelListIndex.libraryTitle": "Catalogue de la bibliothèque de contenus Kolibri", "ChannelModal.APIText": "Les chaînes générées automatiquement ne sont pas modifiables.", "ChannelModal.changesSaved": "Modifications enregistrées", @@ -316,25 +309,6 @@ "ChannelNotFoundError.channelNotFoundHeader": "Chaîne introuvable", "ChannelSelectionList.noChannelsFound": "Aucune chaîne trouvée", "ChannelSelectionList.searchText": "Rechercher une chaîne", - "ChannelSetItem.cancel": "Annuler", - "ChannelSetItem.delete": "Supprimer le recueil", - "ChannelSetItem.deleteChannelSetText": "Êtes-vous sûr de vouloir supprimer ce recueil ?", - "ChannelSetItem.deleteChannelSetTitle": "Supprimer le recueil ", - "ChannelSetItem.edit": "Modifier le recueil", - "ChannelSetItem.options": "Options", - "ChannelSetItem.saving": "Enregistrement en cours", - "ChannelSetList.aboutChannelSets": "À propos des recueils", - "ChannelSetList.aboutChannelSetsLink": "En savoir plus sur les recueils", - "ChannelSetList.addChannelSetTitle": "Nouveau recueil", - "ChannelSetList.cancelButtonLabel": "Fermer", - "ChannelSetList.channelNumber": "Nombre de chaînes", - "ChannelSetList.channelSetsDescriptionText": "Un recueil contient plusieurs chaînes de Kolibri Studio pouvant être importées en une seule fois dans Kolibri avec un seul jeton de recueil.", - "ChannelSetList.channelSetsDisclaimer": "Seule la version 0.12.0 ou supérieure de Kolibri permet d'importer des recueils de chaînes.", - "ChannelSetList.channelSetsInstructionsText": "Vous pouvez créer un recueil en sélectionnant les chaînes que vous souhaitez importer ensemble.", - "ChannelSetList.noChannelSetsFound": "Il est possible de regrouper plusieurs chaînes pour constituer un recueil. Vous pourrez ensuite importer tout le recueil dans Kolibri en une seule fois à l'aide d'un jeton de recueil.", - "ChannelSetList.options": "Options", - "ChannelSetList.title": "Nom du recueil", - "ChannelSetList.token": "ID du jeton", "ChannelSetModal.bookmark": "Favoris", "ChannelSetModal.channelAdded": "Chaîne ajoutée", "ChannelSetModal.channelCountText": "{channelCount, plural, =0 {Aucune chaîne publiée dans votre recueil} =1 {# chaîne} other {# chaînes}}", @@ -356,7 +330,7 @@ "ChannelSetModal.titleRequiredText": "Ce champ est obligatoire", "ChannelSetModal.token": "Jeton du recueil", "ChannelSetModal.tokenPrompt": "Pour pouvoir importer ce recueil sur votre appareil, copiez ce jeton dans Kolibri.", - "ChannelSetModal.unsavedChangesHeader": "Modifications non enregistrées", + "ChannelSetModal.unsavedChangesHeader": " Modifications non enregistrées", "ChannelSetModal.unsavedChangesText": "Vous perdrez toutes les modifications non enregistrées. Êtes-vous sûr de vouloir quitter ?", "ChannelSetModal.view": "Lecture seule", "ChannelSharing.alreadyHasAccessError": "L'utilisateur a déjà accès à cette chaîne", @@ -450,7 +424,7 @@ "CommonMetadataStrings.completion": "Achèvement", "CommonMetadataStrings.computerScience": "Informatique", "CommonMetadataStrings.create": "Créer", - "CommonMetadataStrings.currentEvents": "Événements en cours", + "CommonMetadataStrings.currentEvents": "Evènements en cours", "CommonMetadataStrings.dailyLife": "Vie quotidienne", "CommonMetadataStrings.dance": "Danse", "CommonMetadataStrings.determinedByResource": "Déterminé par la ressource", @@ -459,7 +433,7 @@ "CommonMetadataStrings.drama": "Drame", "CommonMetadataStrings.duration": "Durée", "CommonMetadataStrings.earthScience": "Sciences de la Terre", - "CommonMetadataStrings.entrepreneurship": "Entrepreneuriat", + "CommonMetadataStrings.entrepreneurship": "Entreprenariat", "CommonMetadataStrings.environment": "Environnement", "CommonMetadataStrings.exactTime": "Délai d'exécution", "CommonMetadataStrings.explore": "Explorer", @@ -533,6 +507,163 @@ "CommonMetadataStrings.webDesign": "Web design", "CommonMetadataStrings.work": "Professionnel", "CommonMetadataStrings.writing": "Écriture", + "CommonStrings.backAction": "Retour", + "CommonStrings.channelDetailsLabel": "Channel Details", + "CommonStrings.clearAction": "Supprimer", + "CommonStrings.closeAction": "Fermer", + "CommonStrings.copyChannelTokenAction": "Copier le jeton de la chaîne", + "CommonStrings.dismissAction": "Rejeter", + "CommonStrings.genericErrorMessage": "Désolé ! Une erreur est survenue. Veuillez réessayer.", + "CommonStrings.previewAction": "Aperçu", + "CommonStrings.seeAllAction": "See all", + "CommonStrings.seeLessAction": "See less", + "CommunityChannelsStrings.aboutCommunityLibraryDescription": "Community library contains channels submitted by the community and approved for discovery in Studio.", + "CommunityChannelsStrings.aboutCommunityLibraryTitle": "About Community Library", + "CommunityChannelsStrings.activityHistoryLabel": "Activity history", + "CommunityChannelsStrings.adminLabel": "Administrateur", + "CommunityChannelsStrings.allLicensesCompatible": "All licenses are compatible with Community Library.", + "CommunityChannelsStrings.allNotificationsLabel": "All Notifications", + "CommunityChannelsStrings.alreadySubmittedWarningDescription": "Please wait for the channel to be reviewed. You will see a notification in your Studio account after the review is complete.", + "CommunityChannelsStrings.alreadySubmittedWarningTitle": "This version of the channel has already been submitted to the Community Library.", + "CommunityChannelsStrings.approvedNotification": "{author} ({userType}) approved {channelVersion}", + "CommunityChannelsStrings.approvedPrimaryInfo": "A previous version is live in the Community Library. Reviewers will see the latest submission first.", + "CommunityChannelsStrings.approvedStatus": "Approved", + "CommunityChannelsStrings.availableStatus": "Available in Community Library", + "CommunityChannelsStrings.cancelAction": "Annuler", + "CommunityChannelsStrings.categoriesLabel": "Catégories", + "CommunityChannelsStrings.channelCannotBeDistributed": "This channel cannot be distributed via Kolibri.", + "CommunityChannelsStrings.channelFitAttributionLabel": "Attribution", + "CommunityChannelsStrings.channelFitChannelInfoLabel": "Channel information", + "CommunityChannelsStrings.channelFitChecklistAttribution": "Does each resource have an author listed?", + "CommunityChannelsStrings.channelFitChecklistChannelInfo": "Is your channel's basic information filled in — title, description, thumbnail, language, category, and level?", + "CommunityChannelsStrings.channelFitChecklistIntro": "Criteria for submitting to the Community Library", + "CommunityChannelsStrings.channelFitChecklistLicense": "Are the resources in your channel openly licensed or in the public domain?", + "CommunityChannelsStrings.channelFitChecklistOfflineUse": "Do the resources in your channel work without an Internet connection?", + "CommunityChannelsStrings.channelFitChecklistQuality": "Has anyone on your team or in your organization reviewed the channel?", + "CommunityChannelsStrings.channelFitLicenseLabel": "Licence", + "CommunityChannelsStrings.channelFitOfflineUseLabel": "Offline use", + "CommunityChannelsStrings.channelFitQualityLabel": "Quality", + "CommunityChannelsStrings.channelTokenDescription": "You can use this token to import and preview the draft channel in Kolibri. Please note that the token for the final published channel will be different.", + "CommunityChannelsStrings.channelVersion": "{name} v{version}", + "CommunityChannelsStrings.channelVersionTokenLabel": "Channel version token", + "CommunityChannelsStrings.clearAll": "Supprimer tout", + "CommunityChannelsStrings.clearAllAction": "Supprimer tout", + "CommunityChannelsStrings.communityLibraryCTADescription": "Have a channel worth sharing with other educators and learners? Submit it for review through the Share menu so it can be discovered in Studio.", + "CommunityChannelsStrings.communityLibraryCTATitle": "Help grow the Community Library", + "CommunityChannelsStrings.communityLibraryDescription": "Browse community-submitted channels approved for discovery in Studio. Copy a token to use a channel in Kolibri.", + "CommunityChannelsStrings.communityLibraryLabel": "Community Library", + "CommunityChannelsStrings.communityLibrarySubmissionLabel": "Community Library submission", + "CommunityChannelsStrings.compatibleLicensesDescription": "{licenseNames} - All licenses are compatible with Community Library.", + "CommunityChannelsStrings.confirmDistributionRights": "Please confirm you have the right to distribute these resources via Kolibri.", + "CommunityChannelsStrings.confirmReplacementText": "I understand this will replace my earlier submission on the review queue", + "CommunityChannelsStrings.countriesInfoText": "Select one or more countries to label your channel submission with. For example, if your channel contains materials aligned to a national curriculum or regionally-specific content, selecting the relevant countries will help users find it. Otherwise, leave this blank.", + "CommunityChannelsStrings.countryLabel": "Countries", + "CommunityChannelsStrings.descriptionLabel": "Describe what's included in this submission", + "CommunityChannelsStrings.dismissAction": "Rejeter", + "CommunityChannelsStrings.draftBeingPublishedNotice": "Draft version is being published", + "CommunityChannelsStrings.draftPublishedNotice": "Draft published successfully", + "CommunityChannelsStrings.draftTokenLabel": "Draft token", + "CommunityChannelsStrings.editorLabel": "Editor", + "CommunityChannelsStrings.emptyNotificationsNotice": "You have no notifications at this time.", + "CommunityChannelsStrings.emptyNotificationsWithFiltersNotice": "No notifications match the applied filters.", + "CommunityChannelsStrings.errorLoadingVersions": "Unable to load version history", + "CommunityChannelsStrings.errorSnackbar": "There was an error submitting the channel", + "CommunityChannelsStrings.feedbackNotesLabel": "Notes from the reviewer", + "CommunityChannelsStrings.filterByDateLabel": "Filter by date", + "CommunityChannelsStrings.filterByStatusLabel": "Filter by status", + "CommunityChannelsStrings.filterLabel": "Filtrer", + "CommunityChannelsStrings.fixLicensingBeforeSubmission": "Please fix licensing before submitting a new version.", + "CommunityChannelsStrings.flaggedNotification": "Changes required for version {channelVersion}", + "CommunityChannelsStrings.flaggedStatus": "Needs changes", + "CommunityChannelsStrings.getDraftTokenAction": "Copy token for draft channel", + "CommunityChannelsStrings.goToMyChannelsAction": "Go to My channels", + "CommunityChannelsStrings.hideCriteriaAction": "Hide criteria", + "CommunityChannelsStrings.hideVersions": "Hide versions", + "CommunityChannelsStrings.incompatibleLicensesDescription": "\"{licenseNames}\" - this channel cannot be distributed via Kolibri. If you cannot change the license, remove all resources with \"{licenseNames}\" before submitting again.", + "CommunityChannelsStrings.incompatibleLicensesDetected": "Incompatible licenses detected.", + "CommunityChannelsStrings.incompleteResourcesDescription1": "Les ressources incomplètes ne seront pas publiées et mises à disposition pour le téléchargement dans Kolibri.", + "CommunityChannelsStrings.incompleteResourcesWarning": "{count, number} {count, plural, one {incomplete resource} other {incomplete resources}}", + "CommunityChannelsStrings.internalNotesLabel": "Admin notes (internal use only)", + "CommunityChannelsStrings.invalidLicensingReason": "Invalid or non-compliant licenses", + "CommunityChannelsStrings.invalidMetadataReason": "Invalid or missing metadata", + "CommunityChannelsStrings.languageLabel": "Langue", + "CommunityChannelsStrings.languagesLabel": "Langages", + "CommunityChannelsStrings.lessDetailsButton": "Hide details", + "CommunityChannelsStrings.licenseCheckPassed": "License check passed.", + "CommunityChannelsStrings.licensesLabel": "Licences", + "CommunityChannelsStrings.loadError": "There was an error loading channels.", + "CommunityChannelsStrings.loadingVersionHistory": "Loading version history", + "CommunityChannelsStrings.moreDetails": "After your submission is approved, the channel will be available to other Kolibri Studio users on the 'Community Library' page.", + "CommunityChannelsStrings.moreDetailsButton": "More details", + "CommunityChannelsStrings.needKolibriVersionToImport": "You will need Kolibri version 0.19.4 or higher to import channels from the Community Library.", + "CommunityChannelsStrings.needsChangesPrimaryInfo": "Your previously submitted version needs changes. Make sure you have addressed all comments before resubmitting.", + "CommunityChannelsStrings.newLabel": "Nouveau", + "CommunityChannelsStrings.newNotificationsNotice": "New notifications available.", + "CommunityChannelsStrings.nextPageAction": "Suivant", + "CommunityChannelsStrings.noCommunityChannels": "No channels have been published to the Community Library yet.", + "CommunityChannelsStrings.noResultsWithFilters": "No channels match the selected filters.", + "CommunityChannelsStrings.noVersionsAvailable": "No version history available", + "CommunityChannelsStrings.nonePrimaryInfo": "We're inviting members of the Kolibri community to submit channels they've created for offline learning in low-resource settings. ", + "CommunityChannelsStrings.notPublishedWarningDescription": "Publish to Studio first, then submit to the Community Library.", + "CommunityChannelsStrings.notPublishedWarningTitle": "This channel isn't published to Kolibri Studio yet", + "CommunityChannelsStrings.notificationsLabel": "Notifications", + "CommunityChannelsStrings.otherIssuesReason": "Other issues", + "CommunityChannelsStrings.pageIndicator": "{currentPage} sur {totalPages}", + "CommunityChannelsStrings.pendingStatus": "Submitted", + "CommunityChannelsStrings.portabilityIssuesReason": "Portability problems", + "CommunityChannelsStrings.previewYourDraftTitle": "Preview your draft channel in Kolibri", + "CommunityChannelsStrings.previousPageAction": "Question précédente", + "CommunityChannelsStrings.publicWarningDescription": "It is not possible to submit public channels to the Community Library.", + "CommunityChannelsStrings.publicWarningTitle": "This channel is currently public in the Kolibri Library.", + "CommunityChannelsStrings.publishAction": "Publier", + "CommunityChannelsStrings.publishChannel": "Publish channel", + "CommunityChannelsStrings.publishChannelDescription": "To see your channel in Kolibri, import using the channel token.", + "CommunityChannelsStrings.publishChannelMode": "Publish channel", + "CommunityChannelsStrings.publishDraftDescription": "Your channel will be saved as a draft, allowing you to review and do quality checks on the draft, without altering the current version available for Kolibri users. To see this draft channel in Kolibri, import using the draft channel token.", + "CommunityChannelsStrings.publishDraftMode": "Publish draft channel", + "CommunityChannelsStrings.publishedVersionLabel": "Version publiée:", + "CommunityChannelsStrings.publishingInfo": "You're publishing: Version {version}", + "CommunityChannelsStrings.publishingMessage": "Channel is being published", + "CommunityChannelsStrings.qualityAssuranceReason": "Quality assurance issues", + "CommunityChannelsStrings.reasonLabel": "Reason: {reason}", + "CommunityChannelsStrings.resubmitAction": "Resubmit", + "CommunityChannelsStrings.resubmitModalBodyFirst": "{channelName} v{version} is also published to the Community Library.", + "CommunityChannelsStrings.resubmitModalBodySecond": "Would you like to resubmit this version with your changes for Community Library review?", + "CommunityChannelsStrings.resubmitModalTitle": "Resubmit channel for Community library review?", + "CommunityChannelsStrings.resultsText": "{count, plural, =1 {# result found} other {# results found}}", + "CommunityChannelsStrings.retry": "Réessayer", + "CommunityChannelsStrings.reviewAction": "Passer en revue", + "CommunityChannelsStrings.saveDraft": "Save draft", + "CommunityChannelsStrings.searchLabel": "Rechercher", + "CommunityChannelsStrings.searchNotificationsLabel": "Search notifications", + "CommunityChannelsStrings.seeAllVersions": "See all versions", + "CommunityChannelsStrings.showMore": "Voir plus", + "CommunityChannelsStrings.showOlderAction": "Show older", + "CommunityChannelsStrings.specialPermissionsDetected": "Special Permissions licenses detected", + "CommunityChannelsStrings.submissionCreationNotification": "Your submission to the Community Library was successful and is now under review.", + "CommunityChannelsStrings.submissionNotesLabel": "Submission notes", + "CommunityChannelsStrings.submissionNotification": "{author} ({userType}) submitted {channelVersion}", + "CommunityChannelsStrings.submitButton": "Submit for review", + "CommunityChannelsStrings.submitToCommunityLibrary": "Submit to Community Library", + "CommunityChannelsStrings.submittedPrimaryInfo": "A previous version is still pending review. Reviewers will see the most recent submission by default.", + "CommunityChannelsStrings.submittedSnackbar": "Channel submitted to Community Library", + "CommunityChannelsStrings.submittingSnackbar": "Submitting channel to Community Library...", + "CommunityChannelsStrings.supersededStatus": "Superseded", + "CommunityChannelsStrings.thisMonthLabel": "Ce mois-ci", + "CommunityChannelsStrings.thisWeekLabel": "This week", + "CommunityChannelsStrings.thisYearLabel": "This year", + "CommunityChannelsStrings.todayLabel": "Today", + "CommunityChannelsStrings.unreadNotificationsLabel": "Unread", + "CommunityChannelsStrings.versionDescriptionLabel": "Description de la version", + "CommunityChannelsStrings.versionLabel": "Version {version}", + "CommunityChannelsStrings.versionNotesLabel": "Décrivez les nouveautés de cette version de la chaîne", + "CommunityChannelsStrings.viewCriteriaAction": "View criteria", + "CommunityChannelsStrings.viewMoreAction": "Afficher davantage", + "CommunityChannelsStrings.whatCanYouDoHere": "What you can do here", + "CommunityChannelsStrings.whatCanYouDoHereItem1": "Browse channels by country, category, and language", + "CommunityChannelsStrings.whatCanYouDoHereItem2": "Copy a channel token to use in Kolibri", + "CommunityChannelsStrings.whatCanYouDoHereItem3": "View channel details including description and metadata", + "CommunityChannelsStrings.whatIsCommunityLibrary": "What is the Community Library?", "CommunityStandardsModal.communityStandardsHeader": "Règles de la communauté", "CommunityStandardsModal.coreValuesLink": "En savoir plus sur les valeurs fondamentales de Learning Equality", "CommunityStandardsModal.description": "Learning Equality est une organisation à but non lucratif qui se consacre à fournir un accès équitable à des expériences éducatives de qualité. En plus des Valeurs fondamentales que nous revendiquons, ces Normes communautaires visent à favoriser un environnement valorisant et inclusif pour nos utilisateurs.", @@ -577,13 +708,13 @@ "ConstantStrings.document": "Document", "ConstantStrings.document_thumbnail": "Vignette", "ConstantStrings.edit": "Mes chaînes", - "ConstantStrings.epub": "Document EPUB", + "ConstantStrings.epub": "Document EPub", "ConstantStrings.exercise": "Exercice", "ConstantStrings.exercise_thumbnail": "Vignette", "ConstantStrings.firstCopy": "Copie de {title}", "ConstantStrings.gif": "Image GIF", "ConstantStrings.h5p": "Application H5P", - "ConstantStrings.high_res_video": "Haute définition", + "ConstantStrings.high_res_video": "Haute résolution", "ConstantStrings.html5": "Application HTML5", "ConstantStrings.html5_thumbnail": "Vignette", "ConstantStrings.html5_zip": "Zip HTML5", @@ -600,7 +731,7 @@ "ConstantStrings.mp3": "Audio MP3", "ConstantStrings.mp4": "Vidéo MP4", "ConstantStrings.multiple_selection": "Choix multiple", - "ConstantStrings.nthCopy": "Copier {n, number, integer} du {title}", + "ConstantStrings.nthCopy": "Copier {n, number, integer} du {titre}", "ConstantStrings.num_correct_in_a_row_10": "Objectif : 10 d'affilée", "ConstantStrings.num_correct_in_a_row_10_description": "L'apprenant doit répondre correctement à 10 questions d'affilée", "ConstantStrings.num_correct_in_a_row_2": "Objectif : 2 d'affilée", @@ -616,7 +747,7 @@ "ConstantStrings.public": "Bibliothèque de contenus", "ConstantStrings.single_selection": "Choix unique", "ConstantStrings.slideshow": "Diaporama", - "ConstantStrings.svg": "Image SVG", + "ConstantStrings.svg": "Image SVG ", "ConstantStrings.topic": "Dossier", "ConstantStrings.topic_thumbnail": "Vignette", "ConstantStrings.true_false": "Vrai/Faux", @@ -819,40 +950,11 @@ "DeleteAccountForm.emailInvalidText": "L'adresse e-mail ne correspond pas à celle de votre compte", "DeleteAccountForm.fieldRequired": "Ce champ est obligatoire", "DeleteAccountForm.ok": "OK", - "DetailsPanel.AVERAGE": "Moyenne", - "DetailsPanel.LARGE": "Volumineuse", - "DetailsPanel.SMALL": "Petite", - "DetailsPanel.VERY_LARGE": "Très volumineuse", - "DetailsPanel.VERY_SMALL": "Très petite", - "DetailsPanel.aggregatorToolTip": "Site web ou organisation hébergeant le recueil de contenus, sans forcément en être le créateur ou le titulaire des droits d'auteur", - "DetailsPanel.aggregatorsLabel": "Agrégateurs", - "DetailsPanel.assessmentsIncludedText": "Évaluations", - "DetailsPanel.authorToolTip": "Personne ou organisation créatrice de ce contenu", - "DetailsPanel.authorsLabel": "Auteurs", - "DetailsPanel.categoriesHeading": "Catégories", - "DetailsPanel.coachDescription": "Sur Kolibri, les ressources destinées aux éducateurs ne sont visibles que par eux", - "DetailsPanel.coachHeading": "Ressources pour les éducateurs", - "DetailsPanel.containsContentHeading": "Contient du contenu provenant de", - "DetailsPanel.containsHeading": "Contient", - "DetailsPanel.copyrightHoldersLabel": "Titulaires des droits d'auteur", - "DetailsPanel.creationHeading": "Créé le", - "DetailsPanel.currentVersionHeading": "Version publiée", - "DetailsPanel.languagesHeading": "Langages", - "DetailsPanel.levelsHeading": "Niveaux", - "DetailsPanel.licensesLabel": "Licences", - "DetailsPanel.primaryLanguageHeading": "Langue principale", - "DetailsPanel.providerToolTip": "Organisation ayant commandé le contenu ou le distribuant", - "DetailsPanel.providersLabel": "Fournisseurs", - "DetailsPanel.publishedHeading": "Publiée le", - "DetailsPanel.resourceHeading": "Total des ressources", - "DetailsPanel.sampleFromChannelHeading": "Exemple de contenu de cette chaîne", - "DetailsPanel.sampleFromTopicHeading": "Exemple de contenu sur ce sujet", - "DetailsPanel.sizeHeading": "Taille de la chaîne", - "DetailsPanel.sizeText": "{text} ({size})", - "DetailsPanel.subtitlesHeading": "Légendes et sous-titres", - "DetailsPanel.tagsHeading": "Étiquettes communes", - "DetailsPanel.tokenHeading": "Jeton de la chaîne", - "DetailsPanel.unpublishedText": "Non publiée", + "DeleteChannelModal.cancel": "Annuler", + "DeleteChannelModal.channelDeletedSnackbar": "Chaîne supprimée", + "DeleteChannelModal.deleteChannel": "Supprimer la chaîne", + "DeleteChannelModal.deletePrompt": "Cette chaîne sera définitivement supprimée. Cette action est irréversible.", + "DeleteChannelModal.deleteTitle": "Supprimer cette chaîne", "DetailsTabView.aggregatorLabel": "Agrégateur", "DetailsTabView.aggregatorToolTip": "Site Web ou organisation hébergeant le recueil de contenus, sans forcément en être le créateur ou le titulaire des droits d'auteur", "DetailsTabView.assessmentOptionsLabel": "Options d'évaluation", @@ -1008,10 +1110,8 @@ "ForgotPassword.forgotPasswordPrompt": "Merci de renseigner votre adresse e-mail pour recevoir les instructions de réinitialisation de votre mot de passe", "ForgotPassword.forgotPasswordTitle": "Réinitialiser votre mot de passe", "ForgotPassword.submitButton": "Envoyer", - "FormulasMenu.btnLabelInsert": "Insérer", - "FormulasMenu.formulasMenuTitle": "Caractères spéciaux", "FormulasStrings.addition": "Addition", - "FormulasStrings.advancedCategory": "Advanced", + "FormulasStrings.advancedCategory": "Avancé", "FormulasStrings.alpha": "alpha", "FormulasStrings.and": "And", "FormulasStrings.angle": "Angle", @@ -1019,7 +1119,7 @@ "FormulasStrings.bar": "Bar", "FormulasStrings.basicCategory": "Basic", "FormulasStrings.because": "Because", - "FormulasStrings.beta": "beta", + "FormulasStrings.beta": "bêta", "FormulasStrings.binomialCoefficient": "Binomial coefficient", "FormulasStrings.cardinality": "Cardinality", "FormulasStrings.charactersCategory": "Characters", @@ -1029,7 +1129,7 @@ "FormulasStrings.congruentTo": "Congruent to", "FormulasStrings.conjugate": "Conjugate", "FormulasStrings.conjugateTranspose": "Conjugate transpose", - "FormulasStrings.contains": "Contains", + "FormulasStrings.contains": "Contient", "FormulasStrings.contourIntegral": "Contour integral", "FormulasStrings.coproduct": "Coproduct", "FormulasStrings.definition": "Definition", @@ -1061,7 +1161,7 @@ "FormulasStrings.fraction": "Fraction", "FormulasStrings.gamma": "gamma", "FormulasStrings.gammaCapital": "Gamma", - "FormulasStrings.geometryCategory": "Geometry", + "FormulasStrings.geometryCategory": "Géométrie", "FormulasStrings.givenThat": "Given that/Such that", "FormulasStrings.greaterThan": "Greater than", "FormulasStrings.greaterThanOrEqual": "Greater than or equal to", @@ -1078,7 +1178,7 @@ "FormulasStrings.lambda": "lambda", "FormulasStrings.lambdaCapital": "Lambda", "FormulasStrings.left": "Left", - "FormulasStrings.leftArrow": "Left arrow", + "FormulasStrings.leftArrow": "Flèche gauche", "FormulasStrings.leftCeiling": "Left ceiling", "FormulasStrings.leftDouble": "Left (double)", "FormulasStrings.leftFloor": "Left floor", @@ -1134,7 +1234,7 @@ "FormulasStrings.reducibleTo": "Reducible to", "FormulasStrings.rho": "rho", "FormulasStrings.right": "Right", - "FormulasStrings.rightArrow": "Right arrow", + "FormulasStrings.rightArrow": "Flèche droite", "FormulasStrings.rightCeiling": "Right ceiling", "FormulasStrings.rightDouble": "Right (double)", "FormulasStrings.rightFloor": "Right floor", @@ -1155,21 +1255,21 @@ "FormulasStrings.southeast": "Southeast", "FormulasStrings.southwest": "Southwest", "FormulasStrings.spade": "Spade", - "FormulasStrings.squareRoot": "Square root", + "FormulasStrings.squareRoot": "Racine carrée", "FormulasStrings.subscript": "Subscript", "FormulasStrings.subset": "Subset", "FormulasStrings.subsetOrEqual": "Subset or equal", "FormulasStrings.subtraction": "Subtraction", "FormulasStrings.sum": "Sum", - "FormulasStrings.superscript": "Superscript", + "FormulasStrings.superscript": "Exposant", "FormulasStrings.superset": "Superset", "FormulasStrings.supersetOrEqual": "Superset or equal", "FormulasStrings.symmetricDifference": "Symmetric difference", "FormulasStrings.tau": "tau", "FormulasStrings.tensorProduct": "Tensor product", "FormulasStrings.therefore": "Therefore", - "FormulasStrings.theta": "theta", - "FormulasStrings.thetaCapital": "Theta", + "FormulasStrings.theta": "thêta", + "FormulasStrings.thetaCapital": "Thêta", "FormulasStrings.topElement": "Top element", "FormulasStrings.triangleDown": "Triangle down", "FormulasStrings.triangleUp": "Triangle up", @@ -1204,16 +1304,6 @@ "HintsEditor.newHintBtnLabel": "Nouvel indice", "HintsEditor.noHintsPlaceholder": "La question n'a aucun indice", "ImageOnlyThumbnail.thumbnail": "Vignette {title}", - "ImagesMenu.acceptsText": "Types de fichiers supportés : {acceptedFormats}", - "ImagesMenu.altTextHint": "La description de l'image est nécessaire pour permettre aux apprenants handicapés visuels de répondre aux questions. Elle s'affiche également en cas d'échec du chargement de l'image", - "ImagesMenu.altTextLabel": "Description de l’image", - "ImagesMenu.btnLabelCancel": "Annuler", - "ImagesMenu.btnLabelInsert": "Insérer", - "ImagesMenu.currentImageDefaultText": "Image actuelle", - "ImagesMenu.defaultDropText": "Glissez et déposez une image ici ou téléversez-la manuellement", - "ImagesMenu.imageHeader": "Téléverser une image", - "ImagesMenu.selectFile": "Sélectionner le fichier", - "ImagesMenu.selectFileButton": "Sélectionner le fichier", "ImportFromChannelsModal.addButton": "Ajouter", "ImportFromChannelsModal.addedText": "Ajouté", "ImportFromChannelsModal.importAction": "Importer", @@ -1223,6 +1313,7 @@ "ImportFromChannelsModal.reviewAction": "Passer en revue", "ImportFromChannelsModal.reviewTitle": "Sélection de ressources", "InfoModal.close": "Fermer", + "InfoModal.open": "More information about licenses", "InheritAncestorMetadataModal.applyResourceDetailsTitle": "Appliquer les détails du dossier \"{folder}\"", "InheritAncestorMetadataModal.cancelAction": "Annuler", "InheritAncestorMetadataModal.categories": "Catégories : {categories}", @@ -1255,17 +1346,48 @@ "MainNavigationDrawer.helpLink": "Aide et support", "MainNavigationDrawer.logoutLink": "Se déconnecter", "MainNavigationDrawer.settingsLink": "Paramètres", - "MarkdownEditor.bold": "Gras (Ctrl+B)", - "MarkdownEditor.formulas": "Insérer une formule (Ctrl+F)", - "MarkdownEditor.image": "Insérer une image (Ctrl+P)", - "MarkdownEditor.italic": "Italique (Ctrl+I)", - "MarkdownEditor.minimize": "Réduire", - "MarkdownImageField.editImageOption": "Modifier", - "MarkdownImageField.removeImageOption": "Supprimer", - "MarkdownImageField.resizeImageOption": "Redimensionner", "MasteryCriteriaGoal.labelText": "Objectif", "MasteryCriteriaMofNFields.mHint": "Bonnes réponses nécessaires", "MasteryCriteriaMofNFields.nHint": "Réponses récentes", + "MathLiveA11yStrings.accented": "accented", + "MathLiveA11yStrings.array": "array", + "MathLiveA11yStrings.box": "box", + "MathLiveA11yStrings.chemicalFormula": "chemical formula", + "MathLiveA11yStrings.crossOut": "barrer", + "MathLiveA11yStrings.deleted": "supprimé: ", + "MathLiveA11yStrings.delimiter": "delimiter", + "MathLiveA11yStrings.denominator": "denominator", + "MathLiveA11yStrings.endOf": "{spokenText}; end of {relationName}", + "MathLiveA11yStrings.endOfMathfield": "{spokenText}; end of mathfield", + "MathLiveA11yStrings.error": "erreur", + "MathLiveA11yStrings.extensibleSymbol": "extensible symbol", + "MathLiveA11yStrings.first": "first", + "MathLiveA11yStrings.fraction": "fraction", + "MathLiveA11yStrings.group": "group", + "MathLiveA11yStrings.index": "index", + "MathLiveA11yStrings.latex": "LaTeX", + "MathLiveA11yStrings.line": "ligne", + "MathLiveA11yStrings.mathField": "math field", + "MathLiveA11yStrings.mathfield": "math field", + "MathLiveA11yStrings.numerator": "numerator", + "MathLiveA11yStrings.operator": "operator", + "MathLiveA11yStrings.outOf": "out of {relationName};", + "MathLiveA11yStrings.overUnder": "over-under", + "MathLiveA11yStrings.parent": "parent", + "MathLiveA11yStrings.placeholder": "placeholder", + "MathLiveA11yStrings.prompt": "prompt", + "MathLiveA11yStrings.radicand": "radicand", + "MathLiveA11yStrings.rule": "rule", + "MathLiveA11yStrings.selected": "sélectionné: ", + "MathLiveA11yStrings.space": "space", + "MathLiveA11yStrings.spacing": "spacing", + "MathLiveA11yStrings.squareRoot": "racine carrée", + "MathLiveA11yStrings.startOf": "start of {relationName}: ", + "MathLiveA11yStrings.subscript": "subscript", + "MathLiveA11yStrings.subscriptSuperscript": "subscript-superscript", + "MathLiveA11yStrings.superscript": "exposant", + "MathLiveA11yStrings.superscriptAndSubscript": "superscript and subscript", + "MathLiveA11yStrings.text": "text", "MessageLayout.backToLogin": "Continuer vers la page de connexion", "MoveModal.addTopic": "Ajouter un nouveau dossier", "MoveModal.cancel": "Annuler", @@ -1296,7 +1418,7 @@ "PasswordField.passwordLabel": "Mot de passe", "PasswordInstructionsSent.passwordInstructionsHeader": "Instructions envoyées. Merci !", "PasswordInstructionsSent.passwordInstructionsText": "Si un compte associé à l'adresse e-mail fournie existe déjà, vous devriez recevoir des instructions dans les plus brefs délais. Au cas où vous ne verriez pas d'e-mail de notre part, veuillez vérifier vos spams.", - "PermissionsError.goToHomePageAction": "Aller sur la page d'accueil", + "PermissionsError.backToHome": "Retour à l'accueil", "PermissionsError.permissionDeniedHeader": "Avez-vous oublié de vous connecter ?", "PoliciesModal.checkboxText": "J'ai accepté les conditions ci-dessus", "PoliciesModal.closeButton": "Fermer", @@ -1306,6 +1428,7 @@ "PrivacyPolicyModal.updatedPrivacyHeader": "Politique de confidentialité mise à jour", "ProgressBar.progressText": "{percent} %", "ProgressModal.defaultErrorText": "Échec de la dernière tentative de publication", + "ProgressModal.draftHeader": "Saving draft...", "ProgressModal.lastPublished": "Publié le {last_published}", "ProgressModal.publishHeader": "Publication de la chaîne", "ProgressModal.syncError": "Échec de la dernière tentative de synchronisation", @@ -1346,6 +1469,19 @@ "RelatedResourcesTab.showPreviewBtnLabel": "Montrer", "RelatedResourcesTab.tooManyNextStepsWarning": "Pour une expérience d'apprentissage mieux guidée, limiter le nombre d'étapes suivantes", "RelatedResourcesTab.tooManyPreviousStepsWarning": "Pour une expérience d'apprentissage mieux guidée, limiter le nombre d'étapes antérieures", + "RemoveChannelFromListModal.cancel": "Annuler", + "RemoveChannelFromListModal.channelRemovedSnackbar": "Chaîne supprimée", + "RemoveChannelFromListModal.removeBtn": "Supprimer", + "RemoveChannelFromListModal.removePrompt": "Vous disposez d’un accès en lecture seule à cette chaîne. Confirmez que vous souhaitez la retirer de votre liste de chaînes.", + "RemoveChannelFromListModal.removeTitle": "Retirer de la liste de chaînes", + "RemoveChannelModal.cancel": "Annuler", + "RemoveChannelModal.deleteChannel": "Supprimer la chaîne", + "RemoveChannelModal.deleteChannelWithCLWarning": "This channel has been shared with the Community Library. Deleting it here will not remove it from the Community Library — it may still be approved or remain available there.", + "RemoveChannelModal.deletePrompt": "Cette chaîne sera définitivement supprimée. Cette action est irréversible.", + "RemoveChannelModal.deleteTitle": "Supprimer cette chaîne", + "RemoveChannelModal.removeBtn": "Supprimer", + "RemoveChannelModal.removePrompt": "Vous disposez d’un accès en lecture seule à cette chaîne. Confirmez que vous souhaitez la retirer de votre liste de chaînes.", + "RemoveChannelModal.removeTitle": "Retirer de la liste de chaînes", "ReportErrorModal.closeAction": "Fermer", "ReportErrorModal.emailDescription": "Contactez l’équipe support avec les détails de l’erreur, et nous ferons de notre mieux pour vous aider.", "ReportErrorModal.emailPrompt": "Envoyer un e-mail aux développeurs", @@ -1533,8 +1669,11 @@ "SearchRecommendationsStrings.tooAdvancedForLearnersLabel": "Trop avancé pour le niveau de connaissances des apprenants que je recherche", "SearchRecommendationsStrings.tooBasicForLearnersLabel": "Trop basique pour le niveau de connaissances des apprenants que je recherche", "SearchRecommendationsStrings.tryAgainLink": "Réessayer", + "SearchRecommendationsStrings.trySearchRecommendationsHeader": "Try our new 'Recommendations' feature!", + "SearchRecommendationsStrings.trySearchRecommendationsText": "Based on the title and description of the folder you are working on, we'll suggest relevant resources from the Kolibri Library. Choose 'Import from channels' in any folder of your channels to see the recommendations.", "SearchRecommendationsStrings.undoAction": "Annuler", "SearchRecommendationsStrings.viewMoreLink": "Afficher davantage", + "SearchRecommendationsStrings.viewRecommendationsButton": "View recommendations", "SearchResultsList.failedToLoad": "Impossible de charger les résultats de la recherche", "SearchResultsList.resultsPerPageLabel": "Résultats par page", "SearchResultsList.saveSearchAction": "Enregistrer la recherche", @@ -1588,10 +1727,121 @@ "Storage.spaceUsedOfMax": "{qty} sur {max}", "Storage.storagePercentageUsed": "{qty} % du stockage utilisé", "Storage.videoFiles": "Les fichiers vidéo doivent être compressés pour maximiser l’espace de stockage et garantir une diffusion et une lecture hors ligne fluides. Une fois les fichiers compressés, l’espace total nécessaire peut être inférieur à votre estimation initiale.", + "StudioChannelCard.channelLanguageNotSetIndicator": "Aucune langue définie", + "StudioChannelCard.details": "Détails", + "StudioChannelCard.lastPublished": "Publié le {last_published}", + "StudioChannelCard.lastUpdated": "Mis à jour {updated}", + "StudioChannelCard.multipleCountries": "Multiple countries", + "StudioChannelCard.resourceCount": "{count, plural, one {}\n =1 {# ressource}\n other {# ressources}}", + "StudioChannelCard.selectChannel": "Sélectionner {name}", + "StudioChannelCard.unpublishedText": "Non publiée", + "StudioChannelsPage.invitations": "Vous avez {count, plural, one {}\n =1 {# invitation}\n other {# invitations}}", + "StudioChannelsPage.noChannelsFound": "Aucune chaîne trouvée", + "StudioCollectionsTable.aboutChannelSets": "À propos des recueils", + "StudioCollectionsTable.aboutChannelSetsLink": "Learn more about collections", + "StudioCollectionsTable.addChannelSetTitle": "Nouveau recueil", + "StudioCollectionsTable.cancel": "Annuler", + "StudioCollectionsTable.cancelButtonLabel": "Fermer", + "StudioCollectionsTable.channelNumber": "Nombre de chaînes", + "StudioCollectionsTable.channelSetsDescriptionText": "Un recueil contient plusieurs chaînes de Kolibri Studio pouvant être importées en une seule fois dans Kolibri avec un seul jeton de recueil.", + "StudioCollectionsTable.channelSetsDisclaimer": "Seule la version 0.12.0 ou supérieure de Kolibri permet d'importer des recueils de chaînes.", + "StudioCollectionsTable.channelSetsInstructionsText": "Vous pouvez créer un recueil en sélectionnant les chaînes que vous souhaitez importer ensemble.", + "StudioCollectionsTable.collectionDeleted": "Collection deleted", + "StudioCollectionsTable.copiedTokenId": "Jeton copié", + "StudioCollectionsTable.copyFailed": "Échec de la copie", + "StudioCollectionsTable.copyToken": "Copy token", + "StudioCollectionsTable.delete": "Supprimer le recueil", + "StudioCollectionsTable.deleteChannelSetText": "Êtes-vous sûr de vouloir supprimer ce recueil ?", + "StudioCollectionsTable.deleteChannelSetTitle": "Supprimer le recueil", + "StudioCollectionsTable.deleteError": "Error deleting collection", + "StudioCollectionsTable.edit": "Modifier le recueil", + "StudioCollectionsTable.noChannelSetsFound": "Il est possible de regrouper plusieurs chaînes pour constituer un recueil. Vous pourrez ensuite importer tout le recueil dans Kolibri en une seule fois à l'aide d'un jeton de recueil.", + "StudioCollectionsTable.options": "Options", + "StudioCollectionsTable.pageTitle": "Recueils", + "StudioCollectionsTable.saving": "Enregistrement en cours", + "StudioCollectionsTable.tableCaption": "List of collections", + "StudioCollectionsTable.title": "Nom du recueil", + "StudioCollectionsTable.token": "ID du jeton", + "StudioCopyToken.copiedTokenId": "Jeton copié", + "StudioCopyToken.copyFailed": "Échec de la copie", + "StudioCopyToken.token": "Jeton", + "StudioCopyToken.tooltipText": "Copier le jeton pour importer la chaîne dans Kolibri", + "StudioDetailsPanel.AVERAGE": "Moyenne", + "StudioDetailsPanel.LARGE": "Volumineuse", + "StudioDetailsPanel.SMALL": "Petite", + "StudioDetailsPanel.VERY_LARGE": "Très volumineuse", + "StudioDetailsPanel.VERY_SMALL": "Très petite", + "StudioDetailsPanel.aggregatorToolTip": "Site web ou organisation hébergeant le recueil de contenus, sans forcément en être le créateur ou le titulaire des droits d'auteur", + "StudioDetailsPanel.aggregatorsLabel": "Agrégateurs", + "StudioDetailsPanel.assessmentsIncludedText": "Évaluations", + "StudioDetailsPanel.authorToolTip": "Personne ou organisation créatrice de ce contenu", + "StudioDetailsPanel.authorsLabel": "Auteurs", + "StudioDetailsPanel.categoriesHeading": "Catégories", + "StudioDetailsPanel.coachDescription": "Sur Kolibri, les ressources destinées aux éducateurs ne sont visibles que par eux", + "StudioDetailsPanel.coachHeading": "Ressources pour les éducateurs", + "StudioDetailsPanel.containsContentHeading": "Contient du contenu provenant de", + "StudioDetailsPanel.containsHeading": "Contient", + "StudioDetailsPanel.copyrightHoldersLabel": "Titulaires des droits d'auteur", + "StudioDetailsPanel.creationHeading": "Créé le", + "StudioDetailsPanel.currentVersionHeading": "Version publiée", + "StudioDetailsPanel.languagesHeading": "Langages", + "StudioDetailsPanel.levelsHeading": "Niveaux", + "StudioDetailsPanel.licensesLabel": "Licences", + "StudioDetailsPanel.primaryLanguageHeading": "Langue principale", + "StudioDetailsPanel.providerToolTip": "Organisation ayant commandé le contenu ou le distribuant", + "StudioDetailsPanel.providersLabel": "Fournisseurs", + "StudioDetailsPanel.publishedHeading": "Publiée le", + "StudioDetailsPanel.resourceHeading": "Total des ressources", + "StudioDetailsPanel.sampleFromChannelHeading": "Exemple de contenu de cette chaîne", + "StudioDetailsPanel.sizeHeading": "Taille de la chaîne", + "StudioDetailsPanel.sizeText": "{text} ({size})", + "StudioDetailsPanel.subtitlesHeading": "Légendes et sous-titres", + "StudioDetailsPanel.tagsHeading": "Étiquettes communes", + "StudioDetailsPanel.tokenHeading": "Jeton de chaîne", + "StudioDetailsPanel.unpublishedText": "Non publiée", + "StudioImmersiveModal.close": "Fermer", + "StudioMessageLayout.backToLogin": "Continuer vers la page de connexion", + "StudioMyChannels.copyToken": "Copier le jeton de la chaîne", + "StudioMyChannels.deleteChannel": "Supprimer la chaîne", + "StudioMyChannels.editChannel": "Modifier les détails de la chaîne", + "StudioMyChannels.goToWebsite": "Aller sur le site web source", + "StudioMyChannels.moreOptions": "Plus d'options", + "StudioMyChannels.newChannel": "Nouvelle chaîne", + "StudioMyChannels.title": "Mes chaînes", + "StudioMyChannels.viewContent": "Afficher la chaîne sur Kolibri", "StudioOfflineAlert.offlineText": "Il semblerait que vous soyez hors ligne. Vos modifications seront enregistrées dès que votre connexion sera rétablie.", "StudioOfflineAlert.onlineText": "Vous êtes à nouveau en ligne.", + "StudioStarredChannels.copyToken": "Copier le jeton de la chaîne", + "StudioStarredChannels.deleteChannel": "Supprimer la chaîne", + "StudioStarredChannels.editChannel": "Modifier les détails de la chaîne", + "StudioStarredChannels.goToWebsite": "Aller sur le site web source", + "StudioStarredChannels.moreOptions": "Plus d'options", + "StudioStarredChannels.removeChannel": "Remove channel", + "StudioStarredChannels.title": "Starred channels", + "StudioStarredChannels.viewContent": "Afficher la chaîne sur Kolibri", "StudioTree.missingTitle": "Titre manquant", "StudioTree.optionsTooltip": "Options", + "StudioViewOnlyChannels.copyToken": "Copier le jeton de la chaîne", + "StudioViewOnlyChannels.goToWebsite": "Aller sur le site web source", + "StudioViewOnlyChannels.moreOptions": "Plus d'options", + "StudioViewOnlyChannels.removeChannel": "Remove channel", + "StudioViewOnlyChannels.title": "View-only channels", + "StudioViewOnlyChannels.viewContent": "Afficher la chaîne sur Kolibri", + "SubscriptionCard.annualPrice": "${price, number}/year", + "SubscriptionCard.cancelNotice": "Your subscription will expire on {date, date, medium}. Storage will be removed after that.", + "SubscriptionCard.dismiss": "Rejeter", + "SubscriptionCard.genericError": "There was a problem connecting to the payment provider. Please try again.", + "SubscriptionCard.instantUpgrade": "Upgrade storage now", + "SubscriptionCard.manageSubscription": "Manage subscription", + "SubscriptionCard.renewalNotice": "Your subscription will automatically renew on {date, date, medium}.", + "SubscriptionCard.storageAmount": "Storage (GB)", + "SubscriptionCard.storageIncluded": "{size} included in your subscription", + "SubscriptionCard.storageRange": "Enter a value between 1 and 50", + "SubscriptionCard.subscriptionActive": "Storage subscription active", + "SubscriptionCard.subscriptionCanceling": "Subscription cancelled", + "SubscriptionCard.upgradeDescription": "Purchase additional storage at $15/GB per year.", + "SubscriptionCard.upgradeNow": "Upgrade now", + "SubscriptionCard.upgradeSuccess": "Storage increased to {size}", "SubtitlesList.acceptedFormatsTooltip": "Formats supportés : {extensions}", "SubtitlesList.addSubtitleText": "Ajouter des légendes", "SubtitlesList.subtitlesHeader": "Légendes et sous-titres", @@ -1620,7 +1870,7 @@ "TechnicalTextBlock.copiedToClipboardConfirmation": "Copié dans le presse-papier", "TechnicalTextBlock.copiedToClipboardFailure": "Échec de la copie dans le presse-papier", "TechnicalTextBlock.copyToClipboardButtonPrompt": "Copier dans le presse-papier", - "TermsOfServiceModal.ToSHeader": "Terms of Service", + "TermsOfServiceModal.ToSHeader": "Conditions d'utilisation", "TermsOfServiceModal.acceptableUseHeader": "Acceptable Use Restrictions", "TermsOfServiceModal.acceptableUseItem1": "Will be in strict accordance with these Terms;", "TermsOfServiceModal.acceptableUseItem10": "Will not interfere with, disrupt, or attack any service or network; and", @@ -1651,7 +1901,7 @@ "TermsOfServiceModal.changesToToSP1": "We are constantly updating our Service and that means sometimes we have to change the legal terms under which our Service is offered. These Terms may only be modified by a written amendment signed by an authorized executive of Learning Equality, or by the posting by Learning Equality of a revised version. If we make changes that are material, we will let you know by posting on one of our blogs, or by sending you an email or other communication before the changes take effect. The notice will designate a reasonable period of time after which the new terms will take effect. If you disagree with our changes, then you should stop using the Service within the designated notice period, or once the changes become effective. Your continued use of the Service will be subject to the new terms. However, any dispute that arose before the changes shall be governed by the Terms (including the binding individual arbitration clause) that were in place when the dispute arose.", "TermsOfServiceModal.communicationsHeader": "Communications with Learning Equality", "TermsOfServiceModal.communicationsP1": "For contractual purposes, you (1) consent to receive communications from us in an electronic form via the email address you have submitted or via the Service; and (2) agree that all Terms of Service, agreements, notices, disclosures, and other communications that we provide to you electronically satisfy any legal requirement that those communications would satisfy if they were on paper. This section does not affect your non-waivable rights.", - "TermsOfServiceModal.communityStandardsHeader": "Community Standards", + "TermsOfServiceModal.communityStandardsHeader": "Normes communautaires", "TermsOfServiceModal.communityStandardsLink": "Learn more about Studio's community standards", "TermsOfServiceModal.communityStandardsP1": "For more information about the intended use of the Service, and standards around Content, please see our Community Standards page.", "TermsOfServiceModal.definitionsHeader": "Definitions", @@ -1690,7 +1940,7 @@ "TermsOfServiceModal.thirdPartyP1": "The links to third party websites, any third party content, and any third party applications may be provided for your convenience and information only. The content on any linked website or in any third party application is not under our control and we are not responsible for the content of linked websites and/or third party applications, including any further links contained in a third party website. We make no representations or warranties in connection with any third party content or third party applications, which at all times and in each instance is provided \"as is.\" Third party applications may be subject to additional policies and conditions or agreements between you and the provider of such third party applications. You agree to fully comply with all such additional policies, conditions and agreements. If you decide to access any third party content, and/or any third party application, you do so entirely at your own risk.", "TermsOfServiceModal.thirdPartyRightsHeader": "Third Party Rights", "TermsOfServiceModal.thirdPartyRightsP1": "Nothing in our Terms is intended to confer on any third party any benefit or any right (under the Contracts (Rights of Third Parties) Act 1999 UK or otherwise) to enforce any provision of our Terms or any agreement entered into in connection with it.", - "TermsOfServiceModal.updatedToSHeader": "Updated terms of service", + "TermsOfServiceModal.updatedToSHeader": "Conditions d'utilisation mises à jour", "TermsOfServiceModal.userContentHeader": "User-Generated Content", "TermsOfServiceModal.userContentList1Item1": "We do not endorse any uploaded Content or represent that Content is accurate, useful, or non-harmful. Content could be offensive, indecent, or objectionable; include technical inaccuracies, typographical mistakes, or other errors; or violate or infringe the privacy, publicity rights, intellectual property rights (see our Copyright Infringement and DMCA Policy section to submit copyright complaints), or other proprietary rights of third parties.", "TermsOfServiceModal.userContentList1Item2": "If you upload or author Content, or otherwise make (or allow any third party to make) Content available on the Service, you are entirely responsible for the Content, and any harm resulting from, that Content or your conduct.", @@ -1715,43 +1965,59 @@ "TextArea.fieldRequiredMessage": "Ce champ est obligatoire", "TextField.fieldRequiredMessage": "Ce champ est obligatoire", "Thumbnail.thumbnail": "{title} vignette", + "ThumbnailGenerator.closeButtonLabel": "OK", "ThumbnailGenerator.generatedDefaultFilename": "Vignette générée", "ThumbnailGenerator.thumbnailGenerationFailedHeader": "Impossible de générer une vignette", "ThumbnailGenerator.thumbnailGenerationFailedText": "Une erreur s'est produite lors de la génération d'une vignette", + "TipTapEditorStrings.TipTapEditorLabel": "text editor - Press Enter to start editing", + "TipTapEditorStrings.TipTapViewerLabel": "text editor content", "TipTapEditorStrings.addLink": "Add link", + "TipTapEditorStrings.alignLeft": "Align left", + "TipTapEditorStrings.alignRight": "Align right", "TipTapEditorStrings.altTextDescription": "Alt text is necessary to enable visually impaired learners to answer questions, and it also displays when the image fails to load", "TipTapEditorStrings.altTextLabel": "Alt text (Optional)", "TipTapEditorStrings.altTextPlaceholder": "Describe your image...", "TipTapEditorStrings.bold": "Strong", "TipTapEditorStrings.bulletList": "Bullet list", - "TipTapEditorStrings.cancel": "Cancel", + "TipTapEditorStrings.cancel": "Annuler", "TipTapEditorStrings.cancelLoading": "Cancel loading", + "TipTapEditorStrings.clearFormatting": "Clear formatting", "TipTapEditorStrings.clipboardAccessFailed": "Clipboard access failed. Try copying again.", - "TipTapEditorStrings.close": "Close", + "TipTapEditorStrings.close": "Fermer", "TipTapEditorStrings.closeModal": "Close modal", "TipTapEditorStrings.codeBlock": "Code block", - "TipTapEditorStrings.copy": "Copy", + "TipTapEditorStrings.collapseFormattingBar": "Collapse formatting bar", + "TipTapEditorStrings.copy": "Copie", "TipTapEditorStrings.copyAndPasteActions": "Copy and paste actions", "TipTapEditorStrings.copyLink": "Copy link", + "TipTapEditorStrings.decreaseFormatSize": "Decrease format size", "TipTapEditorStrings.defaultImageName": "Image", - "TipTapEditorStrings.edit": "Edit", + "TipTapEditorStrings.edit": "Modifier", "TipTapEditorStrings.editImage": "Edit image", "TipTapEditorStrings.editLink": "Edit link", + "TipTapEditorStrings.editorControls": "Editor controls", + "TipTapEditorStrings.errorUploadingImage": "Error uploading image", + "TipTapEditorStrings.expandFormattingBar": "Expand formatting bar", "TipTapEditorStrings.failedToProcessImage": "Failed to process the image file.", - "TipTapEditorStrings.fileSizeUnit": "MB.", + "TipTapEditorStrings.fileSizeUnit": "Mo.", "TipTapEditorStrings.fileTooLarge": "File is too large. Maximum size is ", "TipTapEditorStrings.formatHeader1": "Header 1", "TipTapEditorStrings.formatHeader2": "Header 2", "TipTapEditorStrings.formatHeader3": "Header 3", "TipTapEditorStrings.formatNormal": "Normal", "TipTapEditorStrings.formatOptions": "Format options", - "TipTapEditorStrings.formatSmall": "Small", + "TipTapEditorStrings.formatSize": "Format size", + "TipTapEditorStrings.formatSmall": "Petite", "TipTapEditorStrings.formulasMenuTitle": "Special Characters", "TipTapEditorStrings.goToLink": "Go to link", "TipTapEditorStrings.historyActions": "History actions", "TipTapEditorStrings.imageDropZoneText": "Drag and drop an image here or upload manually", "TipTapEditorStrings.imagePreview": "Image preview", - "TipTapEditorStrings.insert": "Insert", + "TipTapEditorStrings.increaseFormatSize": "Increase format size", + "TipTapEditorStrings.insert": "Insérer", + "TipTapEditorStrings.insertContent": "Insert content", + "TipTapEditorStrings.insertContentMenu": "Insert content menu", + "TipTapEditorStrings.insertContentOption": "Insert content option", "TipTapEditorStrings.insertImage": "Insert image", "TipTapEditorStrings.insertLink": "Insert link", "TipTapEditorStrings.insertTools": "Insert tools", @@ -1760,8 +2026,11 @@ "TipTapEditorStrings.link": "Link", "TipTapEditorStrings.linkActions": "Link actions", "TipTapEditorStrings.listFormatting": "List formatting", + "TipTapEditorStrings.loadingFormulas": "Loading math editor", "TipTapEditorStrings.mathFormula": "Math formula", + "TipTapEditorStrings.moreButtonText": "Plus", "TipTapEditorStrings.multipleFilesDroppedWarning": "Multiple files were dropped. Only the first file has been selected.", + "TipTapEditorStrings.noEnoughStorageSpace": "Not enough storage space available. File size exceeds remaining storage.", "TipTapEditorStrings.noFileProvided": "No file provided.", "TipTapEditorStrings.numberedList": "Numbered list", "TipTapEditorStrings.opensInNewTab": "(opens in new tab)", @@ -1770,27 +2039,27 @@ "TipTapEditorStrings.pasteOptionsMenu": "Paste options menu", "TipTapEditorStrings.pasteWithoutFormatting": "Paste without formatting", "TipTapEditorStrings.redo": "Redo", - "TipTapEditorStrings.remove": "Remove", + "TipTapEditorStrings.remove": "Supprimer", "TipTapEditorStrings.removeImage": "Remove image", "TipTapEditorStrings.removeLink": "Remove link", - "TipTapEditorStrings.replaceFile": "Replace file", - "TipTapEditorStrings.save": "Save", - "TipTapEditorStrings.saveChanges": "Save changes", + "TipTapEditorStrings.replaceFile": "Remplacer le fichier", + "TipTapEditorStrings.save": "Enregistrer", + "TipTapEditorStrings.saveChanges": "Enregistrer les modifications", "TipTapEditorStrings.scriptFormatting": "Script formatting", - "TipTapEditorStrings.selectFile": "Select file", + "TipTapEditorStrings.selectFile": "Sélectionner un fichier", "TipTapEditorStrings.selectFileToUpload": "Select file to upload", "TipTapEditorStrings.strikethrough": "Strikethrough", "TipTapEditorStrings.subscript": "Subscript", - "TipTapEditorStrings.superscript": "Superscript", - "TipTapEditorStrings.supportedFileTypes": "Supported file types: png, jpg, jpeg, svg, webp", + "TipTapEditorStrings.superscript": "Exposant", + "TipTapEditorStrings.supportedFileTypes": "Types de fichiers supportés : { extensions }", "TipTapEditorStrings.text": "Text", "TipTapEditorStrings.textFormatOptions": "Text format options", "TipTapEditorStrings.textFormattingOptions": "Text formatting options", "TipTapEditorStrings.textFormattingToolbar": "Text formatting toolbar", "TipTapEditorStrings.textStyleFormatting": "Text style formatting", "TipTapEditorStrings.underline": "Underline", - "TipTapEditorStrings.undo": "Undo", - "TipTapEditorStrings.uploadImage": "Upload image", + "TipTapEditorStrings.undo": "Annuler", + "TipTapEditorStrings.uploadImage": "Téléverser une image", "TitleStrings.catalogTitle": "Catalogue de la bibliothèque de contenus Kolibri", "TitleStrings.defaultTitle": "Kolibri Studio", "TitleStrings.tabTitle": "{title} - {site}", @@ -1814,25 +2083,26 @@ "TreeView.showSidebar": "Afficher la barre latérale", "TreeView.updatedResourcesReadyForReview": "Les ressources mises à jour sont prêtes à être revues", "TreeViewBase.apiGenerated": "Généré par l'API", - "TreeViewBase.cancel": "Annuler", "TreeViewBase.channelDeletedSnackbar": "Chaîne supprimée", "TreeViewBase.channelDetails": "Voir les détails de la chaîne", "TreeViewBase.deleteChannel": "Supprimer la chaîne", - "TreeViewBase.deleteChannelButton": "Supprimer la chaîne", - "TreeViewBase.deletePrompt": "Cette chaîne sera définitivement supprimée. Cette action est irréversible.", - "TreeViewBase.deleteTitle": "Supprimer cette chaîne", "TreeViewBase.editChannel": "Modifier les détails de la chaîne", "TreeViewBase.emptyChannelTooltip": "Impossible de publier une chaîne vide", "TreeViewBase.getToken": "Obtenir un jeton", "TreeViewBase.incompleteDescendantsText": "{count, number, integer} {count, plural, one {la ressource est incomplète et ne peut pas être publiée} other {les ressources sont incomplètes et ne peuvent pas être publiées}}", + "TreeViewBase.inviteCollaborators": "Inviter des collaborateurs", "TreeViewBase.noChangesText": "Aucune modification trouvée dans la chaîne", "TreeViewBase.noLanguageSetError": "La langue de la chaîne est obligatoire", "TreeViewBase.openTrash": "Ouvrir la corbeille", "TreeViewBase.publishButton": "Publier", "TreeViewBase.publishButtonTitle": "Rendre cette chaîne disponible pour importation dans Kolibri", "TreeViewBase.shareChannel": "Partager la chaîne", + "TreeViewBase.shareMenuButton": "Partager", + "TreeViewBase.shareToken": "Share token", + "TreeViewBase.submitToCommunityLibrary": "Submit to Community Library", "TreeViewBase.syncChannel": "Synchroniser les ressources", "TreeViewBase.viewOnly": "Lecture seule", + "Uploader.closeButtonLabel": "OK", "Uploader.listDelimiter": ", ", "Uploader.maxFileSizeText": "{count, plural, one {}\n =1 {# fichier ne sera pas téléversé.}\n other {# fichiers ne seront pas téléversés.}} La taille du fichier doit être inférieure à {size}", "Uploader.noStorageHeader": "Espace insuffisant", @@ -1906,4 +2176,4 @@ "sharedVue.masteryModelRequired": "Le critère de maîtrise est obligatoire", "sharedVue.shortActivityLteThirty": "La valeur doit être égale ou inférieure à 30", "sharedVue.titleRequired": "Le titre est obligatoire" -} +} \ No newline at end of file diff --git a/contentcuration/locale/fr_FR/LC_MESSAGES/django.po b/contentcuration/locale/fr_FR/LC_MESSAGES/django.po index e38a3b7b6f..084836360b 100644 --- a/contentcuration/locale/fr_FR/LC_MESSAGES/django.po +++ b/contentcuration/locale/fr_FR/LC_MESSAGES/django.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: kolibri-studio\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-09-03 19:45+0000\n" -"PO-Revision-Date: 2025-09-12 13:59\n" +"POT-Creation-Date: 2026-04-16 00:57+0000\n" +"PO-Revision-Date: 2026-04-20 19:33\n" "Last-Translator: \n" "Language-Team: French\n" "Language: fr_FR\n" @@ -14,8 +14,8 @@ msgstr "" "X-Crowdin-Project: kolibri-studio\n" "X-Crowdin-Project-ID: 286000\n" "X-Crowdin-Language: fr\n" -"X-Crowdin-File: /search-recommendations-closed-beta/django.po\n" -"X-Crowdin-File-ID: 4886\n" +"X-Crowdin-File: /unstable/django.po\n" +"X-Crowdin-File-ID: 4322\n" #: contentcuration/catalog_settings.py:4 contentcuration/sandbox_settings.py:8 #: contentcuration/settings.py:271 @@ -26,25 +26,25 @@ msgstr "Arabe" msgid "The site is currently in read-only mode. Please try again later." msgstr "Le site est actuellement en mode lecture seule. Veuillez réessayer plus tard." -#: contentcuration/models.py:342 +#: contentcuration/models.py:356 msgid "Not enough space. Check your storage under Settings page." msgstr "Espace insuffisant. Vérifiez votre espace de stockage disponible sur la page Paramètres." -#: contentcuration/models.py:374 contentcuration/models.py:386 +#: contentcuration/models.py:440 contentcuration/models.py:452 msgid "Out of storage! Request more space under Settings > Storage." msgstr "Espace de stockage insuffisant ! Demandez plus d'espace dans Paramètres > Stockage." -#: contentcuration/models.py:2155 +#: contentcuration/models.py:2531 msgid " (Original)" msgstr " (Original)" -#: contentcuration/models.py:3297 +#: contentcuration/models.py:3848 msgid "Created DateTime" -msgstr "Date et heure de création" +msgstr "" -#: contentcuration/models.py:3299 +#: contentcuration/models.py:3850 msgid "Datetime field when the custom_metadata for task was created in UTC" -msgstr "Champ de date et heure indiquant la création des custom_metadata de la tâche en UTC" +msgstr "" #: contentcuration/settings.py:269 msgid "English" @@ -717,7 +717,7 @@ msgstr "Nous rencontrons des problèmes avec un service tiers. Toute opération msgid "We are encountering issues with our data center. This means you may encounter networking problems while using Studio. We appreciate your patience while these issues are being resolved. To check the status of this service, please visit here" msgstr "Nous rencontrons des problèmes avec notre centre de données. Vous pourriez par conséquent rencontrer des problèmes de réseau lors de l'utilisation de Studio. Nous vous remercions de votre patience pendant la résolution de ces problèmes. Pour vérifier l'état de ce service, veuillez visiter ici" -#: contentcuration/utils/publish.py:101 +#: contentcuration/utils/publish.py:103 msgid "Kolibri Studio Channel Published" msgstr "Chaîne Kolibri Studio publiée" @@ -729,14 +729,15 @@ msgstr "Rapport d'erreurs de Kolibri Studio" msgid "Kolibri Studio account deleted" msgstr "Compte Kolibri Studio supprimé" -#: kolibri_public/views.py:223 +#: kolibri_public/views.py:323 msgid "Resource" msgstr "Ressource" -#: kolibri_public/views_v1.py:79 kolibri_public/views_v1.py:94 +#: kolibri_public/views_v1.py:152 kolibri_public/views_v1.py:167 msgid "API version is unavailable" msgstr "La version de l’API est indisponible" -#: kolibri_public/views_v1.py:97 +#: kolibri_public/views_v1.py:170 msgid "No channel matching {} found" msgstr "Aucune chaîne correspondant à {} trouvée" + diff --git a/contentcuration/locale/hi_IN/LC_MESSAGES/contentcuration-messages.json b/contentcuration/locale/hi_IN/LC_MESSAGES/contentcuration-messages.json index 100e64a758..ea81e3a301 100644 --- a/contentcuration/locale/hi_IN/LC_MESSAGES/contentcuration-messages.json +++ b/contentcuration/locale/hi_IN/LC_MESSAGES/contentcuration-messages.json @@ -1,8 +1,8 @@ { - "AccessibilityOptions.altText": "Visual elements in the resource have descriptions that can be accessed by screen readers for the benefit of blind learners", - "AccessibilityOptions.audioDescription": "The resource contains a second narration audio track that provides additional information for the benefit of blind users and those with low vision", - "AccessibilityOptions.highContrast": "The resource text and visual elements are displayed with high contrast for the benefit of users with low vision", - "AccessibilityOptions.signLanguage": "Synchronized sign language intepretation is available for audio and video content", + "AccessibilityOptions.altText": "Les éléments visuels de la ressource sont accompagnés de descriptions accessibles aux lecteurs d'écran, ce qui facilite l'apprentissage des apprenants aveugles", + "AccessibilityOptions.audioDescription": "La ressource contient une deuxième piste audio narrative qui fournit des informations supplémentaires à l'intention des utilisateurs aveugles et malvoyants", + "AccessibilityOptions.highContrast": "Le texte et les éléments visuels sont affichés à fort contraste pour faciliter la lecture des utilisateurs malvoyants", + "AccessibilityOptions.signLanguage": "Une interprétation synchronisée en langue des signes est disponible pour les contenus audio et vidéo", "AccessibilityOptions.taggedPdf": "The document contains PDF tags that can be accessed by screen readers for the benefit of blind learners", "Account.apiDocumentation": "API documentation", "Account.apiTokenHeading": "API Token", @@ -15,6 +15,7 @@ "Account.exportAccountDataHeading": "Export account data", "Account.exportAccountDataLabel": "You will receive an email with all information linked to your account", "Account.exportAccountDataModalMessage": "You'll receive an email with your data when the export is completed", + "Account.exportDataBtn": "OK", "Account.exportDataButton": "Export data", "Account.exportFailed": "Unable to export data. Please try again.", "Account.exportStartedHeader": "Data export started", @@ -30,6 +31,18 @@ "AccountNotActivated.requestNewLink": "Request a new activation link", "AccountNotActivated.text": "Please check your email for an activation link or request a new link.", "AccountNotActivated.title": "Account has not been activated", + "AccountsMain.TOSLink": "Terms of service", + "AccountsMain.copyright": "© {year} Learning Equality", + "AccountsMain.createAccountButton": "अकाउंट बनाएँ", + "AccountsMain.forgotPasswordLink": "Forgot your password?", + "AccountsMain.guestModeLink": "Explore without an account", + "AccountsMain.kolibriStudio": "Kolibri स्टूडियो", + "AccountsMain.loginFailed": "Email or password is incorrect", + "AccountsMain.loginFailedOffline": "You seem to be offline. Please connect to the internet before signing in.", + "AccountsMain.loginToProceed": "You must sign in to view that page", + "AccountsMain.passwordLabel": "पासवर्ड", + "AccountsMain.privacyPolicyLink": "Privacy policy", + "AccountsMain.signInButton": "साइन इन करें", "ActivationExpired.activationExpiredText": "This activation link has been used already or has expired.", "ActivationExpired.activationExpiredTitle": "Activation failed", "ActivationExpired.requestNewLink": "Request a new activation link", @@ -54,8 +67,6 @@ "AdministrationAppError.unauthorizedDetails": "You need to be an administrator of Studio to view this page", "AdministrationIndex.channelsLabel": "चैनल", "AdministrationIndex.usersLabel": "उपयोगकर्ता", - "Alert.closeButtonLabel": "OK", - "Alert.dontShowAgain": "यह संदेश दोबारा न दिखाएँ", "AnswersEditor.answersLabel": "Answers", "AnswersEditor.newAnswerBtnLabel": "New answer", "AnswersEditor.noAnswersPlaceholder": "Question has no answer options", @@ -73,6 +84,7 @@ "AssessmentEditor.noQuestionsPlaceholder": "Exercise has no questions", "AssessmentEditor.showAnswers": "Show answers", "AssessmentEditor.toolbarItemLabel": "प्रश्न", + "AssessmentItemEditor.dialogMessageChangeToFreeResponse": "Switching to 'free response' will remove all current answers. Continue?", "AssessmentItemEditor.dialogMessageChangeToInput": "Switching to 'numeric input' will set all answers as correct and remove all non-numeric answers. Continue?", "AssessmentItemEditor.dialogMessageChangeToSingleSelection": "Switching to 'single choice' will set only one answer as correct. Continue?", "AssessmentItemEditor.dialogMessageChangeToTrueFalse": "Switching to 'true or false' will remove all current answers. Continue?", @@ -88,8 +100,8 @@ "AssessmentItemToolbar.toolbarLabelAddBelow": "Add {itemLabel} below", "AssessmentItemToolbar.toolbarLabelDelete": "हटाएँ", "AssessmentItemToolbar.toolbarLabelEdit": "संपादित करें (एडिट)", - "AssessmentItemToolbar.toolbarLabelMoveDown": "Move down", - "AssessmentItemToolbar.toolbarLabelMoveUp": "Move up", + "AssessmentItemToolbar.toolbarLabelMoveDown": "नीचे जाएँ", + "AssessmentItemToolbar.toolbarLabelMoveUp": "ऊपर जाएं", "AssessmentTab.dialogCancelBtnLabel": "रद्द करें", "AssessmentTab.dialogSubmitBtnLabel": "Submit", "AssessmentTab.incompleteItemsCountMessage": "{invalidItemsCount} incomplete {invalidItemsCount, plural, one {question} other {questions}}", @@ -104,7 +116,7 @@ "BytesForHumansStrings.fileSizeInGigabytes": "{n, number, integer} GB", "BytesForHumansStrings.fileSizeInKilobytes": "{n, number, integer} KB", "BytesForHumansStrings.fileSizeInMegabytes": "{n, number, integer} MB", - "BytesForHumansStrings.fileSizeInTerabytes": "{n, number, integer} TB", + "BytesForHumansStrings.fileSizeInTerabytes": "{n, number} TB", "CatalogFAQ.KolibriAnswer": "Kolibri is an open source ed-tech platform designed for low-resource communities, focused on:", "CatalogFAQ.KolibriAnswerItem1": "Overcoming infrastructural barriers that prevent equitable access to quality education for learners in low-resource and low-connectivity contexts", "CatalogFAQ.KolibriAnswerItem2": "Increasing the availability of open learning materials suitable for many curricula, learning goals, and situations", @@ -170,26 +182,31 @@ "CatalogFilterBar.keywords": "\"{text}\"", "CatalogFilterBar.starred": "Starred", "CatalogFilterBar.subtitles": "उपशीर्षक", - "CatalogFilters.coachDescription": "Resources for coaches are only visible to coaches in Kolibri", - "CatalogFilters.coachLabel": "Resources for coaches", - "CatalogFilters.copyright": "© {year} Learning Equality", - "CatalogFilters.formatLabel": "Formats", - "CatalogFilters.frequentlyAskedQuestionsLink": "Frequently asked questions", - "CatalogFilters.includesLabel": "Display only channels with", - "CatalogFilters.licenseLabel": "Licenses", - "CatalogFilters.searchLabel": "कीबोर्ड", - "CatalogFilters.searchText": "खोज", - "CatalogFilters.starredLabel": "Starred", - "CatalogFilters.subtitlesLabel": "Captions or subtitles", + "CatalogFilterPanelContent.coachDescription": "Resources for coaches are only visible to coaches in Kolibri", + "CatalogFilterPanelContent.coachLabel": "Resources for coaches", + "CatalogFilterPanelContent.copyright": "© {year} Learning Equality", + "CatalogFilterPanelContent.formatLabel": "Formats", + "CatalogFilterPanelContent.frequentlyAskedQuestionsLink": "Frequently asked questions", + "CatalogFilterPanelContent.includesLabel": "Display only channels with", + "CatalogFilterPanelContent.licenseLabel": "Licenses", + "CatalogFilterPanelContent.searchLabel": "कीबोर्ड", + "CatalogFilterPanelContent.starredLabel": "Starred", + "CatalogFilterPanelContent.subtitlesLabel": "Captions or subtitles", + "CatalogFilters.filterLabel": "फ़िल्टर करें", "CatalogList.cancelButton": "रद्द करें", "CatalogList.channelSelectionCount": "{count, plural,\n =1 {# channel selected}\n other {# channels selected}}", + "CatalogList.copyToken": "Copy channel token", "CatalogList.downloadButton": "डाउनलोड करें", "CatalogList.downloadCSV": "CSV डाउनलोड करें", "CatalogList.downloadPDF": "Download PDF", "CatalogList.downloadingMessage": "डाउनलोड शुरू", + "CatalogList.goToWebsite": "Go to source website", + "CatalogList.moreOptions": "अधिक विकल्प", "CatalogList.resultsText": "{count, plural,\n =1 {# result found}\n other {# results found}}", "CatalogList.selectAll": "सभी का चयन करें", "CatalogList.selectChannels": "Download a summary of selected channels", + "CatalogList.title": "Kolibri library", + "CatalogList.viewContent": "View channel on Kolibri", "CategoryOptions.noCategoryFoundText": "Category not found", "ChangePasswordForm.cancelAction": "रद्द करें", "ChangePasswordForm.changePasswordHeader": "पासवर्ड बदलें", @@ -210,9 +227,6 @@ "ChannelCatalogFrontPage.languagesHeading": "भाषाएँ", "ChannelCatalogFrontPage.numberOfChannels": "{ num } channels", "ChannelCatalogFrontPage.subtitlesIncludedText": "Captions or subtitles", - "ChannelDeletedError.backToHomeAction": "होमपेज पर वापिस जाएं", - "ChannelDeletedError.channelDeletedDetails": "This channel does not exist or may have been removed. Please contact us at content@learningequality.org if you think this is a mistake.", - "ChannelDeletedError.channelDeletedHeader": "चैनल नहीं मिला", "ChannelDetailsModal.downloadButton": "Download channel summary", "ChannelDetailsModal.downloadCSV": "CSV डाउनलोड करें", "ChannelDetailsModal.downloadPDF": "Download PDF", @@ -220,6 +234,7 @@ "ChannelExportStrings.assessments": "Assessments", "ChannelExportStrings.authors": "Authors", "ChannelExportStrings.coachContent": "Resources for coaches", + "ChannelExportStrings.containsContentFrom": "Contains content from", "ChannelExportStrings.copyrightHolders": "Copyright holders", "ChannelExportStrings.description": "विवरण", "ChannelExportStrings.downloadFilename": "{year}_{month}_Kolibri_Content_Library", @@ -248,18 +263,17 @@ "ChannelInvitation.editText": "{sender} has invited you to edit {channel}", "ChannelInvitation.goToChannelSnackbarAction": "Go to channel", "ChannelInvitation.viewText": "{sender} has invited you to view {channel}", - "ChannelItem.cancel": "रद्द करें", "ChannelItem.channelDeletedSnackbar": "Channel deleted", "ChannelItem.channelLanguageNotSetIndicator": "No language set", + "ChannelItem.channelRemovedSnackbar": "Channel removed", "ChannelItem.copyToken": "Copy channel token", "ChannelItem.deleteChannel": "चैनल हटाएँ", - "ChannelItem.deletePrompt": "This channel will be permanently deleted. This cannot be undone.", - "ChannelItem.deleteTitle": "Delete this channel", "ChannelItem.details": "विवरण", "ChannelItem.editChannel": "Edit channel details", "ChannelItem.goToWebsite": "Go to source website", "ChannelItem.lastPublished": "Published {last_published}", "ChannelItem.lastUpdated": "Updated {updated}", + "ChannelItem.removeChannel": "Remove channel", "ChannelItem.resourceCount": "{count, plural,\n =1 {# resource}\n other {# resources}}", "ChannelItem.unpublishedText": "Unpublished", "ChannelItem.versionText": "संस्करण {version}", @@ -269,10 +283,9 @@ "ChannelList.noChannelsFound": "No channels found", "ChannelList.noMatchingChannels": "There are no matching channels", "ChannelListAppError.channelPermissionsErrorDetails": "Sign in or ask the owner of this channel to give you permission to edit or view", - "ChannelListIndex.catalog": "Content Library", + "ChannelListIndex.catalog": "Kolibri लाइब्रेरी", "ChannelListIndex.channelSets": "Collections", "ChannelListIndex.frequentlyAskedQuestions": "Frequently asked questions", - "ChannelListIndex.invitations": "You have {count, plural,\n =1 {# invitation}\n other {# invitations}}", "ChannelListIndex.libraryTitle": "Kolibri Content Library Catalog", "ChannelModal.APIText": "Channels generated automatically are not editable.", "ChannelModal.changesSaved": "परिवर्तन सहेजे गए", @@ -284,7 +297,7 @@ "ChannelModal.creatingHeader": "New channel", "ChannelModal.details": "Channel details", "ChannelModal.editTab": "विवरण", - "ChannelModal.keepEditingButton": "Keep editing", + "ChannelModal.keepEditingButton": "एडिटिंग करते रहें", "ChannelModal.notFoundError": "Channel does not exist", "ChannelModal.saveChangesButton": "परिवर्तनों को सेव करें", "ChannelModal.shareTab": "Sharing", @@ -296,25 +309,6 @@ "ChannelNotFoundError.channelNotFoundHeader": "चैनल नहीं मिला", "ChannelSelectionList.noChannelsFound": "No channels found", "ChannelSelectionList.searchText": "किसी चैनल को खोजें", - "ChannelSetItem.cancel": "रद्द करें", - "ChannelSetItem.delete": "Delete collection", - "ChannelSetItem.deleteChannelSetText": "Are you sure you want to delete this collection?", - "ChannelSetItem.deleteChannelSetTitle": "Delete collection", - "ChannelSetItem.edit": "Edit collection", - "ChannelSetItem.options": "विकल्प", - "ChannelSetItem.saving": "Saving", - "ChannelSetList.aboutChannelSets": "About collections", - "ChannelSetList.aboutChannelSetsLink": "Learn about collections", - "ChannelSetList.addChannelSetTitle": "New collection", - "ChannelSetList.cancelButtonLabel": "बंद करें", - "ChannelSetList.channelNumber": "Number of channels", - "ChannelSetList.channelSetsDescriptionText": "A collection contains multiple Kolibri Studio channels that can be imported at one time to Kolibri with a single collection token.", - "ChannelSetList.channelSetsDisclaimer": "You will need Kolibri version 0.12.0 or higher to import channel collections", - "ChannelSetList.channelSetsInstructionsText": "You can make a collection by selecting the channels you want to be imported together.", - "ChannelSetList.noChannelSetsFound": "You can package together multiple channels to create a collection. The entire collection can then be imported to Kolibri at once by using a collection token.", - "ChannelSetList.options": "विकल्प", - "ChannelSetList.title": "Collection name", - "ChannelSetList.token": "Token ID", "ChannelSetModal.bookmark": "Starred", "ChannelSetModal.channelAdded": "Channel added", "ChannelSetModal.channelCountText": "{channelCount, plural, =0 {No published channels in your collection} =1 {# channel} other {# channels}}", @@ -330,7 +324,7 @@ "ChannelSetModal.public": "Public", "ChannelSetModal.publishedChannelsOnlyText": "Only published channels are available for selection", "ChannelSetModal.removeText": "हटा दें", - "ChannelSetModal.saveButton": "Save and close", + "ChannelSetModal.saveButton": "सेव करें और बंद करें", "ChannelSetModal.selectChannelsHeader": "Select channels", "ChannelSetModal.titleLabel": "Collection name", "ChannelSetModal.titleRequiredText": "Field is required", @@ -400,7 +394,7 @@ "Clipboard.close": "बंद करें", "Clipboard.copiedItemsToClipboard": "Copied in clipboard", "Clipboard.deleteSelectedButton": "हटाएँ", - "Clipboard.duplicateSelectedButton": "Make a copy", + "Clipboard.duplicateSelectedButton": "एक कॉपी बनाएं", "Clipboard.emptyDefaultText": "Use the clipboard to copy resources and move them to other folders and channels", "Clipboard.emptyDefaultTitle": "No resources in your clipboard", "Clipboard.moveSelectedButton": "Move", @@ -423,7 +417,7 @@ "CommonMetadataStrings.browseChannel": "चैनल ब्राउज़ करें", "CommonMetadataStrings.calculus": "कैल्कुलस", "CommonMetadataStrings.captionsSubtitles": "Includes captions or subtitles", - "CommonMetadataStrings.category": "Category", + "CommonMetadataStrings.category": "श्रेणी", "CommonMetadataStrings.chemistry": "रसायन विज्ञान", "CommonMetadataStrings.civicEducation": "नागरिक शास्त्र", "CommonMetadataStrings.completeDuration": "When time spent is equal to duration", @@ -477,7 +471,7 @@ "CommonMetadataStrings.physics": "भौतिक विज्ञान", "CommonMetadataStrings.politicalScience": "राजनीतिशास्त्र", "CommonMetadataStrings.practice": "अभ्यास", - "CommonMetadataStrings.practiceQuiz": "Practice quiz", + "CommonMetadataStrings.practiceQuiz": "क्विज करें", "CommonMetadataStrings.preschool": "बालवाड़ी", "CommonMetadataStrings.professionalSkills": "पेशेवर कौशल", "CommonMetadataStrings.programming": "प्रोग्रामिंग", @@ -499,6 +493,7 @@ "CommonMetadataStrings.softwareToolsAndTraining": "सॉफ्टवेयर साधन और प्रशिक्षण", "CommonMetadataStrings.specializedProfessionalTraining": "विशेषज्ञता पेशेवर प्रशिक्षण", "CommonMetadataStrings.statistics": "सांख्यिकी", + "CommonMetadataStrings.survey": "Survey", "CommonMetadataStrings.taggedPdf": "टैग की गयी PDF फाइल", "CommonMetadataStrings.teacher": "Working with a teacher", "CommonMetadataStrings.technicalAndVocationalTraining": "तकनीकी और व्यावसायिक प्रशिक्षण", @@ -512,6 +507,163 @@ "CommonMetadataStrings.webDesign": "वेब डिजाइन", "CommonMetadataStrings.work": "कार्य", "CommonMetadataStrings.writing": "लेखन", + "CommonStrings.backAction": "वापस जाएं", + "CommonStrings.channelDetailsLabel": "Channel Details", + "CommonStrings.clearAction": "साफ़ करें", + "CommonStrings.closeAction": "बंद करें", + "CommonStrings.copyChannelTokenAction": "Copy channel token", + "CommonStrings.dismissAction": "ख़ारिज करें", + "CommonStrings.genericErrorMessage": "माफ़ करें! कुछ गलत हो गया। कृपया पुन: प्रयास करें।", + "CommonStrings.previewAction": "पूर्वावलोकन", + "CommonStrings.seeAllAction": "See all", + "CommonStrings.seeLessAction": "See less", + "CommunityChannelsStrings.aboutCommunityLibraryDescription": "Community library contains channels submitted by the community and approved for discovery in Studio.", + "CommunityChannelsStrings.aboutCommunityLibraryTitle": "About Community Library", + "CommunityChannelsStrings.activityHistoryLabel": "Activity history", + "CommunityChannelsStrings.adminLabel": "एडमिन", + "CommunityChannelsStrings.allLicensesCompatible": "All licenses are compatible with Community Library.", + "CommunityChannelsStrings.allNotificationsLabel": "All Notifications", + "CommunityChannelsStrings.alreadySubmittedWarningDescription": "Please wait for the channel to be reviewed. You will see a notification in your Studio account after the review is complete.", + "CommunityChannelsStrings.alreadySubmittedWarningTitle": "This version of the channel has already been submitted to the Community Library.", + "CommunityChannelsStrings.approvedNotification": "{author} ({userType}) approved {channelVersion}", + "CommunityChannelsStrings.approvedPrimaryInfo": "A previous version is live in the Community Library. Reviewers will see the latest submission first.", + "CommunityChannelsStrings.approvedStatus": "Approved", + "CommunityChannelsStrings.availableStatus": "Available in Community Library", + "CommunityChannelsStrings.cancelAction": "रद्द करें", + "CommunityChannelsStrings.categoriesLabel": "श्रेणियाँ", + "CommunityChannelsStrings.channelCannotBeDistributed": "This channel cannot be distributed via Kolibri.", + "CommunityChannelsStrings.channelFitAttributionLabel": "Attribution", + "CommunityChannelsStrings.channelFitChannelInfoLabel": "Channel information", + "CommunityChannelsStrings.channelFitChecklistAttribution": "Does each resource have an author listed?", + "CommunityChannelsStrings.channelFitChecklistChannelInfo": "Is your channel's basic information filled in — title, description, thumbnail, language, category, and level?", + "CommunityChannelsStrings.channelFitChecklistIntro": "Criteria for submitting to the Community Library", + "CommunityChannelsStrings.channelFitChecklistLicense": "Are the resources in your channel openly licensed or in the public domain?", + "CommunityChannelsStrings.channelFitChecklistOfflineUse": "Do the resources in your channel work without an Internet connection?", + "CommunityChannelsStrings.channelFitChecklistQuality": "Has anyone on your team or in your organization reviewed the channel?", + "CommunityChannelsStrings.channelFitLicenseLabel": "लाइसेंस", + "CommunityChannelsStrings.channelFitOfflineUseLabel": "Offline use", + "CommunityChannelsStrings.channelFitQualityLabel": "Quality", + "CommunityChannelsStrings.channelTokenDescription": "You can use this token to import and preview the draft channel in Kolibri. Please note that the token for the final published channel will be different.", + "CommunityChannelsStrings.channelVersion": "{name} v{version}", + "CommunityChannelsStrings.channelVersionTokenLabel": "Channel version token", + "CommunityChannelsStrings.clearAll": "सभी साफ़ करें", + "CommunityChannelsStrings.clearAllAction": "सभी साफ़ करें", + "CommunityChannelsStrings.communityLibraryCTADescription": "Have a channel worth sharing with other educators and learners? Submit it for review through the Share menu so it can be discovered in Studio.", + "CommunityChannelsStrings.communityLibraryCTATitle": "Help grow the Community Library", + "CommunityChannelsStrings.communityLibraryDescription": "Browse community-submitted channels approved for discovery in Studio. Copy a token to use a channel in Kolibri.", + "CommunityChannelsStrings.communityLibraryLabel": "Community Library", + "CommunityChannelsStrings.communityLibrarySubmissionLabel": "Community Library submission", + "CommunityChannelsStrings.compatibleLicensesDescription": "{licenseNames} - All licenses are compatible with Community Library.", + "CommunityChannelsStrings.confirmDistributionRights": "Please confirm you have the right to distribute these resources via Kolibri.", + "CommunityChannelsStrings.confirmReplacementText": "I understand this will replace my earlier submission on the review queue", + "CommunityChannelsStrings.countriesInfoText": "Select one or more countries to label your channel submission with. For example, if your channel contains materials aligned to a national curriculum or regionally-specific content, selecting the relevant countries will help users find it. Otherwise, leave this blank.", + "CommunityChannelsStrings.countryLabel": "Countries", + "CommunityChannelsStrings.descriptionLabel": "Describe what's included in this submission", + "CommunityChannelsStrings.dismissAction": "ख़ारिज करें", + "CommunityChannelsStrings.draftBeingPublishedNotice": "Draft version is being published", + "CommunityChannelsStrings.draftPublishedNotice": "Draft published successfully", + "CommunityChannelsStrings.draftTokenLabel": "Draft token", + "CommunityChannelsStrings.editorLabel": "Editor", + "CommunityChannelsStrings.emptyNotificationsNotice": "You have no notifications at this time.", + "CommunityChannelsStrings.emptyNotificationsWithFiltersNotice": "No notifications match the applied filters.", + "CommunityChannelsStrings.errorLoadingVersions": "Unable to load version history", + "CommunityChannelsStrings.errorSnackbar": "There was an error submitting the channel", + "CommunityChannelsStrings.feedbackNotesLabel": "Notes from the reviewer", + "CommunityChannelsStrings.filterByDateLabel": "Filter by date", + "CommunityChannelsStrings.filterByStatusLabel": "Filter by status", + "CommunityChannelsStrings.filterLabel": "फ़िल्टर करें", + "CommunityChannelsStrings.fixLicensingBeforeSubmission": "Please fix licensing before submitting a new version.", + "CommunityChannelsStrings.flaggedNotification": "Changes required for version {channelVersion}", + "CommunityChannelsStrings.flaggedStatus": "Needs changes", + "CommunityChannelsStrings.getDraftTokenAction": "Copy token for draft channel", + "CommunityChannelsStrings.goToMyChannelsAction": "Go to My channels", + "CommunityChannelsStrings.hideCriteriaAction": "Hide criteria", + "CommunityChannelsStrings.hideVersions": "Hide versions", + "CommunityChannelsStrings.incompatibleLicensesDescription": "\"{licenseNames}\" - this channel cannot be distributed via Kolibri. If you cannot change the license, remove all resources with \"{licenseNames}\" before submitting again.", + "CommunityChannelsStrings.incompatibleLicensesDetected": "Incompatible licenses detected.", + "CommunityChannelsStrings.incompleteResourcesDescription1": "Incomplete resources will not be published and made available for download in Kolibri.", + "CommunityChannelsStrings.incompleteResourcesWarning": "{count, number} {count, plural, one {incomplete resource} other {incomplete resources}}", + "CommunityChannelsStrings.internalNotesLabel": "Admin notes (internal use only)", + "CommunityChannelsStrings.invalidLicensingReason": "Invalid or non-compliant licenses", + "CommunityChannelsStrings.invalidMetadataReason": "Invalid or missing metadata", + "CommunityChannelsStrings.languageLabel": "भाषा ", + "CommunityChannelsStrings.languagesLabel": "भाषाएँ", + "CommunityChannelsStrings.lessDetailsButton": "Hide details", + "CommunityChannelsStrings.licenseCheckPassed": "License check passed.", + "CommunityChannelsStrings.licensesLabel": "Licenses", + "CommunityChannelsStrings.loadError": "There was an error loading channels.", + "CommunityChannelsStrings.loadingVersionHistory": "Loading version history", + "CommunityChannelsStrings.moreDetails": "After your submission is approved, the channel will be available to other Kolibri Studio users on the 'Community Library' page.", + "CommunityChannelsStrings.moreDetailsButton": "More details", + "CommunityChannelsStrings.needKolibriVersionToImport": "You will need Kolibri version 0.19.4 or higher to import channels from the Community Library.", + "CommunityChannelsStrings.needsChangesPrimaryInfo": "Your previously submitted version needs changes. Make sure you have addressed all comments before resubmitting.", + "CommunityChannelsStrings.newLabel": "नया", + "CommunityChannelsStrings.newNotificationsNotice": "New notifications available.", + "CommunityChannelsStrings.nextPageAction": "अगला", + "CommunityChannelsStrings.noCommunityChannels": "No channels have been published to the Community Library yet.", + "CommunityChannelsStrings.noResultsWithFilters": "No channels match the selected filters.", + "CommunityChannelsStrings.noVersionsAvailable": "No version history available", + "CommunityChannelsStrings.nonePrimaryInfo": "We're inviting members of the Kolibri community to submit channels they've created for offline learning in low-resource settings. ", + "CommunityChannelsStrings.notPublishedWarningDescription": "Publish to Studio first, then submit to the Community Library.", + "CommunityChannelsStrings.notPublishedWarningTitle": "This channel isn't published to Kolibri Studio yet", + "CommunityChannelsStrings.notificationsLabel": "Notifications", + "CommunityChannelsStrings.otherIssuesReason": "Other issues", + "CommunityChannelsStrings.pageIndicator": "{currentPage} of {totalPages}", + "CommunityChannelsStrings.pendingStatus": "Submitted", + "CommunityChannelsStrings.portabilityIssuesReason": "Portability problems", + "CommunityChannelsStrings.previewYourDraftTitle": "Preview your draft channel in Kolibri", + "CommunityChannelsStrings.previousPageAction": "पिछला प्रश्न", + "CommunityChannelsStrings.publicWarningDescription": "It is not possible to submit public channels to the Community Library.", + "CommunityChannelsStrings.publicWarningTitle": "This channel is currently public in the Kolibri Library.", + "CommunityChannelsStrings.publishAction": "Publish", + "CommunityChannelsStrings.publishChannel": "Publish channel", + "CommunityChannelsStrings.publishChannelDescription": "To see your channel in Kolibri, import using the channel token.", + "CommunityChannelsStrings.publishChannelMode": "Publish channel", + "CommunityChannelsStrings.publishDraftDescription": "Your channel will be saved as a draft, allowing you to review and do quality checks on the draft, without altering the current version available for Kolibri users. To see this draft channel in Kolibri, import using the draft channel token.", + "CommunityChannelsStrings.publishDraftMode": "Publish draft channel", + "CommunityChannelsStrings.publishedVersionLabel": "Published version:", + "CommunityChannelsStrings.publishingInfo": "You're publishing: Version {version}", + "CommunityChannelsStrings.publishingMessage": "Channel is being published", + "CommunityChannelsStrings.qualityAssuranceReason": "Quality assurance issues", + "CommunityChannelsStrings.reasonLabel": "Reason: {reason}", + "CommunityChannelsStrings.resubmitAction": "Resubmit", + "CommunityChannelsStrings.resubmitModalBodyFirst": "{channelName} v{version} is also published to the Community Library.", + "CommunityChannelsStrings.resubmitModalBodySecond": "Would you like to resubmit this version with your changes for Community Library review?", + "CommunityChannelsStrings.resubmitModalTitle": "Resubmit channel for Community library review?", + "CommunityChannelsStrings.resultsText": "{count, plural, =1 {# result found} other {# results found}}", + "CommunityChannelsStrings.retry": "पुन: प्रयास करें", + "CommunityChannelsStrings.reviewAction": "Review", + "CommunityChannelsStrings.saveDraft": "Save draft", + "CommunityChannelsStrings.searchLabel": "खोज", + "CommunityChannelsStrings.searchNotificationsLabel": "Search notifications", + "CommunityChannelsStrings.seeAllVersions": "See all versions", + "CommunityChannelsStrings.showMore": "और दिखाएँ", + "CommunityChannelsStrings.showOlderAction": "Show older", + "CommunityChannelsStrings.specialPermissionsDetected": "Special Permissions licenses detected", + "CommunityChannelsStrings.submissionCreationNotification": "Your submission to the Community Library was successful and is now under review.", + "CommunityChannelsStrings.submissionNotesLabel": "Submission notes", + "CommunityChannelsStrings.submissionNotification": "{author} ({userType}) submitted {channelVersion}", + "CommunityChannelsStrings.submitButton": "Submit for review", + "CommunityChannelsStrings.submitToCommunityLibrary": "Submit to Community Library", + "CommunityChannelsStrings.submittedPrimaryInfo": "A previous version is still pending review. Reviewers will see the most recent submission by default.", + "CommunityChannelsStrings.submittedSnackbar": "Channel submitted to Community Library", + "CommunityChannelsStrings.submittingSnackbar": "Submitting channel to Community Library...", + "CommunityChannelsStrings.supersededStatus": "Superseded", + "CommunityChannelsStrings.thisMonthLabel": "इस महीने", + "CommunityChannelsStrings.thisWeekLabel": "This week", + "CommunityChannelsStrings.thisYearLabel": "This year", + "CommunityChannelsStrings.todayLabel": "Today", + "CommunityChannelsStrings.unreadNotificationsLabel": "Unread", + "CommunityChannelsStrings.versionDescriptionLabel": "Version description", + "CommunityChannelsStrings.versionLabel": "संस्करण {version}", + "CommunityChannelsStrings.versionNotesLabel": "Describe what's included in this channel version", + "CommunityChannelsStrings.viewCriteriaAction": "View criteria", + "CommunityChannelsStrings.viewMoreAction": "अधिक देखें", + "CommunityChannelsStrings.whatCanYouDoHere": "What you can do here", + "CommunityChannelsStrings.whatCanYouDoHereItem1": "Browse channels by country, category, and language", + "CommunityChannelsStrings.whatCanYouDoHereItem2": "Copy a channel token to use in Kolibri", + "CommunityChannelsStrings.whatCanYouDoHereItem3": "View channel details including description and metadata", + "CommunityChannelsStrings.whatIsCommunityLibrary": "What is the Community Library?", "CommunityStandardsModal.communityStandardsHeader": "Community Standards", "CommunityStandardsModal.coreValuesLink": "Learn more about Learning Equality's core values", "CommunityStandardsModal.description": "Learning Equality is a nonprofit organization dedicated to enabling equitable access to quality educational experiences. Along with our statement of Core Values, these Community Standards are intended to foster a supportive and inclusive environment for our users.", @@ -566,10 +718,12 @@ "ConstantStrings.html5": "HTML5 App", "ConstantStrings.html5_thumbnail": "Thumbnail", "ConstantStrings.html5_zip": "HTML5 zip", + "ConstantStrings.imscp_zip": "IMSCP zip", "ConstantStrings.input_question": "Numeric input", "ConstantStrings.jpeg": "JPEG image", "ConstantStrings.jpg": "JPG image", "ConstantStrings.json": "JSON", + "ConstantStrings.kpub": "KPub document", "ConstantStrings.learner": "Anyone", "ConstantStrings.low_res_video": "Low resolution", "ConstantStrings.m_of_n": "M of N...", @@ -590,7 +744,7 @@ "ConstantStrings.perseus": "Perseus Exercise", "ConstantStrings.perseus_question": "Khan Academy question", "ConstantStrings.png": "PNG image", - "ConstantStrings.public": "Content library", + "ConstantStrings.public": "Kolibri library", "ConstantStrings.single_selection": "Single choice", "ConstantStrings.slideshow": "स्लाइडशो", "ConstantStrings.svg": "SVG image", @@ -631,9 +785,9 @@ "ContentNodeEditListItem.creatingCopies": "Copying...", "ContentNodeEditListItem.editTooltip": "Edit title and description", "ContentNodeEditListItem.optionsTooltip": "विकल्प", - "ContentNodeEditListItem.removeNode": "Remove", - "ContentNodeEditListItem.retryCopy": "Retry", - "ContentNodeEditListItem.undo": "Undo", + "ContentNodeEditListItem.removeNode": "हटा दें", + "ContentNodeEditListItem.retryCopy": "पुन: प्रयास करें", + "ContentNodeEditListItem.undo": "अकृत करें", "ContentNodeIcon.audio": "ऑडियो", "ContentNodeIcon.document": "दस्तावेज़", "ContentNodeIcon.exercise": "अभ्यास", @@ -655,7 +809,7 @@ "ContentNodeOptions.copiedToClipboardSnackbar": "क्लिपबोर्ड पर कॉपी कर दिया गया", "ContentNodeOptions.copyToClipboard": "क्लिपबोर्ड पर कॉपी करें", "ContentNodeOptions.creatingCopies": "Copying...", - "ContentNodeOptions.editAllDetails": "Edit details", + "ContentNodeOptions.editAllDetails": "विवरण संपादित करें", "ContentNodeOptions.editAudience": "Edit audience", "ContentNodeOptions.editCategories": "Edit categories", "ContentNodeOptions.editCompletion": "Edit completion", @@ -667,11 +821,12 @@ "ContentNodeOptions.editTitleDescription": "Edit title and description", "ContentNodeOptions.editWhatIsNeeded": "Edit requirements", "ContentNodeOptions.goToOriginalLocation": "Go to original location", - "ContentNodeOptions.makeACopy": "Make a copy", + "ContentNodeOptions.makeACopy": "एक कॉपी बनाएं", "ContentNodeOptions.move": "Move", "ContentNodeOptions.moveTo": "Move to...", "ContentNodeOptions.newSubtopic": "New folder", - "ContentNodeOptions.remove": "Delete", + "ContentNodeOptions.remove": "हटा दें", + "ContentNodeOptions.removeNode": "हटाएँ", "ContentNodeOptions.removedFromClipboard": "Deleted from clipboard", "ContentNodeOptions.removedItems": "Sent to trash", "ContentNodeOptions.undo": "अकृत करें", @@ -731,9 +886,9 @@ "Create.organizationSourceOption": "Organization", "Create.organizationSourcePlaceholder": "Name of organization", "Create.organizingUsageOption": "Organizing or aligning existing materials", - "Create.otherSourceOption": "Other", + "Create.otherSourceOption": "अन्य", "Create.otherSourcePlaceholder": "Please describe", - "Create.otherUsageOption": "Other", + "Create.otherUsageOption": "अन्य", "Create.otherUsagePlaceholder": "Please describe", "Create.passwordLabel": "पासवर्ड", "Create.passwordMatchMessage": "Passwords don't match", @@ -765,15 +920,15 @@ "CurrentTopicView.copySelectedButton": "क्लिपबोर्ड पर कॉपी करें", "CurrentTopicView.copyToClipboardButton": "क्लिपबोर्ड पर कॉपी करें", "CurrentTopicView.creatingCopies": "Copying...", - "CurrentTopicView.deleteSelectedButton": "Remove", - "CurrentTopicView.duplicateSelectedButton": "Make a copy", + "CurrentTopicView.deleteSelectedButton": "हटा दें", + "CurrentTopicView.duplicateSelectedButton": "एक कॉपी बनाएं", "CurrentTopicView.editAudienceButton": "Edit audience", "CurrentTopicView.editButton": "संपादित करें (एडिट)", "CurrentTopicView.editCategoriesButton": "Edit categories", "CurrentTopicView.editLanguageButton": "Edit language", "CurrentTopicView.editLearningActivitiesButton": "Edit learning activities", "CurrentTopicView.editLevelsButton": "Edit levels", - "CurrentTopicView.editSelectedButton": "Edit details", + "CurrentTopicView.editSelectedButton": "विवरण संपादित करें", "CurrentTopicView.editSourceButton": "Edit source", "CurrentTopicView.editWhatIsNeededButton": "Edit requirements", "CurrentTopicView.importFromChannels": "Import from channels", @@ -794,40 +949,12 @@ "DeleteAccountForm.emailAddressLabel": "Email address", "DeleteAccountForm.emailInvalidText": "Email does not match your account email", "DeleteAccountForm.fieldRequired": "Field is required", - "Details.AVERAGE": "Average", - "Details.LARGE": "Large", - "Details.SMALL": "Small", - "Details.VERY_LARGE": "Very large", - "Details.VERY_SMALL": "Very small", - "Details.aggregatorToolTip": "Website or organization hosting the content collection but not necessarily the creator or copyright holder", - "Details.aggregatorsLabel": "Aggregators", - "Details.assessmentsIncludedText": "Assessments", - "Details.authorToolTip": "Person or organization who created this content", - "Details.authorsLabel": "Authors", - "Details.categoriesHeading": "Categories", - "Details.coachDescription": "Resources for coaches are only visible to coaches in Kolibri", - "Details.coachHeading": "Resources for coaches", - "Details.containsContentHeading": "Contains content from", - "Details.containsHeading": "Contains", - "Details.copyrightHoldersLabel": "Copyright holders", - "Details.creationHeading": "Created on", - "Details.currentVersionHeading": "Published version", - "Details.languagesHeading": "भाषाएँ", - "Details.levelsHeading": "Levels", - "Details.licensesLabel": "Licenses", - "Details.primaryLanguageHeading": "Primary language", - "Details.providerToolTip": "Organization that commissioned or is distributing the content", - "Details.providersLabel": "Providers", - "Details.publishedHeading": "Published on", - "Details.resourceHeading": "Total resources", - "Details.sampleFromChannelHeading": "Sample content from this channel", - "Details.sampleFromTopicHeading": "Sample content from this topic", - "Details.sizeHeading": "Channel size", - "Details.sizeText": "{text} ({size})", - "Details.subtitlesHeading": "Captions and subtitles", - "Details.tagsHeading": "Common tags", - "Details.tokenHeading": "चैनल टोकन", - "Details.unpublishedText": "Unpublished", + "DeleteAccountForm.ok": "OK", + "DeleteChannelModal.cancel": "रद्द करें", + "DeleteChannelModal.channelDeletedSnackbar": "Channel deleted", + "DeleteChannelModal.deleteChannel": "चैनल हटाएँ", + "DeleteChannelModal.deletePrompt": "This channel will be permanently deleted. This cannot be undone.", + "DeleteChannelModal.deleteTitle": "Delete this channel", "DetailsTabView.aggregatorLabel": "Aggregator", "DetailsTabView.aggregatorToolTip": "Website or org hosting the content collection but not necessarily the creator or copyright holder", "DetailsTabView.assessmentOptionsLabel": "Assessment options", @@ -865,28 +992,28 @@ "DiffTable.typeTopics": "फ़ोल्डर्स", "DiffTable.typeVersion": "API version", "DiffTable.typeVideos": "वीडियो", - "EditAudienceModal.cancelAction": "Cancel", + "EditAudienceModal.cancelAction": "रद्द करें", "EditAudienceModal.editAudienceTitle": "Edit audience", - "EditAudienceModal.forBeginnersCheckbox": "For beginners", + "EditAudienceModal.forBeginnersCheckbox": "नौसिखियों के लिए", "EditAudienceModal.multipleAudience": "The selected resources are visible to different audiences. Choosing an option below will change the visibility of all selected resources.", - "EditAudienceModal.saveAction": "Save", + "EditAudienceModal.saveAction": "सेव करें", "EditAudienceModal.visibleTo": "Visible to:", "EditAudienceModal.visibleToAnyone": "Resources are visible to anyone", "EditAudienceModal.visibleToCoaches": "Resources are visible only to coaches (teachers, facilitators, administrators)", - "EditBooleanMapModal.cancelAction": "Cancel", - "EditBooleanMapModal.saveAction": "Save", + "EditBooleanMapModal.cancelAction": "रद्द करें", + "EditBooleanMapModal.saveAction": "सेव करें", "EditBooleanMapModal.updateDescendantCheckbox": "Apply to all resources, folders, and subfolders contained within the selected folders.", "EditCategoriesModal.editCategories": "Edit categories", - "EditCompletionModal.cancelAction": "Cancel", + "EditCompletionModal.cancelAction": "रद्द करें", "EditCompletionModal.editCompletion": "Edit completion", - "EditCompletionModal.saveAction": "Save", - "EditLanguageModal.cancelAction": "Cancel", + "EditCompletionModal.saveAction": "सेव करें", + "EditLanguageModal.cancelAction": "रद्द करें", "EditLanguageModal.differentLanguages": "You selected resources in different languages. The language you choose below will be applied to all selected resources.", "EditLanguageModal.editLanguage": "Edit language", "EditLanguageModal.emptyLanguagesSearch": "No language matches the search", "EditLanguageModal.languageItemText": "{language} ({code})", - "EditLanguageModal.saveAction": "Save", - "EditLanguageModal.searchAction": "Search", + "EditLanguageModal.saveAction": "सेव करें", + "EditLanguageModal.searchAction": "खोज", "EditLanguageModal.updateDescendantsCheckbox": "Apply the chosen language to all resources, folders, and subfolders contained within the selected folders.", "EditLearningActivitiesModal.editLearningActivitiesTitle": "Edit learning activities", "EditLevelsModal.editLevelsTitle": "Edit levels", @@ -903,7 +1030,7 @@ "EditModal.finishButton": "समाप्त", "EditModal.invalidNodesFound": "{count, plural,\n =1 {# incomplete resource found}\n other {# incomplete resources found}}", "EditModal.invalidNodesFoundText": "Incomplete resources will not be published until these errors are resolved", - "EditModal.keepEditingButton": "Keep editing", + "EditModal.keepEditingButton": "एडिटिंग करते रहें", "EditModal.loadErrorText": "Failed to load content", "EditModal.okButton": "OK", "EditModal.saveAnywaysButton": "Exit anyway", @@ -916,35 +1043,35 @@ "EditResourcesNeededModal.editResourcesNeededTitle": "Edit requirements", "EditSourceModal.aggregatorLabel": "Aggregator", "EditSourceModal.aggregatorToolTip": "Website or org hosting the content collection but not necessarily the creator or copyright holder", - "EditSourceModal.authorLabel": "Author", + "EditSourceModal.authorLabel": "लेखक", "EditSourceModal.authorToolTip": "Person or organization who created this content", - "EditSourceModal.cancelAction": "Cancel", + "EditSourceModal.cancelAction": "रद्द करें", "EditSourceModal.cannotEditPublic": "Not editable for resources from public channels", - "EditSourceModal.copyrightHolderLabel": "Copyright holder", + "EditSourceModal.copyrightHolderLabel": "कॉपीराइट धारक", "EditSourceModal.editAttribution": "Edit source", "EditSourceModal.editOnlyLocal": "Edits will be reflected only for local resources", "EditSourceModal.mixed": "Mixed", "EditSourceModal.providerLabel": "Provider", "EditSourceModal.providerToolTip": "Organization that commissioned or is distributing the content", - "EditSourceModal.saveAction": "Save", - "EditTitleDescriptionModal.cancelAction": "Cancel", - "EditTitleDescriptionModal.descriptionLabel": "Description", + "EditSourceModal.saveAction": "सेव करें", + "EditTitleDescriptionModal.cancelAction": "रद्द करें", + "EditTitleDescriptionModal.descriptionLabel": "विवरण", "EditTitleDescriptionModal.editTitleDescription": "Edit title and description", - "EditTitleDescriptionModal.saveAction": "Save", - "EditTitleDescriptionModal.titleLabel": "Title", + "EditTitleDescriptionModal.saveAction": "सेव करें", + "EditTitleDescriptionModal.titleLabel": "शीर्षक", "EditView.completionLabel": "Completion", - "EditView.copyrightHolderLabel": "Copyright holder", + "EditView.copyrightHolderLabel": "कॉपीराइट धारक", "EditView.details": "विवरण", "EditView.editingMultipleCount": "Editing details for {topicCount, plural,\n =1 {# folder}\n other {# folders}}, {resourceCount, plural,\n =1 {# resource}\n other {# resources}}", "EditView.errorBannerText": "Please provide the required information", "EditView.invalidFieldsToolTip": "Some required information is missing", "EditView.learningActivityLabel": "Learning activity", - "EditView.licenseLabel": "License", + "EditView.licenseLabel": "लाइसेंस", "EditView.noItemsToEditText": "Please select resources or folders to edit", "EditView.preview": "पूर्वावलोकन", "EditView.questions": "प्रश्न", "EditView.related": "संबंधित", - "EditView.titleLabel": "Title", + "EditView.titleLabel": "शीर्षक", "EmailField.emailLabel": "Email", "EmailField.validEmailMessage": "Please enter a valid email", "ExpandableList.less": "कम दिखाएँ", @@ -959,14 +1086,22 @@ "FileStorage.storageLow": "Storage is running low", "FileStorage.storageLowWithSize": "Total storage is running low: {used} of {total}", "FileStorage.storageUsed": "Total storage used: {used} of {total}", + "FileUpload.cancelButton": "रद्द करें", "FileUpload.fileError": "Unsupported file type", - "FileUpload.filesHeader": "Preview files", + "FileUpload.filesHeader": "Files", "FileUpload.noFileText": "Missing files", + "FileUpload.removeFile": "Remove file", + "FileUpload.removeFileDescription": "Once this file is removed, this resource will only be able to include a single file from the formats: { fileTypes }. Are you sure you want to continue?", + "FileUpload.yesButton": "हाँ", "FileUploadDefault.acceptsHelp": "Supported file types: {extensions}", "FileUploadDefault.chooseFilesButton": "Select files", "FileUploadDefault.dropHereText": "Drag and drop your files here, or select your files manually", "FileUploadDefault.uploadToText": "Upload to '{title}'", + "FileUploadItem.downloadFailed": "Failed to download file", + "FileUploadItem.downloadMenuOptionLabel": "डाउनलोड करें", "FileUploadItem.removeFileButton": "हटा दें", + "FileUploadItem.removeMenuOptionLabel": "हटा दें", + "FileUploadItem.replaceFileMenuOptionLabel": "Replace file", "FileUploadItem.retryUpload": "Retry upload", "FileUploadItem.unknownFile": "Unknown filename", "FileUploadItem.uploadButton": "Select file", @@ -975,8 +1110,183 @@ "ForgotPassword.forgotPasswordPrompt": "Please enter your email address to receive instructions for resetting your password", "ForgotPassword.forgotPasswordTitle": "Reset your password", "ForgotPassword.submitButton": "Submit", - "FormulasMenu.btnLabelInsert": "Insert", - "FormulasMenu.formulasMenuTitle": "Special characters", + "FormulasStrings.addition": "Addition", + "FormulasStrings.advancedCategory": "उन्नत", + "FormulasStrings.alpha": "alpha", + "FormulasStrings.and": "And", + "FormulasStrings.angle": "Angle", + "FormulasStrings.approximately": "Approximately", + "FormulasStrings.bar": "Bar", + "FormulasStrings.basicCategory": "Basic", + "FormulasStrings.because": "Because", + "FormulasStrings.beta": "beta", + "FormulasStrings.binomialCoefficient": "Binomial coefficient", + "FormulasStrings.cardinality": "Cardinality", + "FormulasStrings.charactersCategory": "Characters", + "FormulasStrings.chi": "chi", + "FormulasStrings.circle": "Circle", + "FormulasStrings.club": "Club", + "FormulasStrings.congruentTo": "Congruent to", + "FormulasStrings.conjugate": "Conjugate", + "FormulasStrings.conjugateTranspose": "Conjugate transpose", + "FormulasStrings.contains": "Contains", + "FormulasStrings.contourIntegral": "Contour integral", + "FormulasStrings.coproduct": "Coproduct", + "FormulasStrings.definition": "Definition", + "FormulasStrings.degrees": "Degrees", + "FormulasStrings.delta": "delta", + "FormulasStrings.deltaCapital": "Delta", + "FormulasStrings.diamond": "Diamond", + "FormulasStrings.digamma": "digamma", + "FormulasStrings.directionalCategory": "Directional", + "FormulasStrings.division": "Division", + "FormulasStrings.doesNotEqual": "Does not equal", + "FormulasStrings.dot": "Dot", + "FormulasStrings.down": "Down", + "FormulasStrings.downDouble": "Down (double)", + "FormulasStrings.ell": "ell", + "FormulasStrings.ellipsis": "Ellipsis", + "FormulasStrings.ellipsisCentered": "Ellipsis (centered)", + "FormulasStrings.ellipsisDiagonal": "Ellipsis (diagonal)", + "FormulasStrings.ellipsisVertical": "Ellipsis (vertical)", + "FormulasStrings.emptySet": "Empty set", + "FormulasStrings.entails": "Entails", + "FormulasStrings.epsilon": "epsilon", + "FormulasStrings.eta": "eta", + "FormulasStrings.exclusiveOr": "Exclusive or", + "FormulasStrings.exists": "Exists", + "FormulasStrings.flat": "Flat", + "FormulasStrings.forAll": "For all", + "FormulasStrings.formulasCategory": "Formulas", + "FormulasStrings.fraction": "Fraction", + "FormulasStrings.gamma": "gamma", + "FormulasStrings.gammaCapital": "Gamma", + "FormulasStrings.geometryCategory": "रेखागणित", + "FormulasStrings.givenThat": "Given that/Such that", + "FormulasStrings.greaterThan": "Greater than", + "FormulasStrings.greaterThanOrEqual": "Greater than or equal to", + "FormulasStrings.heart": "Heart", + "FormulasStrings.ifAndOnlyIf": "If and only if", + "FormulasStrings.implies": "Implies", + "FormulasStrings.in": "In", + "FormulasStrings.incomparableTo": "Incomparable to", + "FormulasStrings.infinity": "Infinity", + "FormulasStrings.integral": "Integral", + "FormulasStrings.intersection": "Intersection", + "FormulasStrings.iota": "iota", + "FormulasStrings.kappa": "kappa", + "FormulasStrings.lambda": "lambda", + "FormulasStrings.lambdaCapital": "Lambda", + "FormulasStrings.left": "Left", + "FormulasStrings.leftArrow": "बायां तीर", + "FormulasStrings.leftCeiling": "Left ceiling", + "FormulasStrings.leftDouble": "Left (double)", + "FormulasStrings.leftFloor": "Left floor", + "FormulasStrings.leftHarpoonDown": "Left (harpoon down)", + "FormulasStrings.leftHarpoonUp": "Left (harpoon up)", + "FormulasStrings.leftHooked": "Left (hooked)", + "FormulasStrings.leftLong": "Left (long)", + "FormulasStrings.leftLongDouble": "Left (long, double)", + "FormulasStrings.leftRight": "Left-right", + "FormulasStrings.leftRightDouble": "Left-right (double)", + "FormulasStrings.leftRightLong": "Left-right (long)", + "FormulasStrings.leftRightLongDouble": "Left-right (long, double)", + "FormulasStrings.lessThan": "Less than", + "FormulasStrings.lessThanOrEqual": "Less than or equal to", + "FormulasStrings.linesCategory": "Lines", + "FormulasStrings.logicCategory": "Logic", + "FormulasStrings.measuredAngle": "Measured angle", + "FormulasStrings.minusPlus": "Minus-plus", + "FormulasStrings.miscellaneousCategory": "Miscellaneous", + "FormulasStrings.mu": "mu", + "FormulasStrings.multiplication": "Multiplication", + "FormulasStrings.nabla": "Nabla", + "FormulasStrings.natural": "Natural", + "FormulasStrings.naturalJoin": "Natural join", + "FormulasStrings.negation": "Negation", + "FormulasStrings.nondominatedBy": "Nondominated by", + "FormulasStrings.northeast": "Northeast", + "FormulasStrings.northwest": "Northwest", + "FormulasStrings.notGreaterThan": "Not greater than", + "FormulasStrings.notIn": "Not in", + "FormulasStrings.notLessThan": "Not less than", + "FormulasStrings.notSubsetOrEqual": "Not a subset or equal", + "FormulasStrings.notSupersetOrEqual": "Not a superset or equal", + "FormulasStrings.nu": "nu", + "FormulasStrings.omega": "omega", + "FormulasStrings.omegaCapital": "Omega", + "FormulasStrings.or": "Or", + "FormulasStrings.pWeierstrass": "P", + "FormulasStrings.parallel": "Parallel", + "FormulasStrings.partial": "Partial", + "FormulasStrings.perpendicular": "Perpendicular", + "FormulasStrings.phi": "phi", + "FormulasStrings.phiCapital": "Phi", + "FormulasStrings.pi": "पाई", + "FormulasStrings.piCapital": "पाई", + "FormulasStrings.planckConstant": "Planck's constant", + "FormulasStrings.plusMinus": "Plus-minus", + "FormulasStrings.product": "Product", + "FormulasStrings.proportional": "Proportional", + "FormulasStrings.psi": "psi", + "FormulasStrings.psiCapital": "Psi", + "FormulasStrings.qed": "QED", + "FormulasStrings.reducibleTo": "Reducible to", + "FormulasStrings.rho": "rho", + "FormulasStrings.right": "Right", + "FormulasStrings.rightArrow": "दायां तीर", + "FormulasStrings.rightCeiling": "Right ceiling", + "FormulasStrings.rightDouble": "Right (double)", + "FormulasStrings.rightFloor": "Right floor", + "FormulasStrings.rightHarpoonDown": "Right (harpoon down)", + "FormulasStrings.rightHarpoonUp": "Right (harpoon up)", + "FormulasStrings.rightHooked": "Right (hooked)", + "FormulasStrings.rightLong": "Right (long)", + "FormulasStrings.rightLongDouble": "Right (long, double)", + "FormulasStrings.setDifference": "Set difference", + "FormulasStrings.setsCategory": "Sets", + "FormulasStrings.sharp": "Sharp", + "FormulasStrings.sigma": "sigma", + "FormulasStrings.sigmaCapital": "Sigma", + "FormulasStrings.significantlyGreaterThan": "Significantly greater than", + "FormulasStrings.significantlyLessThan": "Significantly less than", + "FormulasStrings.similarOrEqual": "Similar or equal to", + "FormulasStrings.similarTo": "Similar to", + "FormulasStrings.southeast": "Southeast", + "FormulasStrings.southwest": "Southwest", + "FormulasStrings.spade": "Spade", + "FormulasStrings.squareRoot": "वर्ग मूल", + "FormulasStrings.subscript": "Subscript", + "FormulasStrings.subset": "Subset", + "FormulasStrings.subsetOrEqual": "Subset or equal", + "FormulasStrings.subtraction": "Subtraction", + "FormulasStrings.sum": "Sum", + "FormulasStrings.superscript": "सुपरस्क्रिप्ट", + "FormulasStrings.superset": "Superset", + "FormulasStrings.supersetOrEqual": "Superset or equal", + "FormulasStrings.symmetricDifference": "Symmetric difference", + "FormulasStrings.tau": "tau", + "FormulasStrings.tensorProduct": "Tensor product", + "FormulasStrings.therefore": "Therefore", + "FormulasStrings.theta": "थीटा", + "FormulasStrings.thetaCapital": "थीटा", + "FormulasStrings.topElement": "Top element", + "FormulasStrings.triangleDown": "Triangle down", + "FormulasStrings.triangleUp": "Triangle up", + "FormulasStrings.underline": "Underline", + "FormulasStrings.union": "Union", + "FormulasStrings.up": "Up", + "FormulasStrings.upDouble": "Up (double)", + "FormulasStrings.upDown": "Up-down", + "FormulasStrings.upDownDouble": "Up-down (double)", + "FormulasStrings.upsilon": "upsilon", + "FormulasStrings.upsilonCapital": "Upsilon", + "FormulasStrings.vector": "Vector", + "FormulasStrings.wedgeProduct": "Wedge product", + "FormulasStrings.wreathProduct": "Wreath product", + "FormulasStrings.xi": "xi", + "FormulasStrings.xiCapital": "Xi", + "FormulasStrings.zeta": "zeta", "FullNameForm.cancelAction": "रद्द करें", "FullNameForm.changesSavedMessage": "परिवर्तन सहेजे गए", "FullNameForm.editNameHeader": "Edit full name", @@ -994,35 +1304,24 @@ "HintsEditor.newHintBtnLabel": "New hint", "HintsEditor.noHintsPlaceholder": "Question has no hints", "ImageOnlyThumbnail.thumbnail": "{title} thumbnail", - "ImagesMenu.acceptsText": "Supported file types: {acceptedFormats}", - "ImagesMenu.altTextHint": "The image description is necessary to enable visually impaired learners to answer questions, and it also displays when the image fails to load", - "ImagesMenu.altTextLabel": "Image description", - "ImagesMenu.btnLabelCancel": "रद्द करें", - "ImagesMenu.btnLabelInsert": "Insert", - "ImagesMenu.currentImageDefaultText": "Current image", - "ImagesMenu.defaultDropText": "Drag and drop an image here, or upload manually", - "ImagesMenu.imageHeader": "Upload image", - "ImagesMenu.selectFile": "Select file", - "ImagesMenu.selectFileButton": "Select file", "ImportFromChannelsModal.addButton": "जोड़ें", "ImportFromChannelsModal.addedText": "जोड़ा गया", "ImportFromChannelsModal.importAction": "आयात करें", "ImportFromChannelsModal.importTitle": "Import from other channels", "ImportFromChannelsModal.removeButton": "हटा दें", - "ImportFromChannelsModal.resourcesAddedSnackbar": "{count, number} {count, plural, one {resource selected} other {resources selected}}", - "ImportFromChannelsModal.resourcesRemovedSnackbar": "{count, number} {count, plural, one {संसाधन हटाया गया} other {संसाधन हटाए गए}} ", "ImportFromChannelsModal.resourcesSelected": "{count, number} {count, plural, one {resource selected} other {resources selected}}", "ImportFromChannelsModal.reviewAction": "Review", "ImportFromChannelsModal.reviewTitle": "Resource selection", "InfoModal.close": "बंद करें", + "InfoModal.open": "More information about licenses", "InheritAncestorMetadataModal.applyResourceDetailsTitle": "Apply details from the folder '{folder}'", - "InheritAncestorMetadataModal.cancelAction": "Cancel", + "InheritAncestorMetadataModal.cancelAction": "रद्द करें", "InheritAncestorMetadataModal.categories": "Categories: {categories}", - "InheritAncestorMetadataModal.continueAction": "Continue", + "InheritAncestorMetadataModal.continueAction": "जारी रखें", "InheritAncestorMetadataModal.doNotShowAgainDescription": "All future additions to this folder will have the selected details by default", "InheritAncestorMetadataModal.doNotShowThisAgain": "Don't ask me about this folder again", "InheritAncestorMetadataModal.inheritMetadataDescription": "Select details to add:", - "InheritAncestorMetadataModal.language": "Language: {language}", + "InheritAncestorMetadataModal.language": "भाषा: {language}", "InheritAncestorMetadataModal.learnerNeeds": "Requirements: {learnerNeeds}", "InheritAncestorMetadataModal.levels": "Levels: {levels}", "InheritAncestorMetadataModal.updateLanguage": "Update language:", @@ -1039,37 +1338,56 @@ "LicenseDropdown.licenseDescriptionLabel": "License description", "LicenseDropdown.licenseLabel": "लाइसेंस", "LicenseDropdown.mixed": "Mixed", - "Main.TOSLink": "Terms of service", - "Main.copyright": "© {year} Learning Equality", - "Main.createAccountButton": "अकाउंट बनाएँ", - "Main.forgotPasswordLink": "Forgot your password?", - "Main.guestModeLink": "Explore without an account", - "Main.kolibriStudio": "Kolibri स्टूडियो", - "Main.loginFailed": "Email or password is incorrect", - "Main.loginFailedOffline": "You seem to be offline. Please connect to the internet before signing in.", - "Main.loginToProceed": "You must sign in to view that page", - "Main.passwordLabel": "पासवर्ड", - "Main.privacyPolicyLink": "Privacy policy", - "Main.signInButton": "साइन इन करें", "MainNavigationDrawer.administrationLink": "Administration", - "MainNavigationDrawer.changeLanguage": "Change language", + "MainNavigationDrawer.changeLanguage": "भाषा बदलें", "MainNavigationDrawer.channelsLink": "चैनल", "MainNavigationDrawer.copyright": "© {year} Learning Equality", "MainNavigationDrawer.giveFeedback": "Give feedback", "MainNavigationDrawer.helpLink": "Help and support", "MainNavigationDrawer.logoutLink": "साइन आउट करें", "MainNavigationDrawer.settingsLink": "सेटिंग", - "MarkdownEditor.bold": "Bold (Ctrl+B)", - "MarkdownEditor.formulas": "Insert formula (Ctrl+F)", - "MarkdownEditor.image": "Insert image (Ctrl+P)", - "MarkdownEditor.italic": "Italic (Ctrl+I)", - "MarkdownEditor.minimize": "Minimize (Ctrl+M)", - "MarkdownImageField.editImageOption": "संपादित करें (एडिट)", - "MarkdownImageField.removeImageOption": "हटा दें", - "MarkdownImageField.resizeImageOption": "Resize", "MasteryCriteriaGoal.labelText": "Goal", "MasteryCriteriaMofNFields.mHint": "Correct answers needed", "MasteryCriteriaMofNFields.nHint": "Recent answers", + "MathLiveA11yStrings.accented": "accented", + "MathLiveA11yStrings.array": "array", + "MathLiveA11yStrings.box": "box", + "MathLiveA11yStrings.chemicalFormula": "chemical formula", + "MathLiveA11yStrings.crossOut": "क्रॉस करें", + "MathLiveA11yStrings.deleted": "हटाए गए: ", + "MathLiveA11yStrings.delimiter": "delimiter", + "MathLiveA11yStrings.denominator": "denominator", + "MathLiveA11yStrings.endOf": "{spokenText}; end of {relationName}", + "MathLiveA11yStrings.endOfMathfield": "{spokenText}; end of mathfield", + "MathLiveA11yStrings.error": "त्रुटि", + "MathLiveA11yStrings.extensibleSymbol": "extensible symbol", + "MathLiveA11yStrings.first": "first", + "MathLiveA11yStrings.fraction": "fraction", + "MathLiveA11yStrings.group": "group", + "MathLiveA11yStrings.index": "index", + "MathLiveA11yStrings.latex": "LaTeX", + "MathLiveA11yStrings.line": "रेखा", + "MathLiveA11yStrings.mathField": "math field", + "MathLiveA11yStrings.mathfield": "math field", + "MathLiveA11yStrings.numerator": "numerator", + "MathLiveA11yStrings.operator": "operator", + "MathLiveA11yStrings.outOf": "out of {relationName};", + "MathLiveA11yStrings.overUnder": "over-under", + "MathLiveA11yStrings.parent": "parent", + "MathLiveA11yStrings.placeholder": "placeholder", + "MathLiveA11yStrings.prompt": "prompt", + "MathLiveA11yStrings.radicand": "radicand", + "MathLiveA11yStrings.rule": "rule", + "MathLiveA11yStrings.selected": "चयनित: ", + "MathLiveA11yStrings.space": "space", + "MathLiveA11yStrings.spacing": "spacing", + "MathLiveA11yStrings.squareRoot": "वर्ग मूल", + "MathLiveA11yStrings.startOf": "start of {relationName}: ", + "MathLiveA11yStrings.subscript": "subscript", + "MathLiveA11yStrings.subscriptSuperscript": "subscript-superscript", + "MathLiveA11yStrings.superscript": "सुपरस्क्रिप्ट", + "MathLiveA11yStrings.superscriptAndSubscript": "superscript and subscript", + "MathLiveA11yStrings.text": "text", "MessageLayout.backToLogin": "Continue to sign-in page", "MoveModal.addTopic": "Add new folder", "MoveModal.cancel": "रद्द करें", @@ -1090,7 +1408,7 @@ "NodePanel.emptyChannelText": "Click \"ADD\" to start building your channel", "NodePanel.emptyTopicText": "Nothing in this folder yet", "NodePanel.emptyViewOnlyChannelText": "Nothing in this channel yet", - "NodePanel.showMore": "Show more", + "NodePanel.showMore": "और दिखाएँ", "NodeTreeNavigation.noResourcesDefaultText": "No resources found", "OfflineText.offlineIndicatorText": "Offline", "OfflineText.offlineText": "You seem to be offline. Your changes will be saved once your connection is back.", @@ -1100,7 +1418,7 @@ "PasswordField.passwordLabel": "पासवर्ड", "PasswordInstructionsSent.passwordInstructionsHeader": "Instructions sent. Thank you!", "PasswordInstructionsSent.passwordInstructionsText": "If there is already an account with the email address provided, you should receive the instructions shortly. If you don't see an email from us, please check your spam folder.", - "PermissionsError.goToHomePageAction": "होमपेज पर जाएं", + "PermissionsError.backToHome": "होमपेज पर वापिस जाएं", "PermissionsError.permissionDeniedHeader": "क्या आप साइन इन करना भूल गए?", "PoliciesModal.checkboxText": "I have agreed to the above terms", "PoliciesModal.closeButton": "बंद करें", @@ -1110,6 +1428,7 @@ "PrivacyPolicyModal.updatedPrivacyHeader": "Updated privacy policy", "ProgressBar.progressText": "{percent}%", "ProgressModal.defaultErrorText": "Last attempt to publish failed", + "ProgressModal.draftHeader": "Saving draft...", "ProgressModal.lastPublished": "Published {last_published}", "ProgressModal.publishHeader": "Publishing channel", "ProgressModal.syncError": "Last attempt to sync failed", @@ -1123,12 +1442,15 @@ "PublishModal.incompleteInstructions": "Click 'Continue' to confirm that you would like to publish anyway.", "PublishModal.incompleteWarning": "Incomplete resources will not be published and made available for download in Kolibri.", "PublishModal.languageDescriptionTooltip": "The default language for a channel and its resources", - "PublishModal.languageLabel": "Language", + "PublishModal.languageLabel": "भाषा ", "PublishModal.languageRequiredMessage": "Please select a language for this channel", "PublishModal.nextButton": "जारी रखें", "PublishModal.publishButton": "Publish", "PublishModal.publishMessageLabel": "Describe what's new in this channel version", "PublishModal.versionDescriptionLabel": "Version description", + "RecommendedResourceCard.goToLocationTooltip": "Go to location", + "RecommendedResourceCard.markNotRelevantTooltip": "Mark as not relevant", + "RecommendedResourceCard.selectCard": "Select { title }", "RelatedResourcesList.removeBtnLabel": "हटा दें", "RelatedResourcesTab.addNextStepBtnLabel": "Add next step", "RelatedResourcesTab.addPreviousStepBtnLabel": "Add previous step", @@ -1147,6 +1469,19 @@ "RelatedResourcesTab.showPreviewBtnLabel": "Show me", "RelatedResourcesTab.tooManyNextStepsWarning": "Limit the number of next steps to create a more guided learning experience", "RelatedResourcesTab.tooManyPreviousStepsWarning": "Limit the number of previous steps to create a more guided learning experience", + "RemoveChannelFromListModal.cancel": "रद्द करें", + "RemoveChannelFromListModal.channelRemovedSnackbar": "Channel removed", + "RemoveChannelFromListModal.removeBtn": "हटा दें", + "RemoveChannelFromListModal.removePrompt": "You have view-only access to this channel. Confirm that you want to remove it from your list of channels.", + "RemoveChannelFromListModal.removeTitle": "Remove from channel list", + "RemoveChannelModal.cancel": "रद्द करें", + "RemoveChannelModal.deleteChannel": "चैनल हटाएँ", + "RemoveChannelModal.deleteChannelWithCLWarning": "This channel has been shared with the Community Library. Deleting it here will not remove it from the Community Library — it may still be approved or remain available there.", + "RemoveChannelModal.deletePrompt": "This channel will be permanently deleted. This cannot be undone.", + "RemoveChannelModal.deleteTitle": "Delete this channel", + "RemoveChannelModal.removeBtn": "हटा दें", + "RemoveChannelModal.removePrompt": "You have view-only access to this channel. Confirm that you want to remove it from your list of channels.", + "RemoveChannelModal.removeTitle": "Remove from channel list", "ReportErrorModal.closeAction": "बंद करें", "ReportErrorModal.emailDescription": "अपने त्रुटि विवरण के साथ समर्थन टीम से संपर्क करें और हम मदद करने की पूरी कोशिश करेंगे ।", "ReportErrorModal.emailPrompt": "डेवलपर को ई-मेल करें", @@ -1179,7 +1514,7 @@ "RequestForm.oneWeekLabel": "1 week", "RequestForm.organizationNamePlaceholder": "Organization name", "RequestForm.organizationalAffiliationLabel": "Organizational affiliation", - "RequestForm.otherLabel": "Other", + "RequestForm.otherLabel": "अन्य", "RequestForm.pasteLinkPlaceholder": "Paste link here", "RequestForm.provideSampleLinkLabel": "Please provide a link to a sample of your content (on Kolibri Studio or from source site)", "RequestForm.requestFailed": "Unable to send request. Please try again.", @@ -1251,12 +1586,14 @@ "ResourcePanel.resources": "संसाधन", "ResourcePanel.showAnswers": "Show answers", "ResourcePanel.source": "Source", + "ResourcePanel.sourceContentDoesntExist": "Source content no longer exists. Please contact your administrator.", "ResourcePanel.subtitles": "Captions and subtitles", "ResourcePanel.tags": "Tags", "ResourcePanel.totalResources": "Total resources", "ResourcePanel.visibleTo": "Visible to", "ResourcesNeededOptions.furtherExplanation": "Please add to the 'Description' field any additional supplies learners will need in order to use this resource", "ResourcesNeededOptions.resourcesNeededLabel": "Requirements", + "ReviewSelectionsPage.addResourcesToFolderLabel": "Add {count, number} {count, plural, one {resource} other {resources}} to '{folder}'", "ReviewSelectionsPage.noResourcesSelected": "कोई भी संसाधन नहीं चुने गए है", "ReviewSelectionsPage.removeAction": "हटा दें", "ReviewSelectionsPage.resourcesInTopic": "{count, number} {count, plural, one {संसाधन} other {संसाधन}}", @@ -1291,9 +1628,52 @@ "SearchOrBrowseWindow.backToBrowseAction": "Back to browse", "SearchOrBrowseWindow.copiedToClipboard": "क्लिपबोर्ड पर कॉपी कर दिया गया", "SearchOrBrowseWindow.copyFailed": "Failed to copy to clipboard", + "SearchOrBrowseWindow.jumpToRecommendations": "Jump to recommendations", + "SearchOrBrowseWindow.jumpToSearch": "Jump to search", + "SearchOrBrowseWindow.jumpToSearchResults": "Jump to search results", + "SearchOrBrowseWindow.jumpToTop": "Jump to top", "SearchOrBrowseWindow.savedSearchesLabel": "View saved searches", "SearchOrBrowseWindow.searchAction": "खोज", "SearchOrBrowseWindow.searchLabel": "Search for resources…", + "SearchRecommendationsStrings.aboutRecommendationsDescription": "Our recommendation system uses the titles and descriptions of folders that you already have in your channel to show you other potentially relevant resources across the Kolibri Library.", + "SearchRecommendationsStrings.aboutRecommendationsFeedbackDescription": "Interacting with these recommendations, whether by using them or marking them as not relevant, will help us improve the quality of the recommendations you see.", + "SearchRecommendationsStrings.aboutRecommendationsText": "About recommendations", + "SearchRecommendationsStrings.alreadyUsedResourceLabel": "I already used this resource in my channel", + "SearchRecommendationsStrings.cancelAction": "रद्द करें", + "SearchRecommendationsStrings.closeAction": "बंद करें", + "SearchRecommendationsStrings.enterFeedbackLabel": "Enter your feedback", + "SearchRecommendationsStrings.feedbackConfirmationMessage": "Thank you! Your feedback will help us improve our recommendations.", + "SearchRecommendationsStrings.feedbackFailedMessage": "Feedback submission failed", + "SearchRecommendationsStrings.feedbackInputValidationMessage": "Please enter your feedback", + "SearchRecommendationsStrings.feedbackSubmittedMessage": "Feedback submitted", + "SearchRecommendationsStrings.giveFeedbackDescription": "Help us understand why this resource is not relevant for you right now. Check all that apply:", + "SearchRecommendationsStrings.giveFeedbackText": "Give feedback", + "SearchRecommendationsStrings.goToLocationText": "Go to location", + "SearchRecommendationsStrings.markAsNotRelevantTooltip": "Mark as not relevant", + "SearchRecommendationsStrings.noDirectMatchesMessage": "No direct matches found, but there are other resources that might be helpful", + "SearchRecommendationsStrings.noFeedbackSelectedErrorMessage": "Please select at least one option in order to submit your feedback", + "SearchRecommendationsStrings.notRelatedToSubjectLabel": "Not related to the subject or knowledge area I am trying to find resources for", + "SearchRecommendationsStrings.notRelevantAction": "Not relevant", + "SearchRecommendationsStrings.notSpecificLearningActivityLabel": "Not the type of learning activity I'm looking for (e.g. document instead of video, etc.)", + "SearchRecommendationsStrings.notSuitableForCulturalBackgroundLabel": "Not suitable for the cultural backgrounds and experiences of learners", + "SearchRecommendationsStrings.notSuitableForCurriculumLabel": "Not suitable for the curriculum I am using", + "SearchRecommendationsStrings.otherLabel": "अन्य", + "SearchRecommendationsStrings.problemShowingResourcesMessage": "There was a problem showing these resources", + "SearchRecommendationsStrings.resourceNotWellMadeLabel": "The resource doesn't look or sound well-made enough for use", + "SearchRecommendationsStrings.resourcesInChannelMightBeRelevantTitle": "These resources in { channelName } might be relevant to '{ topic }'", + "SearchRecommendationsStrings.resourcesMightBeRelevantTitle": "These resources might be relevant to '{ topic }'", + "SearchRecommendationsStrings.showOtherRecommendationsLink": "Show other recommendations", + "SearchRecommendationsStrings.showOtherResourcesLink": "Show other resources anyway", + "SearchRecommendationsStrings.showOtherResourcesMessage": "We can show you other resources, but they might not be what you're looking for", + "SearchRecommendationsStrings.submitAction": "Submit", + "SearchRecommendationsStrings.tooAdvancedForLearnersLabel": "Too advanced for the knowledge level of learners I'm looking for", + "SearchRecommendationsStrings.tooBasicForLearnersLabel": "Too basic for the knowledge level of learners I'm looking for", + "SearchRecommendationsStrings.tryAgainLink": "दोबारा कोशिश करें", + "SearchRecommendationsStrings.trySearchRecommendationsHeader": "Try our new 'Recommendations' feature!", + "SearchRecommendationsStrings.trySearchRecommendationsText": "Based on the title and description of the folder you are working on, we'll suggest relevant resources from the Kolibri Library. Choose 'Import from channels' in any folder of your channels to see the recommendations.", + "SearchRecommendationsStrings.undoAction": "अकृत करें", + "SearchRecommendationsStrings.viewMoreLink": "अधिक देखें", + "SearchRecommendationsStrings.viewRecommendationsButton": "View recommendations", "SearchResultsList.failedToLoad": "Failed to load search results", "SearchResultsList.resultsPerPageLabel": "Results per page", "SearchResultsList.saveSearchAction": "Save search", @@ -1305,19 +1685,25 @@ "SettingsIndex.usingStudioLabel": "About Studio", "StagingTreePage.backToViewing": "Back to viewing", "StagingTreePage.cancelDeployBtn": "रद्द करें", + "StagingTreePage.cancelPublishDraftBtn": "रद्द करें", "StagingTreePage.channelDeployed": "Channel has been deployed", "StagingTreePage.closeSummaryDetailsDialogBtn": "बंद करें", - "StagingTreePage.collapseAllButton": "Collapse all", + "StagingTreePage.collapseAllButton": "सभी को संक्षिप्त करें", "StagingTreePage.confirmDeployBtn": "Deploy channel", + "StagingTreePage.confirmPublishDraftBtn": "Publish", "StagingTreePage.deploy": "Deploy", "StagingTreePage.deployChannel": "Deploy channel", "StagingTreePage.deployDialogDescription": "You are about to replace all live resources with staged resources.", + "StagingTreePage.draftPublished": "Draft channel has been published", "StagingTreePage.emptyChannelSubText": "No changes to review! The channel contains all the most recent folders and resources.", "StagingTreePage.emptyChannelText": "No resources found", "StagingTreePage.emptyTopicText": "This topic is empty", "StagingTreePage.liveResources": "Live resources", "StagingTreePage.openCurrentLocationButton": "Expand to current folder location", "StagingTreePage.openSummaryDetailsDialogBtn": "View summary", + "StagingTreePage.publishDraft": "Publish draft", + "StagingTreePage.publishDraftDialogDescription": "You are about to publish a draft of your staged changes.", + "StagingTreePage.publishDraftError": "An error occurred while publishing the draft channel", "StagingTreePage.resourcesCount": "{count, number} {count, plural, one { resource } other { resources }}", "StagingTreePage.reviewMode": "Review mode", "StagingTreePage.stagedResources": "Staged resources", @@ -1329,15 +1715,133 @@ "StatusStrings.noStorageError": "Not enough space", "StatusStrings.uploadFailedError": "Upload failed", "StatusStrings.uploadFileSize": "{uploaded} of {total}", + "Storage.beforeRequestingHeading": "Before requesting additional storage space, please note the following:", + "Storage.beforeRequestingMessage": "The resources you import from our Kolibri Library to your channels do not count towards your storage limit.", "Storage.hideFormAction": "Close form", - "Storage.learnMoreAboutImportingContentFromChannels": "Learn more about how to import resources from other channels", + "Storage.largerStorageRequestPricing": "For larger storage requests, there will be associated costs to help Learning Equality cover hosting, maintenance, and administrative expenses. We are happy to discuss possible rates based on your needs.", + "Storage.learnMoreAboutImportingContentFromChannels": "Learn more about how to import resources from other channels.", + "Storage.learnMoreAboutVideoCompression": "Learn more about how to compress video resources.", "Storage.requestMoreSpaceHeading": "Request more space", - "Storage.requestMoreSpaceMessage": "Please use this form to request additional uploading storage for your Kolibri Studio account. The resources you import from our public library to your channels do not count towards your storage limit.", + "Storage.requestMoreSpaceMessage": "Please use this form to request additional uploading storage for your Kolibri Studio account.", "Storage.showFormAction": "Open form", "Storage.spaceUsedOfMax": "{qty} of {max}", "Storage.storagePercentageUsed": "{qty}% storage used", + "Storage.videoFiles": "Video files should be compressed to maximize storage space and ensure smooth offline distribution and playback. Once compressed, the total storage required may be less than what you originally estimated.", + "StudioChannelCard.channelLanguageNotSetIndicator": "No language set", + "StudioChannelCard.details": "विवरण", + "StudioChannelCard.lastPublished": "Published {last_published}", + "StudioChannelCard.lastUpdated": "Updated {updated}", + "StudioChannelCard.multipleCountries": "Multiple countries", + "StudioChannelCard.resourceCount": "{count, plural,\n =1 {# resource}\n other {# resources}}", + "StudioChannelCard.selectChannel": "Select {name}", + "StudioChannelCard.unpublishedText": "Unpublished", + "StudioChannelsPage.invitations": "You have {count, plural,\n =1 {# invitation}\n other {# invitations}}", + "StudioChannelsPage.noChannelsFound": "No channels found", + "StudioCollectionsTable.aboutChannelSets": "About collections", + "StudioCollectionsTable.aboutChannelSetsLink": "Learn more about collections", + "StudioCollectionsTable.addChannelSetTitle": "New collection", + "StudioCollectionsTable.cancel": "रद्द करें", + "StudioCollectionsTable.cancelButtonLabel": "बंद करें", + "StudioCollectionsTable.channelNumber": "Number of channels", + "StudioCollectionsTable.channelSetsDescriptionText": "A collection contains multiple Kolibri Studio channels that can be imported at one time to Kolibri with a single collection token.", + "StudioCollectionsTable.channelSetsDisclaimer": "You will need Kolibri version 0.12.0 or higher to import channel collections", + "StudioCollectionsTable.channelSetsInstructionsText": "You can make a collection by selecting the channels you want to be imported together.", + "StudioCollectionsTable.collectionDeleted": "Collection deleted", + "StudioCollectionsTable.copiedTokenId": "Token copied", + "StudioCollectionsTable.copyFailed": "Copy failed", + "StudioCollectionsTable.copyToken": "Copy token", + "StudioCollectionsTable.delete": "Delete collection", + "StudioCollectionsTable.deleteChannelSetText": "Are you sure you want to delete this collection?", + "StudioCollectionsTable.deleteChannelSetTitle": "Delete collection", + "StudioCollectionsTable.deleteError": "Error deleting collection", + "StudioCollectionsTable.edit": "Edit collection", + "StudioCollectionsTable.noChannelSetsFound": "You can package together multiple channels to create a collection. The entire collection can then be imported to Kolibri at once by using a collection token.", + "StudioCollectionsTable.options": "विकल्प", + "StudioCollectionsTable.pageTitle": "Collections", + "StudioCollectionsTable.saving": "Saving", + "StudioCollectionsTable.tableCaption": "List of collections", + "StudioCollectionsTable.title": "Collection name", + "StudioCollectionsTable.token": "Token ID", + "StudioCopyToken.copiedTokenId": "Token copied", + "StudioCopyToken.copyFailed": "Copy failed", + "StudioCopyToken.token": "Token", + "StudioCopyToken.tooltipText": "Copy token to import channel into Kolibri", + "StudioDetailsPanel.AVERAGE": "Average", + "StudioDetailsPanel.LARGE": "Large", + "StudioDetailsPanel.SMALL": "Small", + "StudioDetailsPanel.VERY_LARGE": "Very large", + "StudioDetailsPanel.VERY_SMALL": "Very small", + "StudioDetailsPanel.aggregatorToolTip": "Website or organization hosting the content collection but not necessarily the creator or copyright holder", + "StudioDetailsPanel.aggregatorsLabel": "Aggregators", + "StudioDetailsPanel.assessmentsIncludedText": "Assessments", + "StudioDetailsPanel.authorToolTip": "Person or organization who created this content", + "StudioDetailsPanel.authorsLabel": "Authors", + "StudioDetailsPanel.categoriesHeading": "श्रेणियाँ", + "StudioDetailsPanel.coachDescription": "Resources for coaches are only visible to coaches in Kolibri", + "StudioDetailsPanel.coachHeading": "Resources for coaches", + "StudioDetailsPanel.containsContentHeading": "Contains content from", + "StudioDetailsPanel.containsHeading": "Contains", + "StudioDetailsPanel.copyrightHoldersLabel": "Copyright holders", + "StudioDetailsPanel.creationHeading": "Created on", + "StudioDetailsPanel.currentVersionHeading": "Published version", + "StudioDetailsPanel.languagesHeading": "भाषाएँ", + "StudioDetailsPanel.levelsHeading": "Levels", + "StudioDetailsPanel.licensesLabel": "Licenses", + "StudioDetailsPanel.primaryLanguageHeading": "Primary language", + "StudioDetailsPanel.providerToolTip": "Organization that commissioned or is distributing the content", + "StudioDetailsPanel.providersLabel": "Providers", + "StudioDetailsPanel.publishedHeading": "Published on", + "StudioDetailsPanel.resourceHeading": "Total resources", + "StudioDetailsPanel.sampleFromChannelHeading": "Sample content from this channel", + "StudioDetailsPanel.sizeHeading": "Channel size", + "StudioDetailsPanel.sizeText": "{text} ({size})", + "StudioDetailsPanel.subtitlesHeading": "Captions and subtitles", + "StudioDetailsPanel.tagsHeading": "Common tags", + "StudioDetailsPanel.tokenHeading": "चैनल टोकन", + "StudioDetailsPanel.unpublishedText": "Unpublished", + "StudioImmersiveModal.close": "बंद करें", + "StudioMessageLayout.backToLogin": "Continue to sign-in page", + "StudioMyChannels.copyToken": "Copy channel token", + "StudioMyChannels.deleteChannel": "चैनल हटाएँ", + "StudioMyChannels.editChannel": "Edit channel details", + "StudioMyChannels.goToWebsite": "Go to source website", + "StudioMyChannels.moreOptions": "अधिक विकल्प", + "StudioMyChannels.newChannel": "New channel", + "StudioMyChannels.title": "My channels", + "StudioMyChannels.viewContent": "View channel on Kolibri", + "StudioOfflineAlert.offlineText": "You seem to be offline. Your changes will be saved once your connection is back.", + "StudioOfflineAlert.onlineText": "You are back online.", + "StudioStarredChannels.copyToken": "Copy channel token", + "StudioStarredChannels.deleteChannel": "चैनल हटाएँ", + "StudioStarredChannels.editChannel": "Edit channel details", + "StudioStarredChannels.goToWebsite": "Go to source website", + "StudioStarredChannels.moreOptions": "अधिक विकल्प", + "StudioStarredChannels.removeChannel": "Remove channel", + "StudioStarredChannels.title": "Starred channels", + "StudioStarredChannels.viewContent": "View channel on Kolibri", "StudioTree.missingTitle": "Missing title", "StudioTree.optionsTooltip": "विकल्प", + "StudioViewOnlyChannels.copyToken": "Copy channel token", + "StudioViewOnlyChannels.goToWebsite": "Go to source website", + "StudioViewOnlyChannels.moreOptions": "अधिक विकल्प", + "StudioViewOnlyChannels.removeChannel": "Remove channel", + "StudioViewOnlyChannels.title": "View-only channels", + "StudioViewOnlyChannels.viewContent": "View channel on Kolibri", + "SubscriptionCard.annualPrice": "${price, number}/year", + "SubscriptionCard.cancelNotice": "Your subscription will expire on {date, date, medium}. Storage will be removed after that.", + "SubscriptionCard.dismiss": "ख़ारिज करें", + "SubscriptionCard.genericError": "There was a problem connecting to the payment provider. Please try again.", + "SubscriptionCard.instantUpgrade": "Upgrade storage now", + "SubscriptionCard.manageSubscription": "Manage subscription", + "SubscriptionCard.renewalNotice": "Your subscription will automatically renew on {date, date, medium}.", + "SubscriptionCard.storageAmount": "Storage (GB)", + "SubscriptionCard.storageIncluded": "{size} included in your subscription", + "SubscriptionCard.storageRange": "Enter a value between 1 and 50", + "SubscriptionCard.subscriptionActive": "Storage subscription active", + "SubscriptionCard.subscriptionCanceling": "Subscription cancelled", + "SubscriptionCard.upgradeDescription": "Purchase additional storage at $15/GB per year.", + "SubscriptionCard.upgradeNow": "Upgrade now", + "SubscriptionCard.upgradeSuccess": "Storage increased to {size}", "SubtitlesList.acceptedFormatsTooltip": "Supported formats: {extensions}", "SubtitlesList.addSubtitleText": "Add captions", "SubtitlesList.subtitlesHeader": "Captions and subtitles", @@ -1366,7 +1870,6 @@ "TechnicalTextBlock.copiedToClipboardConfirmation": "क्लिपबोर्ड पर कॉपी कर दिया गया", "TechnicalTextBlock.copiedToClipboardFailure": "Copy to clipboard failed", "TechnicalTextBlock.copyToClipboardButtonPrompt": "क्लिपबोर्ड पर कॉपी करें", - "Template.templateString": "You have {count, plural,\n =1 {# node for testing}\n other {# nodes for testing}}", "TermsOfServiceModal.ToSHeader": "Terms of Service", "TermsOfServiceModal.acceptableUseHeader": "Acceptable Use Restrictions", "TermsOfServiceModal.acceptableUseItem1": "Will be in strict accordance with these Terms;", @@ -1462,9 +1965,101 @@ "TextArea.fieldRequiredMessage": "Field is required", "TextField.fieldRequiredMessage": "Field is required", "Thumbnail.thumbnail": "{title} thumbnail", + "ThumbnailGenerator.closeButtonLabel": "OK", "ThumbnailGenerator.generatedDefaultFilename": "Generated thumbnail", "ThumbnailGenerator.thumbnailGenerationFailedHeader": "Unable to generate thumbnail", "ThumbnailGenerator.thumbnailGenerationFailedText": "There was a problem generating a thumbnail", + "TipTapEditorStrings.TipTapEditorLabel": "text editor - Press Enter to start editing", + "TipTapEditorStrings.TipTapViewerLabel": "text editor content", + "TipTapEditorStrings.addLink": "Add link", + "TipTapEditorStrings.alignLeft": "Align left", + "TipTapEditorStrings.alignRight": "Align right", + "TipTapEditorStrings.altTextDescription": "Alt text is necessary to enable visually impaired learners to answer questions, and it also displays when the image fails to load", + "TipTapEditorStrings.altTextLabel": "Alt text (Optional)", + "TipTapEditorStrings.altTextPlaceholder": "Describe your image...", + "TipTapEditorStrings.bold": "Strong", + "TipTapEditorStrings.bulletList": "Bullet list", + "TipTapEditorStrings.cancel": "रद्द करें", + "TipTapEditorStrings.cancelLoading": "Cancel loading", + "TipTapEditorStrings.clearFormatting": "Clear formatting", + "TipTapEditorStrings.clipboardAccessFailed": "Clipboard access failed. Try copying again.", + "TipTapEditorStrings.close": "बंद करें", + "TipTapEditorStrings.closeModal": "Close modal", + "TipTapEditorStrings.codeBlock": "Code block", + "TipTapEditorStrings.collapseFormattingBar": "Collapse formatting bar", + "TipTapEditorStrings.copy": "कॉपी बनाएँ", + "TipTapEditorStrings.copyAndPasteActions": "Copy and paste actions", + "TipTapEditorStrings.copyLink": "Copy link", + "TipTapEditorStrings.decreaseFormatSize": "Decrease format size", + "TipTapEditorStrings.defaultImageName": "Image", + "TipTapEditorStrings.edit": "संपादित करें (एडिट)", + "TipTapEditorStrings.editImage": "Edit image", + "TipTapEditorStrings.editLink": "Edit link", + "TipTapEditorStrings.editorControls": "Editor controls", + "TipTapEditorStrings.errorUploadingImage": "Error uploading image", + "TipTapEditorStrings.expandFormattingBar": "Expand formatting bar", + "TipTapEditorStrings.failedToProcessImage": "Failed to process the image file.", + "TipTapEditorStrings.fileSizeUnit": "MB.", + "TipTapEditorStrings.fileTooLarge": "File is too large. Maximum size is ", + "TipTapEditorStrings.formatHeader1": "Header 1", + "TipTapEditorStrings.formatHeader2": "Header 2", + "TipTapEditorStrings.formatHeader3": "Header 3", + "TipTapEditorStrings.formatNormal": "Normal", + "TipTapEditorStrings.formatOptions": "Format options", + "TipTapEditorStrings.formatSize": "Format size", + "TipTapEditorStrings.formatSmall": "Small", + "TipTapEditorStrings.formulasMenuTitle": "Special Characters", + "TipTapEditorStrings.goToLink": "Go to link", + "TipTapEditorStrings.historyActions": "History actions", + "TipTapEditorStrings.imageDropZoneText": "Drag and drop an image here or upload manually", + "TipTapEditorStrings.imagePreview": "Image preview", + "TipTapEditorStrings.increaseFormatSize": "Increase format size", + "TipTapEditorStrings.insert": "Insert", + "TipTapEditorStrings.insertContent": "Insert content", + "TipTapEditorStrings.insertContentMenu": "Insert content menu", + "TipTapEditorStrings.insertContentOption": "Insert content option", + "TipTapEditorStrings.insertImage": "Insert image", + "TipTapEditorStrings.insertLink": "Insert link", + "TipTapEditorStrings.insertTools": "Insert tools", + "TipTapEditorStrings.invalidFileType": "Invalid file type. Please use: ", + "TipTapEditorStrings.italic": "Italic", + "TipTapEditorStrings.link": "Link", + "TipTapEditorStrings.linkActions": "Link actions", + "TipTapEditorStrings.listFormatting": "List formatting", + "TipTapEditorStrings.loadingFormulas": "Loading math editor", + "TipTapEditorStrings.mathFormula": "Math formula", + "TipTapEditorStrings.moreButtonText": "और", + "TipTapEditorStrings.multipleFilesDroppedWarning": "Multiple files were dropped. Only the first file has been selected.", + "TipTapEditorStrings.noEnoughStorageSpace": "Not enough storage space available. File size exceeds remaining storage.", + "TipTapEditorStrings.noFileProvided": "No file provided.", + "TipTapEditorStrings.numberedList": "Numbered list", + "TipTapEditorStrings.opensInNewTab": "(opens in new tab)", + "TipTapEditorStrings.paste": "Paste", + "TipTapEditorStrings.pasteOptions": "Paste options", + "TipTapEditorStrings.pasteOptionsMenu": "Paste options menu", + "TipTapEditorStrings.pasteWithoutFormatting": "Paste without formatting", + "TipTapEditorStrings.redo": "Redo", + "TipTapEditorStrings.remove": "हटा दें", + "TipTapEditorStrings.removeImage": "Remove image", + "TipTapEditorStrings.removeLink": "Remove link", + "TipTapEditorStrings.replaceFile": "Replace file", + "TipTapEditorStrings.save": "सेव करें", + "TipTapEditorStrings.saveChanges": "परिवर्तनों को सेव करें", + "TipTapEditorStrings.scriptFormatting": "Script formatting", + "TipTapEditorStrings.selectFile": "Select file", + "TipTapEditorStrings.selectFileToUpload": "Select file to upload", + "TipTapEditorStrings.strikethrough": "Strikethrough", + "TipTapEditorStrings.subscript": "Subscript", + "TipTapEditorStrings.superscript": "सुपरस्क्रिप्ट", + "TipTapEditorStrings.supportedFileTypes": "Supported file types: { extensions }", + "TipTapEditorStrings.text": "Text", + "TipTapEditorStrings.textFormatOptions": "Text format options", + "TipTapEditorStrings.textFormattingOptions": "Text formatting options", + "TipTapEditorStrings.textFormattingToolbar": "Text formatting toolbar", + "TipTapEditorStrings.textStyleFormatting": "Text style formatting", + "TipTapEditorStrings.underline": "Underline", + "TipTapEditorStrings.undo": "अकृत करें", + "TipTapEditorStrings.uploadImage": "Upload image", "TitleStrings.catalogTitle": "Kolibri Content Library Catalog", "TitleStrings.defaultTitle": "Kolibri स्टूडियो", "TitleStrings.tabTitle": "{title} - {site}", @@ -1472,7 +2067,7 @@ "ToggleText.more": "और दिखाएँ", "TrashModal.deleteButton": "हटाएँ", "TrashModal.deleteConfirmationCancelButton": "रद्द करें", - "TrashModal.deleteConfirmationDeleteButton": "Delete permanently", + "TrashModal.deleteConfirmationDeleteButton": "हमेशा के लिए डिलीट करें", "TrashModal.deleteConfirmationHeader": "Permanently delete {topicCount, plural,\n =1 {# folder}\n other {# folders}}, {resourceCount, plural,\n =1 {# resource}\n other {# resources}}?", "TrashModal.deleteConfirmationText": "You cannot undo this action. Are you sure you want to continue?", "TrashModal.deleteSuccessMessage": "Permanently deleted", @@ -1483,30 +2078,31 @@ "TrashModal.trashEmptyText": "Trash is empty", "TrashModal.trashModalTitle": "Trash", "TreeView.closeDrawer": "बंद करें", - "TreeView.collapseAllButton": "Collapse all", + "TreeView.collapseAllButton": "सभी को संक्षिप्त करें", "TreeView.openCurrentLocationButton": "Expand to current folder location", "TreeView.showSidebar": "Show sidebar", "TreeView.updatedResourcesReadyForReview": "Updated resources are ready for review", "TreeViewBase.apiGenerated": "Generated by API", - "TreeViewBase.cancel": "रद्द करें", "TreeViewBase.channelDeletedSnackbar": "Channel deleted", "TreeViewBase.channelDetails": "View channel details", "TreeViewBase.deleteChannel": "चैनल हटाएँ", - "TreeViewBase.deleteChannelButton": "चैनल हटाएँ", - "TreeViewBase.deletePrompt": "This channel will be permanently deleted. This cannot be undone.", - "TreeViewBase.deleteTitle": "Delete this channel", "TreeViewBase.editChannel": "Edit channel details", "TreeViewBase.emptyChannelTooltip": "You cannot publish an empty channel", "TreeViewBase.getToken": "Get token", "TreeViewBase.incompleteDescendantsText": "{count, number, integer} {count, plural, one {resource is incomplete and cannot be published} other {resources are incomplete and cannot be published}}", + "TreeViewBase.inviteCollaborators": "Invite collaborators", "TreeViewBase.noChangesText": "No changes found in channel", "TreeViewBase.noLanguageSetError": "Channel language is required", "TreeViewBase.openTrash": "Open trash", "TreeViewBase.publishButton": "Publish", "TreeViewBase.publishButtonTitle": "Make this channel available for import into Kolibri", "TreeViewBase.shareChannel": "Share channel", + "TreeViewBase.shareMenuButton": "साझा करें ", + "TreeViewBase.shareToken": "Share token", + "TreeViewBase.submitToCommunityLibrary": "Submit to Community Library", "TreeViewBase.syncChannel": "Sync resources", "TreeViewBase.viewOnly": "View-only", + "Uploader.closeButtonLabel": "OK", "Uploader.listDelimiter": ", ", "Uploader.maxFileSizeText": "{count, plural,\n =1 {# file will not be uploaded.}\n other {# files will not be uploaded.}} File size must be under {size}", "Uploader.noStorageHeader": "Not enough space", @@ -1541,10 +2137,12 @@ "VisibilityDropdown.visibilityHeader": "About resource visibility", "VisibilityDropdown.visibilityRequired": "Field is required", "channelEditVue.errorChooseAtLeastOneCorrectAnswer": "Choose at least one correct answer", + "channelEditVue.errorInvalidQuestionType": "Invalid question type", "channelEditVue.errorMissingAnswer": "Choose a correct answer", "channelEditVue.errorProvideAtLeastOneCorrectAnswer": "Provide at least one correct answer", "channelEditVue.errorQuestionRequired": "Question is required", "channelEditVue.false": "गलत", + "channelEditVue.questionTypeFreeResponse": "Free response", "channelEditVue.questionTypeInput": "Numeric input", "channelEditVue.questionTypeMultipleSelection": "Multiple choice", "channelEditVue.questionTypePerseus": "Perseus", @@ -1557,7 +2155,7 @@ "sharedVue.activityDurationRequired": "यह जानकारी ज़रूरी है", "sharedVue.activityDurationTooLongWarning": "This value is very high. Please make sure this is how long learners should work on the resource for, in order to complete it.", "sharedVue.addAdditionalCatgoriesDescription": "You selected resources that have different categories. The categories you choose below will be added to all selected resources. This will not remove existing categories.", - "sharedVue.changesSaved": "Changes saved", + "sharedVue.changesSaved": "परिवर्तन सहेजे गए", "sharedVue.confirmLogout": "Changes you made may not be saved. Are you sure you want to leave this page?", "sharedVue.copyrightHolderRequired": "Copyright holder is required", "sharedVue.durationRequired": "Duration is required", @@ -1578,4 +2176,4 @@ "sharedVue.masteryModelRequired": "Mastery is required", "sharedVue.shortActivityLteThirty": "Value must be equal or less than 30", "sharedVue.titleRequired": "Title is required" -} +} \ No newline at end of file diff --git a/contentcuration/locale/hi_IN/LC_MESSAGES/django.po b/contentcuration/locale/hi_IN/LC_MESSAGES/django.po index 523e513477..c8729a9817 100644 --- a/contentcuration/locale/hi_IN/LC_MESSAGES/django.po +++ b/contentcuration/locale/hi_IN/LC_MESSAGES/django.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: kolibri-studio\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-30 18:12+0000\n" -"PO-Revision-Date: 2024-09-11 20:07\n" +"POT-Creation-Date: 2026-04-16 00:57+0000\n" +"PO-Revision-Date: 2026-04-20 19:33\n" "Last-Translator: \n" "Language-Team: Hindi\n" "Language: hi_IN\n" @@ -18,47 +18,47 @@ msgstr "" "X-Crowdin-File-ID: 4322\n" #: contentcuration/catalog_settings.py:4 contentcuration/sandbox_settings.py:8 -#: contentcuration/settings.py:289 +#: contentcuration/settings.py:271 msgid "Arabic" msgstr "" -#: contentcuration/middleware/db_readonly.py:24 +#: contentcuration/middleware/db_readonly.py:25 msgid "The site is currently in read-only mode. Please try again later." msgstr "" -#: contentcuration/models.py:295 +#: contentcuration/models.py:356 msgid "Not enough space. Check your storage under Settings page." msgstr "" -#: contentcuration/models.py:312 contentcuration/models.py:319 +#: contentcuration/models.py:440 contentcuration/models.py:452 msgid "Out of storage! Request more space under Settings > Storage." msgstr "" -#: contentcuration/models.py:1767 +#: contentcuration/models.py:2531 msgid " (Original)" msgstr "" -#: contentcuration/models.py:2657 +#: contentcuration/models.py:3848 msgid "Created DateTime" msgstr "" -#: contentcuration/models.py:2658 +#: contentcuration/models.py:3850 msgid "Datetime field when the custom_metadata for task was created in UTC" msgstr "" -#: contentcuration/settings.py:287 +#: contentcuration/settings.py:269 msgid "English" msgstr "" -#: contentcuration/settings.py:288 +#: contentcuration/settings.py:270 msgid "Spanish" msgstr "" -#: contentcuration/settings.py:290 +#: contentcuration/settings.py:272 msgid "French" msgstr "" -#: contentcuration/settings.py:291 +#: contentcuration/settings.py:273 msgid "Portuguese" msgstr "" @@ -575,151 +575,152 @@ msgstr "" msgid "You can also try updating your current browser." msgstr "आप अपने वर्तमान ब्राउज़र को अपडेट करने का प्रयास भी कर सकते हैं ।" -#: contentcuration/templatetags/license_tags.py:11 +#: contentcuration/templatetags/license_tags.py:13 msgid "The Attribution License lets others distribute, remix, tweak, and build upon your work, even commercially, as long as they credit you for the original creation. This is the most accommodating of licenses offered. Recommended for maximum dissemination and use of licensed materials." msgstr "" -#: contentcuration/templatetags/license_tags.py:16 +#: contentcuration/templatetags/license_tags.py:20 msgid "The Attribution-ShareAlike License lets others remix, tweak, and build upon your work even for commercial purposes, as long as they credit you and license their new creations under the identical terms. This license is often compared to \"copyleft\" free and open source software licenses. All new works based on yours will carry the same license, so any derivatives will also allow commercial use. This is the license used by Wikipedia, and is recommended for materials that would benefit from incorporating content from Wikipedia and similarly licensed projects." msgstr "" -#: contentcuration/templatetags/license_tags.py:26 +#: contentcuration/templatetags/license_tags.py:32 msgid "The Attribution-NoDerivs License allows for redistribution, commercial and non-commercial, as long as it is passed along unchanged and in whole, with credit to you." msgstr "" -#: contentcuration/templatetags/license_tags.py:29 +#: contentcuration/templatetags/license_tags.py:37 msgid "The Attribution-NonCommercial License lets others remix, tweak, and build upon your work non-commercially, and although their new works must also acknowledge you and be non-commercial, they don't have to license their derivative works on the same terms." msgstr "" -#: contentcuration/templatetags/license_tags.py:33 +#: contentcuration/templatetags/license_tags.py:43 msgid "The Attribution-NonCommercial-ShareAlike License lets others remix, tweak, and build upon your work non-commercially, as long as they credit you and license their new creations under the identical terms." msgstr "" -#: contentcuration/templatetags/license_tags.py:37 +#: contentcuration/templatetags/license_tags.py:49 msgid "The Attribution-NonCommercial-NoDerivs License is the most restrictive of our six main licenses, only allowing others to download your works and share them with others as long as they credit you, but they can't change them in any way or use them commercially." msgstr "" -#: contentcuration/templatetags/license_tags.py:41 +#: contentcuration/templatetags/license_tags.py:55 msgid "The All Rights Reserved License indicates that the copyright holder reserves, or holds for their own use, all the rights provided by copyright law under one specific copyright treaty." msgstr "" -#: contentcuration/templatetags/license_tags.py:44 +#: contentcuration/templatetags/license_tags.py:60 msgid "Public Domain work has been identified as being free of known restrictions under copyright law, including all related and neighboring rights." msgstr "" -#: contentcuration/templatetags/license_tags.py:47 +#: contentcuration/templatetags/license_tags.py:65 msgid "Special Permissions is a custom license to use when the current licenses do not apply to the content. The owner of this license is responsible for creating a description of what this license entails." msgstr "" -#: contentcuration/utils/csv_writer.py:45 -#: contentcuration/utils/csv_writer.py:108 +#: contentcuration/utils/csv_writer.py:49 +#: contentcuration/utils/csv_writer.py:140 msgid "No Channel" msgstr "" -#: contentcuration/utils/csv_writer.py:46 +#: contentcuration/utils/csv_writer.py:50 msgid "No resource" msgstr "" -#: contentcuration/utils/csv_writer.py:71 +#: contentcuration/utils/csv_writer.py:86 msgid "Channel" msgstr "चैनल" -#: contentcuration/utils/csv_writer.py:71 +#: contentcuration/utils/csv_writer.py:87 msgid "Title" msgstr "शीर्षक" -#: contentcuration/utils/csv_writer.py:71 +#: contentcuration/utils/csv_writer.py:88 msgid "Kind" msgstr "" -#: contentcuration/utils/csv_writer.py:71 +#: contentcuration/utils/csv_writer.py:89 msgid "Filename" msgstr "" -#: contentcuration/utils/csv_writer.py:71 +#: contentcuration/utils/csv_writer.py:90 msgid "File Size" msgstr "" -#: contentcuration/utils/csv_writer.py:71 +#: contentcuration/utils/csv_writer.py:91 msgid "URL" msgstr "" -#: contentcuration/utils/csv_writer.py:71 +#: contentcuration/utils/csv_writer.py:92 msgid "Description" msgstr "विवरण" -#: contentcuration/utils/csv_writer.py:72 +#: contentcuration/utils/csv_writer.py:93 msgid "Author" msgstr "लेखक" -#: contentcuration/utils/csv_writer.py:72 +#: contentcuration/utils/csv_writer.py:94 msgid "Language" msgstr "भाषा " -#: contentcuration/utils/csv_writer.py:72 +#: contentcuration/utils/csv_writer.py:95 msgid "License" msgstr "लाइसेंस" -#: contentcuration/utils/csv_writer.py:72 +#: contentcuration/utils/csv_writer.py:96 msgid "License Description" msgstr "" -#: contentcuration/utils/csv_writer.py:72 +#: contentcuration/utils/csv_writer.py:97 msgid "Copyright Holder" msgstr "" -#: contentcuration/utils/csv_writer.py:108 +#: contentcuration/utils/csv_writer.py:141 msgid "No Resource" msgstr "" -#: contentcuration/utils/csv_writer.py:108 +#: contentcuration/utils/csv_writer.py:143 msgid "Staged File" msgstr "" -#: contentcuration/utils/incidents.py:7 +#: contentcuration/utils/incidents.py:8 msgid "There was a problem with a third-party service. This means certain operations might be blocked. We appreciate your patience while these issues are being resolved." msgstr "" -#: contentcuration/utils/incidents.py:16 +#: contentcuration/utils/incidents.py:18 msgid "EMERGENCY MAINTENANCE Kolibri Studio is operating on read-only mode for the time being in order for us to resolve some maintenance issues. This means all editing capabilities are disabled at the moment. We're currently working very hard to fix the issue as soon as possible. If you have any questions please contact us at content@learningequality.org. We apologize for any inconvenience caused and appreciate your patience while we resolve these issues." msgstr "" -#: contentcuration/utils/incidents.py:31 +#: contentcuration/utils/incidents.py:34 msgid "EMERGENCY MAINTENANCE Kolibri Studio is operating on read-only mode for the time being in order for us to resolve some database issues. This means all editing capabilities are disabled at the moment. We're currently working very hard to fix the issue as soon as possible. If you have any questions please contact us at content@learningequality.org. We apologize for any inconvenience caused and appreciate your patience while we resolve these issues." msgstr "" -#: contentcuration/utils/incidents.py:46 +#: contentcuration/utils/incidents.py:50 msgid "We are encountering issues with Google Cloud Storage. This means any file uploading and publishing operations are currently unavailable. We appreciate your patience while these issues are being resolved. To check the status of this service, please visit here" msgstr "" -#: contentcuration/utils/incidents.py:57 +#: contentcuration/utils/incidents.py:62 msgid "We are encountering issues with a third-party service. This means publishing is currently unavailable. We appreciate your patience while these issues are being resolved." msgstr "" -#: contentcuration/utils/incidents.py:65 +#: contentcuration/utils/incidents.py:71 msgid "We are encountering issues with our data center. This means you may encounter networking problems while using Studio. We appreciate your patience while these issues are being resolved. To check the status of this service, please visit here" msgstr "" -#: contentcuration/utils/publish.py:101 +#: contentcuration/utils/publish.py:103 msgid "Kolibri Studio Channel Published" msgstr "" -#: contentcuration/views/settings.py:111 +#: contentcuration/views/settings.py:115 msgid "Kolibri Studio issue report" msgstr "" -#: contentcuration/views/settings.py:143 +#: contentcuration/views/settings.py:147 msgid "Kolibri Studio account deleted" msgstr "" -#: kolibri_public/views.py:220 +#: kolibri_public/views.py:323 msgid "Resource" msgstr "संसाधन" -#: kolibri_public/views_v1.py:65 kolibri_public/views_v1.py:76 -msgid "Api endpoint {} is not available" +#: kolibri_public/views_v1.py:152 kolibri_public/views_v1.py:167 +msgid "API version is unavailable" msgstr "" -#: kolibri_public/views_v1.py:78 +#: kolibri_public/views_v1.py:170 msgid "No channel matching {} found" msgstr "" + diff --git a/contentcuration/locale/pt_BR/LC_MESSAGES/contentcuration-messages.json b/contentcuration/locale/pt_BR/LC_MESSAGES/contentcuration-messages.json index 677eb7883d..6fd2349d0b 100644 --- a/contentcuration/locale/pt_BR/LC_MESSAGES/contentcuration-messages.json +++ b/contentcuration/locale/pt_BR/LC_MESSAGES/contentcuration-messages.json @@ -67,8 +67,6 @@ "AdministrationAppError.unauthorizedDetails": "Você precisa ser um administrador do Studio para visualizar esta página", "AdministrationIndex.channelsLabel": "Canais", "AdministrationIndex.usersLabel": "Usuários", - "Alert.closeButtonLabel": "OK", - "Alert.dontShowAgain": "Não mostrar esta mensagem novamente", "AnswersEditor.answersLabel": "Respostas", "AnswersEditor.newAnswerBtnLabel": "Nova resposta", "AnswersEditor.noAnswersPlaceholder": "A pergunta não tem opções de resposta", @@ -184,26 +182,31 @@ "CatalogFilterBar.keywords": "\"{text}\"", "CatalogFilterBar.starred": "Favoritos", "CatalogFilterBar.subtitles": "Legendas", - "CatalogFilters.coachDescription": "Os materiais para professores são visíveis apenas para eles no Kolibri", - "CatalogFilters.coachLabel": "Materiais para professores", - "CatalogFilters.copyright": "© {year} Learning Equality", - "CatalogFilters.formatLabel": "Formatos", - "CatalogFilters.frequentlyAskedQuestionsLink": "Perguntas frequentes", - "CatalogFilters.includesLabel": "Mostrar apenas canais com", - "CatalogFilters.licenseLabel": "Licenças", - "CatalogFilters.searchLabel": "Palavras-chave", - "CatalogFilters.searchText": "Buscar", - "CatalogFilters.starredLabel": "Favoritos", - "CatalogFilters.subtitlesLabel": "Legendas", + "CatalogFilterPanelContent.coachDescription": "Os materiais para professores são visíveis apenas para eles no Kolibri", + "CatalogFilterPanelContent.coachLabel": "Materiais para professores", + "CatalogFilterPanelContent.copyright": "© {year} Learning Equality", + "CatalogFilterPanelContent.formatLabel": "Formatos", + "CatalogFilterPanelContent.frequentlyAskedQuestionsLink": "Perguntas frequentes", + "CatalogFilterPanelContent.includesLabel": "Mostrar apenas canais com", + "CatalogFilterPanelContent.licenseLabel": "Licenças", + "CatalogFilterPanelContent.searchLabel": "Palavras-chave", + "CatalogFilterPanelContent.starredLabel": "Favoritos", + "CatalogFilterPanelContent.subtitlesLabel": "Legendas", + "CatalogFilters.filterLabel": "filtrar", "CatalogList.cancelButton": "Cancelar", "CatalogList.channelSelectionCount": "{count, plural, one {}\n =1 {# canal selecionado}\n other {# canais selecionados}}", + "CatalogList.copyToken": "Copiar token do canal", "CatalogList.downloadButton": "Baixar", "CatalogList.downloadCSV": "Baixar CSV", "CatalogList.downloadPDF": "Baixar PDF", "CatalogList.downloadingMessage": "Download iniciado", + "CatalogList.goToWebsite": "Ir para o website de origem", + "CatalogList.moreOptions": "Outras opções", "CatalogList.resultsText": "{count, plural, one {}\n =1 {# resultado encontrado}\n other {# resultados encontrados}}", "CatalogList.selectAll": "Selecionar todos", "CatalogList.selectChannels": "Baixar um resumo dos canais selecionados", + "CatalogList.title": "Biblioteca de conteúdo", + "CatalogList.viewContent": "Visualizar canal no Kolibri", "CategoryOptions.noCategoryFoundText": "Categoria não encontrada", "ChangePasswordForm.cancelAction": "Cancelar", "ChangePasswordForm.changePasswordHeader": "Mudar de senha", @@ -224,9 +227,6 @@ "ChannelCatalogFrontPage.languagesHeading": "Idiomas", "ChannelCatalogFrontPage.numberOfChannels": "{ num } canais", "ChannelCatalogFrontPage.subtitlesIncludedText": "Legendas", - "ChannelDeletedError.backToHomeAction": "Voltar à página inicial", - "ChannelDeletedError.channelDeletedDetails": "Este canal não existe ou pode ter sido removido. Por favor, envie um e-mail para content@learningequality.org se achar que isto é um erro.", - "ChannelDeletedError.channelDeletedHeader": "Canal não encontrado", "ChannelDetailsModal.downloadButton": "Baixar resumo do canal", "ChannelDetailsModal.downloadCSV": "Baixar CSV", "ChannelDetailsModal.downloadPDF": "Baixar PDF", @@ -263,23 +263,17 @@ "ChannelInvitation.editText": "{sender} te convidou para editar {channel}", "ChannelInvitation.goToChannelSnackbarAction": "Ir para o canal", "ChannelInvitation.viewText": "{sender} te convidou para visualizar {channel}", - "ChannelItem.cancel": "Cancelar", "ChannelItem.channelDeletedSnackbar": "Canal excluído", "ChannelItem.channelLanguageNotSetIndicator": "Nenhum idioma definido", "ChannelItem.channelRemovedSnackbar": "Canal removido", "ChannelItem.copyToken": "Copiar token do canal", "ChannelItem.deleteChannel": "Excluir canal", - "ChannelItem.deletePrompt": "Este canal será excluído permanentemente. Esta ação não pode ser desfeita.", - "ChannelItem.deleteTitle": "Excluir este canal", "ChannelItem.details": "Detalhes", "ChannelItem.editChannel": "Editar detalhes do canal", "ChannelItem.goToWebsite": "Ir para o website de origem", "ChannelItem.lastPublished": "Publicado em {last_published}", "ChannelItem.lastUpdated": "Atualizado {updated}", - "ChannelItem.removeBtn": "Remover", - "ChannelItem.removeChannel": "Remover da lista de canais", - "ChannelItem.removePrompt": "Você tem acesso somente de visualização para este canal. Confirme que você deseja removê-lo da sua lista de canais.", - "ChannelItem.removeTitle": "Remover da lista de canais", + "ChannelItem.removeChannel": "Remove channel", "ChannelItem.resourceCount": "{count, plural, one {}\n =1 {# conteúdo}\n other {# conteúdos}}", "ChannelItem.unpublishedText": "Não publicado", "ChannelItem.versionText": "Versão {version}", @@ -292,7 +286,6 @@ "ChannelListIndex.catalog": "Biblioteca de conteúdo", "ChannelListIndex.channelSets": "Conjuntos", "ChannelListIndex.frequentlyAskedQuestions": "Perguntas frequentes", - "ChannelListIndex.invitations": "Você tem {count, plural, one {}\n =1 {# convite}\n other {# convites}}", "ChannelListIndex.libraryTitle": "Catálogo da Biblioteca de Conteúdos Kolibri", "ChannelModal.APIText": "Os canais gerados automaticamente não são editáveis.", "ChannelModal.changesSaved": "Alterações salvas", @@ -316,25 +309,6 @@ "ChannelNotFoundError.channelNotFoundHeader": "Canal não encontrado", "ChannelSelectionList.noChannelsFound": "Nenhum canal encontrado", "ChannelSelectionList.searchText": "Buscar canal", - "ChannelSetItem.cancel": "Cancelar", - "ChannelSetItem.delete": "Excluir conjunto", - "ChannelSetItem.deleteChannelSetText": "Tem certeza que deseja excluir esta coleção?", - "ChannelSetItem.deleteChannelSetTitle": "Excluir conjunto", - "ChannelSetItem.edit": "Editar conjunto", - "ChannelSetItem.options": "Opções", - "ChannelSetItem.saving": "Salvando", - "ChannelSetList.aboutChannelSets": "Sobre os conjuntos", - "ChannelSetList.aboutChannelSetsLink": "Saiba mais sobre os conjuntos", - "ChannelSetList.addChannelSetTitle": "Novo conjunto", - "ChannelSetList.cancelButtonLabel": "Fechar", - "ChannelSetList.channelNumber": "Número de canais", - "ChannelSetList.channelSetsDescriptionText": "Um conjunto contém vários canais do Kolibri Studio que podem ser importados de uma vez ao Kolibri com um único token de conjunto.", - "ChannelSetList.channelSetsDisclaimer": "Você precisará da versão 0.12.0 ou superior do Kolibri para importar conjuntos de canais", - "ChannelSetList.channelSetsInstructionsText": "Você pode fazer um conjunto selecionando os canais que quer importar juntos.", - "ChannelSetList.noChannelSetsFound": "Você pode integrar vários canais para criar um conjunto. O conjunto inteiro pode então ser importado ao Kolibri de uma vez por meio de um token de conjunto.", - "ChannelSetList.options": "Opções", - "ChannelSetList.title": "Nome do conjunto", - "ChannelSetList.token": "ID do token", "ChannelSetModal.bookmark": "Favoritos", "ChannelSetModal.channelAdded": "Canal adicionado", "ChannelSetModal.channelCountText": "{channelCount, plural, one {} =0 {Não há canais publicados no seu conjunto} =1 {# canal} other {# canais}}", @@ -533,6 +507,163 @@ "CommonMetadataStrings.webDesign": "Desenho de Web", "CommonMetadataStrings.work": "Trabalho", "CommonMetadataStrings.writing": "Escrita", + "CommonStrings.backAction": "Voltar", + "CommonStrings.channelDetailsLabel": "Channel Details", + "CommonStrings.clearAction": "Limpar", + "CommonStrings.closeAction": "Fechar", + "CommonStrings.copyChannelTokenAction": "Copiar token do canal", + "CommonStrings.dismissAction": "Descartar", + "CommonStrings.genericErrorMessage": "Desculpe, algo deu errado. Tente novamente.", + "CommonStrings.previewAction": "Pré-visualizar", + "CommonStrings.seeAllAction": "See all", + "CommonStrings.seeLessAction": "See less", + "CommunityChannelsStrings.aboutCommunityLibraryDescription": "Community library contains channels submitted by the community and approved for discovery in Studio.", + "CommunityChannelsStrings.aboutCommunityLibraryTitle": "About Community Library", + "CommunityChannelsStrings.activityHistoryLabel": "Activity history", + "CommunityChannelsStrings.adminLabel": "Administrador", + "CommunityChannelsStrings.allLicensesCompatible": "All licenses are compatible with Community Library.", + "CommunityChannelsStrings.allNotificationsLabel": "All Notifications", + "CommunityChannelsStrings.alreadySubmittedWarningDescription": "Please wait for the channel to be reviewed. You will see a notification in your Studio account after the review is complete.", + "CommunityChannelsStrings.alreadySubmittedWarningTitle": "This version of the channel has already been submitted to the Community Library.", + "CommunityChannelsStrings.approvedNotification": "{author} ({userType}) approved {channelVersion}", + "CommunityChannelsStrings.approvedPrimaryInfo": "A previous version is live in the Community Library. Reviewers will see the latest submission first.", + "CommunityChannelsStrings.approvedStatus": "Approved", + "CommunityChannelsStrings.availableStatus": "Available in Community Library", + "CommunityChannelsStrings.cancelAction": "Cancelar", + "CommunityChannelsStrings.categoriesLabel": "Categorias", + "CommunityChannelsStrings.channelCannotBeDistributed": "This channel cannot be distributed via Kolibri.", + "CommunityChannelsStrings.channelFitAttributionLabel": "Attribution", + "CommunityChannelsStrings.channelFitChannelInfoLabel": "Channel information", + "CommunityChannelsStrings.channelFitChecklistAttribution": "Does each resource have an author listed?", + "CommunityChannelsStrings.channelFitChecklistChannelInfo": "Is your channel's basic information filled in — title, description, thumbnail, language, category, and level?", + "CommunityChannelsStrings.channelFitChecklistIntro": "Criteria for submitting to the Community Library", + "CommunityChannelsStrings.channelFitChecklistLicense": "Are the resources in your channel openly licensed or in the public domain?", + "CommunityChannelsStrings.channelFitChecklistOfflineUse": "Do the resources in your channel work without an Internet connection?", + "CommunityChannelsStrings.channelFitChecklistQuality": "Has anyone on your team or in your organization reviewed the channel?", + "CommunityChannelsStrings.channelFitLicenseLabel": "Licença", + "CommunityChannelsStrings.channelFitOfflineUseLabel": "Offline use", + "CommunityChannelsStrings.channelFitQualityLabel": "Quality", + "CommunityChannelsStrings.channelTokenDescription": "You can use this token to import and preview the draft channel in Kolibri. Please note that the token for the final published channel will be different.", + "CommunityChannelsStrings.channelVersion": "{name} v{version}", + "CommunityChannelsStrings.channelVersionTokenLabel": "Channel version token", + "CommunityChannelsStrings.clearAll": "Limpar tudo", + "CommunityChannelsStrings.clearAllAction": "Limpar tudo", + "CommunityChannelsStrings.communityLibraryCTADescription": "Have a channel worth sharing with other educators and learners? Submit it for review through the Share menu so it can be discovered in Studio.", + "CommunityChannelsStrings.communityLibraryCTATitle": "Help grow the Community Library", + "CommunityChannelsStrings.communityLibraryDescription": "Browse community-submitted channels approved for discovery in Studio. Copy a token to use a channel in Kolibri.", + "CommunityChannelsStrings.communityLibraryLabel": "Community Library", + "CommunityChannelsStrings.communityLibrarySubmissionLabel": "Community Library submission", + "CommunityChannelsStrings.compatibleLicensesDescription": "{licenseNames} - All licenses are compatible with Community Library.", + "CommunityChannelsStrings.confirmDistributionRights": "Please confirm you have the right to distribute these resources via Kolibri.", + "CommunityChannelsStrings.confirmReplacementText": "I understand this will replace my earlier submission on the review queue", + "CommunityChannelsStrings.countriesInfoText": "Select one or more countries to label your channel submission with. For example, if your channel contains materials aligned to a national curriculum or regionally-specific content, selecting the relevant countries will help users find it. Otherwise, leave this blank.", + "CommunityChannelsStrings.countryLabel": "Países", + "CommunityChannelsStrings.descriptionLabel": "Describe what's included in this submission", + "CommunityChannelsStrings.dismissAction": "Descartar", + "CommunityChannelsStrings.draftBeingPublishedNotice": "Draft version is being published", + "CommunityChannelsStrings.draftPublishedNotice": "Draft published successfully", + "CommunityChannelsStrings.draftTokenLabel": "Draft token", + "CommunityChannelsStrings.editorLabel": "Editor", + "CommunityChannelsStrings.emptyNotificationsNotice": "You have no notifications at this time.", + "CommunityChannelsStrings.emptyNotificationsWithFiltersNotice": "No notifications match the applied filters.", + "CommunityChannelsStrings.errorLoadingVersions": "Unable to load version history", + "CommunityChannelsStrings.errorSnackbar": "There was an error submitting the channel", + "CommunityChannelsStrings.feedbackNotesLabel": "Notes from the reviewer", + "CommunityChannelsStrings.filterByDateLabel": "Filter by date", + "CommunityChannelsStrings.filterByStatusLabel": "Filter by status", + "CommunityChannelsStrings.filterLabel": "filtrar", + "CommunityChannelsStrings.fixLicensingBeforeSubmission": "Please fix licensing before submitting a new version.", + "CommunityChannelsStrings.flaggedNotification": "Changes required for version {channelVersion}", + "CommunityChannelsStrings.flaggedStatus": "Needs changes", + "CommunityChannelsStrings.getDraftTokenAction": "Copy token for draft channel", + "CommunityChannelsStrings.goToMyChannelsAction": "Go to My channels", + "CommunityChannelsStrings.hideCriteriaAction": "Hide criteria", + "CommunityChannelsStrings.hideVersions": "Hide versions", + "CommunityChannelsStrings.incompatibleLicensesDescription": "\"{licenseNames}\" - this channel cannot be distributed via Kolibri. If you cannot change the license, remove all resources with \"{licenseNames}\" before submitting again.", + "CommunityChannelsStrings.incompatibleLicensesDetected": "Incompatible licenses detected.", + "CommunityChannelsStrings.incompleteResourcesDescription1": "Conteúdos incompletos não poderão ser publicados e disponibilizados para uso na Plataforma de Aprendizagem Kolibri.", + "CommunityChannelsStrings.incompleteResourcesWarning": "{count, number} {count, plural, one {incomplete resource} other {incomplete resources}}", + "CommunityChannelsStrings.internalNotesLabel": "Admin notes (internal use only)", + "CommunityChannelsStrings.invalidLicensingReason": "Invalid or non-compliant licenses", + "CommunityChannelsStrings.invalidMetadataReason": "Invalid or missing metadata", + "CommunityChannelsStrings.languageLabel": "Idioma", + "CommunityChannelsStrings.languagesLabel": "Idiomas", + "CommunityChannelsStrings.lessDetailsButton": "Hide details", + "CommunityChannelsStrings.licenseCheckPassed": "License check passed.", + "CommunityChannelsStrings.licensesLabel": "Licenças", + "CommunityChannelsStrings.loadError": "There was an error loading channels.", + "CommunityChannelsStrings.loadingVersionHistory": "Loading version history", + "CommunityChannelsStrings.moreDetails": "After your submission is approved, the channel will be available to other Kolibri Studio users on the 'Community Library' page.", + "CommunityChannelsStrings.moreDetailsButton": "More details", + "CommunityChannelsStrings.needKolibriVersionToImport": "You will need Kolibri version 0.19.4 or higher to import channels from the Community Library.", + "CommunityChannelsStrings.needsChangesPrimaryInfo": "Your previously submitted version needs changes. Make sure you have addressed all comments before resubmitting.", + "CommunityChannelsStrings.newLabel": "Novo", + "CommunityChannelsStrings.newNotificationsNotice": "New notifications available.", + "CommunityChannelsStrings.nextPageAction": "Próximo", + "CommunityChannelsStrings.noCommunityChannels": "No channels have been published to the Community Library yet.", + "CommunityChannelsStrings.noResultsWithFilters": "No channels match the selected filters.", + "CommunityChannelsStrings.noVersionsAvailable": "No version history available", + "CommunityChannelsStrings.nonePrimaryInfo": "We're inviting members of the Kolibri community to submit channels they've created for offline learning in low-resource settings. ", + "CommunityChannelsStrings.notPublishedWarningDescription": "Publish to Studio first, then submit to the Community Library.", + "CommunityChannelsStrings.notPublishedWarningTitle": "This channel isn't published to Kolibri Studio yet", + "CommunityChannelsStrings.notificationsLabel": "Notifications", + "CommunityChannelsStrings.otherIssuesReason": "Other issues", + "CommunityChannelsStrings.pageIndicator": "{currentPage} de {totalPages}", + "CommunityChannelsStrings.pendingStatus": "Submitted", + "CommunityChannelsStrings.portabilityIssuesReason": "Portability problems", + "CommunityChannelsStrings.previewYourDraftTitle": "Preview your draft channel in Kolibri", + "CommunityChannelsStrings.previousPageAction": "Pergunta anterior", + "CommunityChannelsStrings.publicWarningDescription": "It is not possible to submit public channels to the Community Library.", + "CommunityChannelsStrings.publicWarningTitle": "This channel is currently public in the Kolibri Library.", + "CommunityChannelsStrings.publishAction": "Publicar", + "CommunityChannelsStrings.publishChannel": "Publish channel", + "CommunityChannelsStrings.publishChannelDescription": "To see your channel in Kolibri, import using the channel token.", + "CommunityChannelsStrings.publishChannelMode": "Publish channel", + "CommunityChannelsStrings.publishDraftDescription": "Your channel will be saved as a draft, allowing you to review and do quality checks on the draft, without altering the current version available for Kolibri users. To see this draft channel in Kolibri, import using the draft channel token.", + "CommunityChannelsStrings.publishDraftMode": "Publish draft channel", + "CommunityChannelsStrings.publishedVersionLabel": "Versão publicada:", + "CommunityChannelsStrings.publishingInfo": "You're publishing: Version {version}", + "CommunityChannelsStrings.publishingMessage": "Channel is being published", + "CommunityChannelsStrings.qualityAssuranceReason": "Quality assurance issues", + "CommunityChannelsStrings.reasonLabel": "Reason: {reason}", + "CommunityChannelsStrings.resubmitAction": "Resubmit", + "CommunityChannelsStrings.resubmitModalBodyFirst": "{channelName} v{version} is also published to the Community Library.", + "CommunityChannelsStrings.resubmitModalBodySecond": "Would you like to resubmit this version with your changes for Community Library review?", + "CommunityChannelsStrings.resubmitModalTitle": "Resubmit channel for Community library review?", + "CommunityChannelsStrings.resultsText": "{count, plural, =1 {# result found} other {# results found}}", + "CommunityChannelsStrings.retry": "Tentar novamente", + "CommunityChannelsStrings.reviewAction": "Revisar", + "CommunityChannelsStrings.saveDraft": "Save draft", + "CommunityChannelsStrings.searchLabel": "Buscar", + "CommunityChannelsStrings.searchNotificationsLabel": "Search notifications", + "CommunityChannelsStrings.seeAllVersions": "See all versions", + "CommunityChannelsStrings.showMore": "Mostrar mais", + "CommunityChannelsStrings.showOlderAction": "Show older", + "CommunityChannelsStrings.specialPermissionsDetected": "Special Permissions licenses detected", + "CommunityChannelsStrings.submissionCreationNotification": "Your submission to the Community Library was successful and is now under review.", + "CommunityChannelsStrings.submissionNotesLabel": "Submission notes", + "CommunityChannelsStrings.submissionNotification": "{author} ({userType}) submitted {channelVersion}", + "CommunityChannelsStrings.submitButton": "Submit for review", + "CommunityChannelsStrings.submitToCommunityLibrary": "Submit to Community Library", + "CommunityChannelsStrings.submittedPrimaryInfo": "A previous version is still pending review. Reviewers will see the most recent submission by default.", + "CommunityChannelsStrings.submittedSnackbar": "Channel submitted to Community Library", + "CommunityChannelsStrings.submittingSnackbar": "Submitting channel to Community Library...", + "CommunityChannelsStrings.supersededStatus": "Superseded", + "CommunityChannelsStrings.thisMonthLabel": "Este mês", + "CommunityChannelsStrings.thisWeekLabel": "This week", + "CommunityChannelsStrings.thisYearLabel": "This year", + "CommunityChannelsStrings.todayLabel": "Today", + "CommunityChannelsStrings.unreadNotificationsLabel": "Unread", + "CommunityChannelsStrings.versionDescriptionLabel": "Descrição da versão", + "CommunityChannelsStrings.versionLabel": "Versão {version}", + "CommunityChannelsStrings.versionNotesLabel": "Descreva o que há de novo nesta versão do canal", + "CommunityChannelsStrings.viewCriteriaAction": "View criteria", + "CommunityChannelsStrings.viewMoreAction": "Ver mais", + "CommunityChannelsStrings.whatCanYouDoHere": "What you can do here", + "CommunityChannelsStrings.whatCanYouDoHereItem1": "Browse channels by country, category, and language", + "CommunityChannelsStrings.whatCanYouDoHereItem2": "Copy a channel token to use in Kolibri", + "CommunityChannelsStrings.whatCanYouDoHereItem3": "View channel details including description and metadata", + "CommunityChannelsStrings.whatIsCommunityLibrary": "What is the Community Library?", "CommunityStandardsModal.communityStandardsHeader": "Normas comunitárias", "CommunityStandardsModal.coreValuesLink": "Saiba mais sobre os valores fundamentais da Learning Equality", "CommunityStandardsModal.description": "A Learning Equality é uma organização sem fins lucrativos dedicada a possibilitar o acesso igualitário a experiências de educação de qualidade. Junto à nossa declaração de valores fundamentais, estas normas comunitárias pretendem promover um ambiente solidário e inclusivo para nossos usuários.", @@ -819,40 +950,11 @@ "DeleteAccountForm.emailInvalidText": "O e-mail não corresponde ao e-mail da sua conta", "DeleteAccountForm.fieldRequired": "Campo obrigatório", "DeleteAccountForm.ok": "OK", - "DetailsPanel.AVERAGE": "Médio", - "DetailsPanel.LARGE": "Grande", - "DetailsPanel.SMALL": "Pequeno", - "DetailsPanel.VERY_LARGE": "Muito grande", - "DetailsPanel.VERY_SMALL": "Muito pequeno", - "DetailsPanel.aggregatorToolTip": "Website ou organização hospedando este conjunto de conteúdos, mas não necessariamente o criador ou detentor dos direitos autorais", - "DetailsPanel.aggregatorsLabel": "Agregadores", - "DetailsPanel.assessmentsIncludedText": "Avaliações", - "DetailsPanel.authorToolTip": "Pessoa ou organização que criou este conteúdo", - "DetailsPanel.authorsLabel": "Autores", - "DetailsPanel.categoriesHeading": "Categorias", - "DetailsPanel.coachDescription": "Os materiais para professores são visíveis apenas para eles no Kolibri", - "DetailsPanel.coachHeading": "Materiais para professores", - "DetailsPanel.containsContentHeading": "Contém conteúdo de", - "DetailsPanel.containsHeading": "Contém", - "DetailsPanel.copyrightHoldersLabel": "Detentores dos direitos autorais", - "DetailsPanel.creationHeading": "Criado em", - "DetailsPanel.currentVersionHeading": "Versão publicada", - "DetailsPanel.languagesHeading": "Idiomas", - "DetailsPanel.levelsHeading": "Níveis", - "DetailsPanel.licensesLabel": "Licenças", - "DetailsPanel.primaryLanguageHeading": "Idioma principal", - "DetailsPanel.providerToolTip": "Organização que encomendou ou distribui o conteúdo", - "DetailsPanel.providersLabel": "Provedores", - "DetailsPanel.publishedHeading": "Publicado em", - "DetailsPanel.resourceHeading": "Conteúdos totais", - "DetailsPanel.sampleFromChannelHeading": "Amostra de conteúdo deste canal", - "DetailsPanel.sampleFromTopicHeading": "Amostra de conteúdo deste tema", - "DetailsPanel.sizeHeading": "Tamanho do canal", - "DetailsPanel.sizeText": "{text} ({size})", - "DetailsPanel.subtitlesHeading": "Legendas", - "DetailsPanel.tagsHeading": "Etiquetas comuns", - "DetailsPanel.tokenHeading": "Token do canal", - "DetailsPanel.unpublishedText": "Não publicado", + "DeleteChannelModal.cancel": "Cancelar", + "DeleteChannelModal.channelDeletedSnackbar": "Canal excluído", + "DeleteChannelModal.deleteChannel": "Excluir canal", + "DeleteChannelModal.deletePrompt": "Este canal será excluído permanentemente. Esta ação não pode ser desfeita.", + "DeleteChannelModal.deleteTitle": "Excluir este canal", "DetailsTabView.aggregatorLabel": "Agregador", "DetailsTabView.aggregatorToolTip": "Website ou organização hospedando este conjunto de conteúdos, mas não necessariamente o criador ou detentor dos direitos autorais", "DetailsTabView.assessmentOptionsLabel": "Opções de avaliação", @@ -1008,10 +1110,8 @@ "ForgotPassword.forgotPasswordPrompt": "Por favor, digite seu endereço de e-mail para receber as instruções de redefinição de senha", "ForgotPassword.forgotPasswordTitle": "Redefinir sua senha", "ForgotPassword.submitButton": "Enviar", - "FormulasMenu.btnLabelInsert": "Inserir", - "FormulasMenu.formulasMenuTitle": "Caracteres especiais", "FormulasStrings.addition": "Addition", - "FormulasStrings.advancedCategory": "Advanced", + "FormulasStrings.advancedCategory": "Avançado", "FormulasStrings.alpha": "alpha", "FormulasStrings.and": "And", "FormulasStrings.angle": "Angle", @@ -1029,7 +1129,7 @@ "FormulasStrings.congruentTo": "Congruent to", "FormulasStrings.conjugate": "Conjugate", "FormulasStrings.conjugateTranspose": "Conjugate transpose", - "FormulasStrings.contains": "Contains", + "FormulasStrings.contains": "Contém", "FormulasStrings.contourIntegral": "Contour integral", "FormulasStrings.coproduct": "Coproduct", "FormulasStrings.definition": "Definition", @@ -1061,7 +1161,7 @@ "FormulasStrings.fraction": "Fraction", "FormulasStrings.gamma": "gamma", "FormulasStrings.gammaCapital": "Gamma", - "FormulasStrings.geometryCategory": "Geometry", + "FormulasStrings.geometryCategory": "Geometria", "FormulasStrings.givenThat": "Given that/Such that", "FormulasStrings.greaterThan": "Greater than", "FormulasStrings.greaterThanOrEqual": "Greater than or equal to", @@ -1078,7 +1178,7 @@ "FormulasStrings.lambda": "lambda", "FormulasStrings.lambdaCapital": "Lambda", "FormulasStrings.left": "Left", - "FormulasStrings.leftArrow": "Left arrow", + "FormulasStrings.leftArrow": "Seta para a esquerda", "FormulasStrings.leftCeiling": "Left ceiling", "FormulasStrings.leftDouble": "Left (double)", "FormulasStrings.leftFloor": "Left floor", @@ -1134,7 +1234,7 @@ "FormulasStrings.reducibleTo": "Reducible to", "FormulasStrings.rho": "rho", "FormulasStrings.right": "Right", - "FormulasStrings.rightArrow": "Right arrow", + "FormulasStrings.rightArrow": "Seta para a direita", "FormulasStrings.rightCeiling": "Right ceiling", "FormulasStrings.rightDouble": "Right (double)", "FormulasStrings.rightFloor": "Right floor", @@ -1155,13 +1255,13 @@ "FormulasStrings.southeast": "Southeast", "FormulasStrings.southwest": "Southwest", "FormulasStrings.spade": "Spade", - "FormulasStrings.squareRoot": "Square root", + "FormulasStrings.squareRoot": "Raiz quadrada", "FormulasStrings.subscript": "Subscript", "FormulasStrings.subset": "Subset", "FormulasStrings.subsetOrEqual": "Subset or equal", "FormulasStrings.subtraction": "Subtraction", "FormulasStrings.sum": "Sum", - "FormulasStrings.superscript": "Superscript", + "FormulasStrings.superscript": "Exponente", "FormulasStrings.superset": "Superset", "FormulasStrings.supersetOrEqual": "Superset or equal", "FormulasStrings.symmetricDifference": "Symmetric difference", @@ -1204,16 +1304,6 @@ "HintsEditor.newHintBtnLabel": "Nova dica", "HintsEditor.noHintsPlaceholder": "A pergunta não tem dicas", "ImageOnlyThumbnail.thumbnail": "{title} miniatura", - "ImagesMenu.acceptsText": "Tipos suportados de arquivos: {acceptedFormats}", - "ImagesMenu.altTextHint": "A descrição da imagem é necessária para possibilitar que alunos com deficiência visual respondam a perguntas, e também é exibida quando a imagem falha ao carregar", - "ImagesMenu.altTextLabel": "Descrição da imagem", - "ImagesMenu.btnLabelCancel": "Cancelar", - "ImagesMenu.btnLabelInsert": "Inserir", - "ImagesMenu.currentImageDefaultText": "Imagem atual", - "ImagesMenu.defaultDropText": "Arraste e solte uma imagem aqui ou envie-a manualmente", - "ImagesMenu.imageHeader": "Enviar imagem", - "ImagesMenu.selectFile": "Selecionar arquivo", - "ImagesMenu.selectFileButton": "Selecionar arquivo", "ImportFromChannelsModal.addButton": "Adicionar", "ImportFromChannelsModal.addedText": "Adicionado", "ImportFromChannelsModal.importAction": "Importar", @@ -1223,6 +1313,7 @@ "ImportFromChannelsModal.reviewAction": "Revisar", "ImportFromChannelsModal.reviewTitle": "Seleção de conteúdos", "InfoModal.close": "Fechar", + "InfoModal.open": "More information about licenses", "InheritAncestorMetadataModal.applyResourceDetailsTitle": "Aplicar detalhes da pasta '{folder}'", "InheritAncestorMetadataModal.cancelAction": "Cancelar", "InheritAncestorMetadataModal.categories": "Categorias: {categories}", @@ -1255,17 +1346,48 @@ "MainNavigationDrawer.helpLink": "Ajuda e suporte", "MainNavigationDrawer.logoutLink": "Terminar sessão", "MainNavigationDrawer.settingsLink": "Configurações", - "MarkdownEditor.bold": "Negrito (Ctrl+B)", - "MarkdownEditor.formulas": "Inserir fórmula (Ctrl+F)", - "MarkdownEditor.image": "Inserir imagem (Ctrl+P)", - "MarkdownEditor.italic": "Itálico (Ctrl+I)", - "MarkdownEditor.minimize": "Minimizar (Ctrl+M)", - "MarkdownImageField.editImageOption": "Editar", - "MarkdownImageField.removeImageOption": "Remover", - "MarkdownImageField.resizeImageOption": "Redimensionar", "MasteryCriteriaGoal.labelText": "Objetivo", "MasteryCriteriaMofNFields.mHint": "Respostas corretas são necessárias", "MasteryCriteriaMofNFields.nHint": "Respostas recentes", + "MathLiveA11yStrings.accented": "accented", + "MathLiveA11yStrings.array": "array", + "MathLiveA11yStrings.box": "box", + "MathLiveA11yStrings.chemicalFormula": "chemical formula", + "MathLiveA11yStrings.crossOut": "riscar", + "MathLiveA11yStrings.deleted": "excluído: ", + "MathLiveA11yStrings.delimiter": "delimiter", + "MathLiveA11yStrings.denominator": "denominator", + "MathLiveA11yStrings.endOf": "{spokenText}; end of {relationName}", + "MathLiveA11yStrings.endOfMathfield": "{spokenText}; end of mathfield", + "MathLiveA11yStrings.error": "erro", + "MathLiveA11yStrings.extensibleSymbol": "extensible symbol", + "MathLiveA11yStrings.first": "first", + "MathLiveA11yStrings.fraction": "fraction", + "MathLiveA11yStrings.group": "group", + "MathLiveA11yStrings.index": "index", + "MathLiveA11yStrings.latex": "LaTeX", + "MathLiveA11yStrings.line": "linha", + "MathLiveA11yStrings.mathField": "math field", + "MathLiveA11yStrings.mathfield": "math field", + "MathLiveA11yStrings.numerator": "numerator", + "MathLiveA11yStrings.operator": "operator", + "MathLiveA11yStrings.outOf": "out of {relationName};", + "MathLiveA11yStrings.overUnder": "over-under", + "MathLiveA11yStrings.parent": "parent", + "MathLiveA11yStrings.placeholder": "placeholder", + "MathLiveA11yStrings.prompt": "prompt", + "MathLiveA11yStrings.radicand": "radicand", + "MathLiveA11yStrings.rule": "rule", + "MathLiveA11yStrings.selected": "selecionado: ", + "MathLiveA11yStrings.space": "space", + "MathLiveA11yStrings.spacing": "spacing", + "MathLiveA11yStrings.squareRoot": "raiz quadrada", + "MathLiveA11yStrings.startOf": "start of {relationName}: ", + "MathLiveA11yStrings.subscript": "subscript", + "MathLiveA11yStrings.subscriptSuperscript": "subscript-superscript", + "MathLiveA11yStrings.superscript": "exponente", + "MathLiveA11yStrings.superscriptAndSubscript": "superscript and subscript", + "MathLiveA11yStrings.text": "text", "MessageLayout.backToLogin": "Continuar para a página de login", "MoveModal.addTopic": "Adicionar nova pasta", "MoveModal.cancel": "Cancelar", @@ -1296,7 +1418,7 @@ "PasswordField.passwordLabel": "Senha", "PasswordInstructionsSent.passwordInstructionsHeader": "Instruções enviadas. Obrigado!", "PasswordInstructionsSent.passwordInstructionsText": "Se uma conta com o e-mail fornecido já existe, você receberá as instruções em breve. Se não encontrar um e-mail nosso, por favor, verifique a sua pasta de spam.", - "PermissionsError.goToHomePageAction": "Ir para a página inicial", + "PermissionsError.backToHome": "Voltar à página inicial", "PermissionsError.permissionDeniedHeader": "Você se esqueceu de fazer o login?", "PoliciesModal.checkboxText": "Eu concordo com os termos acima", "PoliciesModal.closeButton": "Fechar", @@ -1306,6 +1428,7 @@ "PrivacyPolicyModal.updatedPrivacyHeader": "Política de privacidade atualizada", "ProgressBar.progressText": "{percent}%", "ProgressModal.defaultErrorText": "Falha na última tentativa de publicação", + "ProgressModal.draftHeader": "Saving draft...", "ProgressModal.lastPublished": "Publicado em {last_published}", "ProgressModal.publishHeader": "Publicando canal", "ProgressModal.syncError": "Falha na última tentativa de sincronização", @@ -1346,6 +1469,19 @@ "RelatedResourcesTab.showPreviewBtnLabel": "Mostrar", "RelatedResourcesTab.tooManyNextStepsWarning": "Limite o número de próximos passos para criar uma experiência de aprendizagem mais guiada", "RelatedResourcesTab.tooManyPreviousStepsWarning": "Limite o número de passos anteriores para criar uma experiência de aprendizagem mais guiada", + "RemoveChannelFromListModal.cancel": "Cancelar", + "RemoveChannelFromListModal.channelRemovedSnackbar": "Canal removido", + "RemoveChannelFromListModal.removeBtn": "Remover", + "RemoveChannelFromListModal.removePrompt": "Você tem acesso somente de visualização para este canal. Confirme que você deseja removê-lo da sua lista de canais.", + "RemoveChannelFromListModal.removeTitle": "Remover da lista de canais", + "RemoveChannelModal.cancel": "Cancelar", + "RemoveChannelModal.deleteChannel": "Excluir canal", + "RemoveChannelModal.deleteChannelWithCLWarning": "This channel has been shared with the Community Library. Deleting it here will not remove it from the Community Library — it may still be approved or remain available there.", + "RemoveChannelModal.deletePrompt": "Este canal será excluído permanentemente. Esta ação não pode ser desfeita.", + "RemoveChannelModal.deleteTitle": "Excluir este canal", + "RemoveChannelModal.removeBtn": "Remover", + "RemoveChannelModal.removePrompt": "Você tem acesso somente de visualização para este canal. Confirme que você deseja removê-lo da sua lista de canais.", + "RemoveChannelModal.removeTitle": "Remover da lista de canais", "ReportErrorModal.closeAction": "Fechar", "ReportErrorModal.emailDescription": "Entre em contato com o nosso suporte técnico detalhando o erro e faremos o possível para ajudar.", "ReportErrorModal.emailPrompt": "Enviar um e-mail para os desenvolvedores", @@ -1533,8 +1669,11 @@ "SearchRecommendationsStrings.tooAdvancedForLearnersLabel": "Muito avançado para o nível de conhecimento de alunos que estou procurando", "SearchRecommendationsStrings.tooBasicForLearnersLabel": "Muito básico para o nível de conhecimento de alunos que estou procurando", "SearchRecommendationsStrings.tryAgainLink": "Tentar novamente", + "SearchRecommendationsStrings.trySearchRecommendationsHeader": "Try our new 'Recommendations' feature!", + "SearchRecommendationsStrings.trySearchRecommendationsText": "Based on the title and description of the folder you are working on, we'll suggest relevant resources from the Kolibri Library. Choose 'Import from channels' in any folder of your channels to see the recommendations.", "SearchRecommendationsStrings.undoAction": "Desfazer", "SearchRecommendationsStrings.viewMoreLink": "Ver mais", + "SearchRecommendationsStrings.viewRecommendationsButton": "View recommendations", "SearchResultsList.failedToLoad": "Falha ao carregar os resultados da busca", "SearchResultsList.resultsPerPageLabel": "Resultados por página", "SearchResultsList.saveSearchAction": "Salvar busca", @@ -1588,10 +1727,121 @@ "Storage.spaceUsedOfMax": "{qty} de {max}", "Storage.storagePercentageUsed": "{qty}% do armazenamento usado", "Storage.videoFiles": "Os arquivos de vídeo devem ser comprimidos para maximizar o espaço de armazenamento e garantir distribuição off-line e uma reprodução fluidas. Uma vez compactado, o armazenamento total necessário pode ser inferior ao que você estimou originalmente.", + "StudioChannelCard.channelLanguageNotSetIndicator": "Nenhum idioma definido", + "StudioChannelCard.details": "Detalhes", + "StudioChannelCard.lastPublished": "Publicado em {last_published}", + "StudioChannelCard.lastUpdated": "Atualizado {updated}", + "StudioChannelCard.multipleCountries": "Multiple countries", + "StudioChannelCard.resourceCount": "{count, plural, one {}\n =1 {# conteúdo}\n other {# conteúdos}}", + "StudioChannelCard.selectChannel": "Selecionar {name}", + "StudioChannelCard.unpublishedText": "Não publicado", + "StudioChannelsPage.invitations": "Você tem {count, plural, one {}\n =1 {# convite}\n other {# convites}}", + "StudioChannelsPage.noChannelsFound": "Nenhum canal encontrado", + "StudioCollectionsTable.aboutChannelSets": "Sobre os conjuntos", + "StudioCollectionsTable.aboutChannelSetsLink": "Learn more about collections", + "StudioCollectionsTable.addChannelSetTitle": "Novo conjunto", + "StudioCollectionsTable.cancel": "Cancelar", + "StudioCollectionsTable.cancelButtonLabel": "Fechar", + "StudioCollectionsTable.channelNumber": "Número de canais", + "StudioCollectionsTable.channelSetsDescriptionText": "Um conjunto contém vários canais do Kolibri Studio que podem ser importados de uma vez ao Kolibri com um único token de conjunto.", + "StudioCollectionsTable.channelSetsDisclaimer": "Você precisará da versão 0.12.0 ou superior do Kolibri para importar conjuntos de canais", + "StudioCollectionsTable.channelSetsInstructionsText": "Você pode fazer um conjunto selecionando os canais que quer importar juntos.", + "StudioCollectionsTable.collectionDeleted": "Collection deleted", + "StudioCollectionsTable.copiedTokenId": "Token copiado", + "StudioCollectionsTable.copyFailed": "Falha ao copiar", + "StudioCollectionsTable.copyToken": "Copy token", + "StudioCollectionsTable.delete": "Excluir conjunto", + "StudioCollectionsTable.deleteChannelSetText": "Tem certeza que deseja excluir esta coleção?", + "StudioCollectionsTable.deleteChannelSetTitle": "Excluir conjunto", + "StudioCollectionsTable.deleteError": "Error deleting collection", + "StudioCollectionsTable.edit": "Editar conjunto", + "StudioCollectionsTable.noChannelSetsFound": "Você pode integrar vários canais para criar um conjunto. O conjunto inteiro pode então ser importado ao Kolibri de uma vez por meio de um token de conjunto.", + "StudioCollectionsTable.options": "Opções", + "StudioCollectionsTable.pageTitle": "Conjuntos", + "StudioCollectionsTable.saving": "Salvando", + "StudioCollectionsTable.tableCaption": "List of collections", + "StudioCollectionsTable.title": "Nome do conjunto", + "StudioCollectionsTable.token": "ID do token", + "StudioCopyToken.copiedTokenId": "Token copiado", + "StudioCopyToken.copyFailed": "Falha ao copiar", + "StudioCopyToken.token": "Token", + "StudioCopyToken.tooltipText": "Copiar token para importar o canal no Kolibri", + "StudioDetailsPanel.AVERAGE": "Médio", + "StudioDetailsPanel.LARGE": "Grande", + "StudioDetailsPanel.SMALL": "Pequeno", + "StudioDetailsPanel.VERY_LARGE": "Muito grande", + "StudioDetailsPanel.VERY_SMALL": "Muito pequeno", + "StudioDetailsPanel.aggregatorToolTip": "Website ou organização hospedando este conjunto de conteúdos, mas não necessariamente o criador ou detentor dos direitos autorais", + "StudioDetailsPanel.aggregatorsLabel": "Agregadores", + "StudioDetailsPanel.assessmentsIncludedText": "Avaliações", + "StudioDetailsPanel.authorToolTip": "Pessoa ou organização que criou este conteúdo", + "StudioDetailsPanel.authorsLabel": "Autores", + "StudioDetailsPanel.categoriesHeading": "Categorias", + "StudioDetailsPanel.coachDescription": "Os materiais para professores são visíveis apenas para eles no Kolibri", + "StudioDetailsPanel.coachHeading": "Materiais para professores", + "StudioDetailsPanel.containsContentHeading": "Contém conteúdo de", + "StudioDetailsPanel.containsHeading": "Contém", + "StudioDetailsPanel.copyrightHoldersLabel": "Detentores dos direitos autorais", + "StudioDetailsPanel.creationHeading": "Criado em", + "StudioDetailsPanel.currentVersionHeading": "Versão publicada", + "StudioDetailsPanel.languagesHeading": "Idiomas", + "StudioDetailsPanel.levelsHeading": "Níveis", + "StudioDetailsPanel.licensesLabel": "Licenças", + "StudioDetailsPanel.primaryLanguageHeading": "Idioma principal", + "StudioDetailsPanel.providerToolTip": "Organização que encomendou ou distribui o conteúdo", + "StudioDetailsPanel.providersLabel": "Provedores", + "StudioDetailsPanel.publishedHeading": "Publicado em", + "StudioDetailsPanel.resourceHeading": "Conteúdos totais", + "StudioDetailsPanel.sampleFromChannelHeading": "Amostra de conteúdo deste canal", + "StudioDetailsPanel.sizeHeading": "Tamanho do canal", + "StudioDetailsPanel.sizeText": "{text} ({size})", + "StudioDetailsPanel.subtitlesHeading": "Legendas", + "StudioDetailsPanel.tagsHeading": "Etiquetas comuns", + "StudioDetailsPanel.tokenHeading": "Token do canal", + "StudioDetailsPanel.unpublishedText": "Não publicado", + "StudioImmersiveModal.close": "Fechar", + "StudioMessageLayout.backToLogin": "Continuar para a página de login", + "StudioMyChannels.copyToken": "Copiar token do canal", + "StudioMyChannels.deleteChannel": "Excluir canal", + "StudioMyChannels.editChannel": "Editar detalhes do canal", + "StudioMyChannels.goToWebsite": "Ir para o website de origem", + "StudioMyChannels.moreOptions": "Mais opções", + "StudioMyChannels.newChannel": "Novo canal", + "StudioMyChannels.title": "Meus canais", + "StudioMyChannels.viewContent": "Visualizar canal no Kolibri", "StudioOfflineAlert.offlineText": "Você parece estar desconectado. Suas mudanças serão salvas quando a sua conexão voltar.", "StudioOfflineAlert.onlineText": "Você está on-line novamente.", + "StudioStarredChannels.copyToken": "Copiar token do canal", + "StudioStarredChannels.deleteChannel": "Excluir canal", + "StudioStarredChannels.editChannel": "Editar detalhes do canal", + "StudioStarredChannels.goToWebsite": "Ir para o website de origem", + "StudioStarredChannels.moreOptions": "Mais opções", + "StudioStarredChannels.removeChannel": "Remove channel", + "StudioStarredChannels.title": "Starred channels", + "StudioStarredChannels.viewContent": "Visualizar canal no Kolibri", "StudioTree.missingTitle": "Falta o título", "StudioTree.optionsTooltip": "Opções", + "StudioViewOnlyChannels.copyToken": "Copiar token do canal", + "StudioViewOnlyChannels.goToWebsite": "Ir para o website de origem", + "StudioViewOnlyChannels.moreOptions": "Outras opções", + "StudioViewOnlyChannels.removeChannel": "Remove channel", + "StudioViewOnlyChannels.title": "View-only channels", + "StudioViewOnlyChannels.viewContent": "Visualizar canal no Kolibri", + "SubscriptionCard.annualPrice": "${price, number}/year", + "SubscriptionCard.cancelNotice": "Your subscription will expire on {date, date, medium}. Storage will be removed after that.", + "SubscriptionCard.dismiss": "Descartar", + "SubscriptionCard.genericError": "There was a problem connecting to the payment provider. Please try again.", + "SubscriptionCard.instantUpgrade": "Upgrade storage now", + "SubscriptionCard.manageSubscription": "Manage subscription", + "SubscriptionCard.renewalNotice": "Your subscription will automatically renew on {date, date, medium}.", + "SubscriptionCard.storageAmount": "Storage (GB)", + "SubscriptionCard.storageIncluded": "{size} included in your subscription", + "SubscriptionCard.storageRange": "Enter a value between 1 and 50", + "SubscriptionCard.subscriptionActive": "Storage subscription active", + "SubscriptionCard.subscriptionCanceling": "Subscription cancelled", + "SubscriptionCard.upgradeDescription": "Purchase additional storage at $15/GB per year.", + "SubscriptionCard.upgradeNow": "Upgrade now", + "SubscriptionCard.upgradeSuccess": "Storage increased to {size}", "SubtitlesList.acceptedFormatsTooltip": "Formatos suportados: {extensions}", "SubtitlesList.addSubtitleText": "Adicionar legendas", "SubtitlesList.subtitlesHeader": "Legendas", @@ -1620,7 +1870,7 @@ "TechnicalTextBlock.copiedToClipboardConfirmation": "Copiado para a área de transferência", "TechnicalTextBlock.copiedToClipboardFailure": "Falha ao copiar para área de transferência", "TechnicalTextBlock.copyToClipboardButtonPrompt": "Copiar para área de transferência", - "TermsOfServiceModal.ToSHeader": "Terms of Service", + "TermsOfServiceModal.ToSHeader": "Termos de serviço", "TermsOfServiceModal.acceptableUseHeader": "Acceptable Use Restrictions", "TermsOfServiceModal.acceptableUseItem1": "Will be in strict accordance with these Terms;", "TermsOfServiceModal.acceptableUseItem10": "Will not interfere with, disrupt, or attack any service or network; and", @@ -1651,7 +1901,7 @@ "TermsOfServiceModal.changesToToSP1": "We are constantly updating our Service and that means sometimes we have to change the legal terms under which our Service is offered. These Terms may only be modified by a written amendment signed by an authorized executive of Learning Equality, or by the posting by Learning Equality of a revised version. If we make changes that are material, we will let you know by posting on one of our blogs, or by sending you an email or other communication before the changes take effect. The notice will designate a reasonable period of time after which the new terms will take effect. If you disagree with our changes, then you should stop using the Service within the designated notice period, or once the changes become effective. Your continued use of the Service will be subject to the new terms. However, any dispute that arose before the changes shall be governed by the Terms (including the binding individual arbitration clause) that were in place when the dispute arose.", "TermsOfServiceModal.communicationsHeader": "Communications with Learning Equality", "TermsOfServiceModal.communicationsP1": "For contractual purposes, you (1) consent to receive communications from us in an electronic form via the email address you have submitted or via the Service; and (2) agree that all Terms of Service, agreements, notices, disclosures, and other communications that we provide to you electronically satisfy any legal requirement that those communications would satisfy if they were on paper. This section does not affect your non-waivable rights.", - "TermsOfServiceModal.communityStandardsHeader": "Community Standards", + "TermsOfServiceModal.communityStandardsHeader": "Normas comunitárias", "TermsOfServiceModal.communityStandardsLink": "Learn more about Studio's community standards", "TermsOfServiceModal.communityStandardsP1": "For more information about the intended use of the Service, and standards around Content, please see our Community Standards page.", "TermsOfServiceModal.definitionsHeader": "Definitions", @@ -1678,7 +1928,7 @@ "TermsOfServiceModal.licensingList1Item2": "If you are not yourself the copyright holder, you must have the rights to distribute the uploaded Content, either through explicit written permission from the copyright holder, or as allowed by the terms of the license under which the Content has been released.", "TermsOfServiceModal.licensingList1Item3": "If you are the copyright holder of the uploaded content, then by marking the Content you upload with a particular license, you are agreeing for the Content to be distributed and used under the terms of that license in perpetuity.", "TermsOfServiceModal.licensingList2Item1": "Descriptive metadata: This includes primary metadata associated with a single piece of Content, for example, titles, descriptions, and other elements which constitute a definitive part of the Content regardless of which system it appears on. These metadata elements will fall under the same copyright and licensing as the Content itself.", - "TermsOfServiceModal.licensingList2Item2": "Organizational metadata: This defines how a piece of content may be used, aids with discovery, and places it within some broader structure of relations on the Service, for example, tags, curation into folders (including the titles of those folders), and other elements pertaining to the display and ordering of Content on the system itself. By using the Service, you agree that work you do to generate organizational metadata elements are released into the Public Domain, and may be made available for others to use, without any claim to copyright or restricted licensing. We may also share, leverage and distribute this organizational metadata. This is so that we can benefit others and improve the impact of our platforms.", + "TermsOfServiceModal.licensingList2Item2": "Metadados organizacionais definem como um conteúdo educacional pode ser usado, ajudam a descoberta do material, e criam mais estruturas e melheroes relações com o Serviço, por exemplo, etiquetas, curadoria em pastas (incluindo os títulos das pastas), e outros elementos relacionados `a apresentação e `a ordem do Conteúdo no próprio sistema. Ao usar o Serviço, todos os metadados organizacionais que você venha a ter criado passam a ser de Domínio Público, podendo ser disponibilizados para outras pessoas. Você abdica de qualquer reivindicação de direitos autorais ou licença restrita. Também fica a nosso critério a distribuição desses metadados para que possamos beneficiar a todos e melhorar o impacto de nossas plataformas.", "TermsOfServiceModal.licensingP1": "The Service allows you to upload and distribute Content. When you do, the following terms apply:", "TermsOfServiceModal.licensingP2": "We follow a policy of making content, including its associated metadata, as open as possible while following the appropriate copyright laws. With this in mind, we distinguish between:", "TermsOfServiceModal.miscellaneousHeader": "Miscellaneous", @@ -1690,7 +1940,7 @@ "TermsOfServiceModal.thirdPartyP1": "The links to third party websites, any third party content, and any third party applications may be provided for your convenience and information only. The content on any linked website or in any third party application is not under our control and we are not responsible for the content of linked websites and/or third party applications, including any further links contained in a third party website. We make no representations or warranties in connection with any third party content or third party applications, which at all times and in each instance is provided \"as is.\" Third party applications may be subject to additional policies and conditions or agreements between you and the provider of such third party applications. You agree to fully comply with all such additional policies, conditions and agreements. If you decide to access any third party content, and/or any third party application, you do so entirely at your own risk.", "TermsOfServiceModal.thirdPartyRightsHeader": "Third Party Rights", "TermsOfServiceModal.thirdPartyRightsP1": "Nothing in our Terms is intended to confer on any third party any benefit or any right (under the Contracts (Rights of Third Parties) Act 1999 UK or otherwise) to enforce any provision of our Terms or any agreement entered into in connection with it.", - "TermsOfServiceModal.updatedToSHeader": "Updated terms of service", + "TermsOfServiceModal.updatedToSHeader": "Termos de serviço atualizados", "TermsOfServiceModal.userContentHeader": "User-Generated Content", "TermsOfServiceModal.userContentList1Item1": "We do not endorse any uploaded Content or represent that Content is accurate, useful, or non-harmful. Content could be offensive, indecent, or objectionable; include technical inaccuracies, typographical mistakes, or other errors; or violate or infringe the privacy, publicity rights, intellectual property rights (see our Copyright Infringement and DMCA Policy section to submit copyright complaints), or other proprietary rights of third parties.", "TermsOfServiceModal.userContentList1Item2": "If you upload or author Content, or otherwise make (or allow any third party to make) Content available on the Service, you are entirely responsible for the Content, and any harm resulting from, that Content or your conduct.", @@ -1715,28 +1965,39 @@ "TextArea.fieldRequiredMessage": "Campo obrigatório", "TextField.fieldRequiredMessage": "Campo obrigatório", "Thumbnail.thumbnail": "{title} miniatura", + "ThumbnailGenerator.closeButtonLabel": "OK", "ThumbnailGenerator.generatedDefaultFilename": "Miniatura gerada", "ThumbnailGenerator.thumbnailGenerationFailedHeader": "Não foi possível gerar miniatura", "ThumbnailGenerator.thumbnailGenerationFailedText": "Ocorreu um erro ao gerar uma miniatura", + "TipTapEditorStrings.TipTapEditorLabel": "text editor - Press Enter to start editing", + "TipTapEditorStrings.TipTapViewerLabel": "text editor content", "TipTapEditorStrings.addLink": "Add link", + "TipTapEditorStrings.alignLeft": "Align left", + "TipTapEditorStrings.alignRight": "Align right", "TipTapEditorStrings.altTextDescription": "Alt text is necessary to enable visually impaired learners to answer questions, and it also displays when the image fails to load", "TipTapEditorStrings.altTextLabel": "Alt text (Optional)", "TipTapEditorStrings.altTextPlaceholder": "Describe your image...", "TipTapEditorStrings.bold": "Strong", "TipTapEditorStrings.bulletList": "Bullet list", - "TipTapEditorStrings.cancel": "Cancel", + "TipTapEditorStrings.cancel": "Cancelar", "TipTapEditorStrings.cancelLoading": "Cancel loading", + "TipTapEditorStrings.clearFormatting": "Clear formatting", "TipTapEditorStrings.clipboardAccessFailed": "Clipboard access failed. Try copying again.", - "TipTapEditorStrings.close": "Close", + "TipTapEditorStrings.close": "Fechar", "TipTapEditorStrings.closeModal": "Close modal", "TipTapEditorStrings.codeBlock": "Code block", - "TipTapEditorStrings.copy": "Copy", + "TipTapEditorStrings.collapseFormattingBar": "Collapse formatting bar", + "TipTapEditorStrings.copy": "Copiar", "TipTapEditorStrings.copyAndPasteActions": "Copy and paste actions", "TipTapEditorStrings.copyLink": "Copy link", + "TipTapEditorStrings.decreaseFormatSize": "Decrease format size", "TipTapEditorStrings.defaultImageName": "Image", - "TipTapEditorStrings.edit": "Edit", + "TipTapEditorStrings.edit": "Editar", "TipTapEditorStrings.editImage": "Edit image", "TipTapEditorStrings.editLink": "Edit link", + "TipTapEditorStrings.editorControls": "Editor controls", + "TipTapEditorStrings.errorUploadingImage": "Error uploading image", + "TipTapEditorStrings.expandFormattingBar": "Expand formatting bar", "TipTapEditorStrings.failedToProcessImage": "Failed to process the image file.", "TipTapEditorStrings.fileSizeUnit": "MB.", "TipTapEditorStrings.fileTooLarge": "File is too large. Maximum size is ", @@ -1745,13 +2006,18 @@ "TipTapEditorStrings.formatHeader3": "Header 3", "TipTapEditorStrings.formatNormal": "Normal", "TipTapEditorStrings.formatOptions": "Format options", - "TipTapEditorStrings.formatSmall": "Small", + "TipTapEditorStrings.formatSize": "Format size", + "TipTapEditorStrings.formatSmall": "Pequeno", "TipTapEditorStrings.formulasMenuTitle": "Special Characters", "TipTapEditorStrings.goToLink": "Go to link", "TipTapEditorStrings.historyActions": "History actions", "TipTapEditorStrings.imageDropZoneText": "Drag and drop an image here or upload manually", "TipTapEditorStrings.imagePreview": "Image preview", - "TipTapEditorStrings.insert": "Insert", + "TipTapEditorStrings.increaseFormatSize": "Increase format size", + "TipTapEditorStrings.insert": "Inserir", + "TipTapEditorStrings.insertContent": "Insert content", + "TipTapEditorStrings.insertContentMenu": "Insert content menu", + "TipTapEditorStrings.insertContentOption": "Insert content option", "TipTapEditorStrings.insertImage": "Insert image", "TipTapEditorStrings.insertLink": "Insert link", "TipTapEditorStrings.insertTools": "Insert tools", @@ -1760,8 +2026,11 @@ "TipTapEditorStrings.link": "Link", "TipTapEditorStrings.linkActions": "Link actions", "TipTapEditorStrings.listFormatting": "List formatting", + "TipTapEditorStrings.loadingFormulas": "Loading math editor", "TipTapEditorStrings.mathFormula": "Math formula", + "TipTapEditorStrings.moreButtonText": "Mais", "TipTapEditorStrings.multipleFilesDroppedWarning": "Multiple files were dropped. Only the first file has been selected.", + "TipTapEditorStrings.noEnoughStorageSpace": "Not enough storage space available. File size exceeds remaining storage.", "TipTapEditorStrings.noFileProvided": "No file provided.", "TipTapEditorStrings.numberedList": "Numbered list", "TipTapEditorStrings.opensInNewTab": "(opens in new tab)", @@ -1770,27 +2039,27 @@ "TipTapEditorStrings.pasteOptionsMenu": "Paste options menu", "TipTapEditorStrings.pasteWithoutFormatting": "Paste without formatting", "TipTapEditorStrings.redo": "Redo", - "TipTapEditorStrings.remove": "Remove", + "TipTapEditorStrings.remove": "Remover", "TipTapEditorStrings.removeImage": "Remove image", "TipTapEditorStrings.removeLink": "Remove link", - "TipTapEditorStrings.replaceFile": "Replace file", - "TipTapEditorStrings.save": "Save", - "TipTapEditorStrings.saveChanges": "Save changes", + "TipTapEditorStrings.replaceFile": "Substituir arquivo", + "TipTapEditorStrings.save": "Salvar", + "TipTapEditorStrings.saveChanges": "Salvar alterações", "TipTapEditorStrings.scriptFormatting": "Script formatting", - "TipTapEditorStrings.selectFile": "Select file", + "TipTapEditorStrings.selectFile": "Selecionar arquivo", "TipTapEditorStrings.selectFileToUpload": "Select file to upload", "TipTapEditorStrings.strikethrough": "Strikethrough", "TipTapEditorStrings.subscript": "Subscript", - "TipTapEditorStrings.superscript": "Superscript", - "TipTapEditorStrings.supportedFileTypes": "Supported file types: png, jpg, jpeg, svg, webp", + "TipTapEditorStrings.superscript": "Exponente", + "TipTapEditorStrings.supportedFileTypes": "Tipos de arquivo suportados: { extensions }", "TipTapEditorStrings.text": "Text", "TipTapEditorStrings.textFormatOptions": "Text format options", "TipTapEditorStrings.textFormattingOptions": "Text formatting options", "TipTapEditorStrings.textFormattingToolbar": "Text formatting toolbar", "TipTapEditorStrings.textStyleFormatting": "Text style formatting", "TipTapEditorStrings.underline": "Underline", - "TipTapEditorStrings.undo": "Undo", - "TipTapEditorStrings.uploadImage": "Upload image", + "TipTapEditorStrings.undo": "Desfazer", + "TipTapEditorStrings.uploadImage": "Enviar imagem", "TitleStrings.catalogTitle": "Catálogo da Biblioteca de Conteúdos Kolibri", "TitleStrings.defaultTitle": "Kolibri Studio", "TitleStrings.tabTitle": "{title} - {site}", @@ -1814,25 +2083,26 @@ "TreeView.showSidebar": "Mostrar a barra lateral", "TreeView.updatedResourcesReadyForReview": "Conteúdos atualizados estão prontos para revisão", "TreeViewBase.apiGenerated": "Gerado pela API", - "TreeViewBase.cancel": "Cancelar", "TreeViewBase.channelDeletedSnackbar": "Canal excluído", "TreeViewBase.channelDetails": "Ver detalhes do canal", "TreeViewBase.deleteChannel": "Excluir canal", - "TreeViewBase.deleteChannelButton": "Excluir canal", - "TreeViewBase.deletePrompt": "Este canal será excluído permanentemente. Esta ação não pode ser desfeita.", - "TreeViewBase.deleteTitle": "Excluir este canal", "TreeViewBase.editChannel": "Editar detalhes do canal", "TreeViewBase.emptyChannelTooltip": "Você não pode publicar um canal vazio", "TreeViewBase.getToken": "Obter token", "TreeViewBase.incompleteDescendantsText": "{count, number, integer} {count, plural, one {conteúdo está incompleto e não pode ser publicado} other {conteúdos estão incompletos e não podem ser publicados}}", + "TreeViewBase.inviteCollaborators": "Convidar colaboradores", "TreeViewBase.noChangesText": "Nenhuma alteração encontrada no canal", "TreeViewBase.noLanguageSetError": "Campo obrigatório", "TreeViewBase.openTrash": "Abrir lixeira", "TreeViewBase.publishButton": "Publicar", "TreeViewBase.publishButtonTitle": "Tornar este canal disponível para importação no Kolibri", "TreeViewBase.shareChannel": "Compartilhar canal", + "TreeViewBase.shareMenuButton": "Compartilhar", + "TreeViewBase.shareToken": "Share token", + "TreeViewBase.submitToCommunityLibrary": "Submit to Community Library", "TreeViewBase.syncChannel": "Sincronizar conteúdos", "TreeViewBase.viewOnly": "Para visualizar", + "Uploader.closeButtonLabel": "OK", "Uploader.listDelimiter": ", ", "Uploader.maxFileSizeText": "{count, plural, one {}\n =1 {# arquivo não será enviado.}\n other {# arquivos não serão enviados.}} O tamanho do(s) arquivo(s) deve ser menor que {size}", "Uploader.noStorageHeader": "Espaço insuficiente", @@ -1906,4 +2176,4 @@ "sharedVue.masteryModelRequired": "O critério de domínio é obrigatório", "sharedVue.shortActivityLteThirty": "O número deve ser igual ou menor que 30", "sharedVue.titleRequired": "O título é obrigatório" -} +} \ No newline at end of file diff --git a/contentcuration/locale/pt_BR/LC_MESSAGES/django.po b/contentcuration/locale/pt_BR/LC_MESSAGES/django.po index dba8cd2438..7b7633e874 100644 --- a/contentcuration/locale/pt_BR/LC_MESSAGES/django.po +++ b/contentcuration/locale/pt_BR/LC_MESSAGES/django.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: kolibri-studio\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-09-03 19:45+0000\n" -"PO-Revision-Date: 2025-09-12 13:59\n" +"POT-Creation-Date: 2026-04-16 00:57+0000\n" +"PO-Revision-Date: 2026-04-20 19:33\n" "Last-Translator: \n" "Language-Team: Portuguese, Brazilian\n" "Language: pt_BR\n" @@ -14,8 +14,8 @@ msgstr "" "X-Crowdin-Project: kolibri-studio\n" "X-Crowdin-Project-ID: 286000\n" "X-Crowdin-Language: pt-BR\n" -"X-Crowdin-File: /search-recommendations-closed-beta/django.po\n" -"X-Crowdin-File-ID: 4886\n" +"X-Crowdin-File: /unstable/django.po\n" +"X-Crowdin-File-ID: 4322\n" #: contentcuration/catalog_settings.py:4 contentcuration/sandbox_settings.py:8 #: contentcuration/settings.py:271 @@ -26,25 +26,25 @@ msgstr "Árabe" msgid "The site is currently in read-only mode. Please try again later." msgstr "No momento, o site está em modo somente leitura. Por favor, tente novamente mais tarde." -#: contentcuration/models.py:342 +#: contentcuration/models.py:356 msgid "Not enough space. Check your storage under Settings page." msgstr "Espaço insuficiente. Verifique seu armazenamento na página Configurações." -#: contentcuration/models.py:374 contentcuration/models.py:386 +#: contentcuration/models.py:440 contentcuration/models.py:452 msgid "Out of storage! Request more space under Settings > Storage." msgstr "Sem armazenamento! Solicite mais espaço em Configurações > Armazenamento." -#: contentcuration/models.py:2155 +#: contentcuration/models.py:2531 msgid " (Original)" msgstr " (Original)" -#: contentcuration/models.py:3297 +#: contentcuration/models.py:3848 msgid "Created DateTime" -msgstr "Data/Hora de criação" +msgstr "" -#: contentcuration/models.py:3299 +#: contentcuration/models.py:3850 msgid "Datetime field when the custom_metadata for task was created in UTC" -msgstr "Campo data-hora quando o metadado personalizado para tarefa foi criado em UTC" +msgstr "" #: contentcuration/settings.py:269 msgid "English" @@ -719,7 +719,7 @@ msgstr "Estamos com problemas com um serviço de terceiros. Isso significa que o msgid "We are encountering issues with our data center. This means you may encounter networking problems while using Studio. We appreciate your patience while these issues are being resolved. To check the status of this service, please visit here" msgstr "Estamos com problemas com o nosso centro de dados. Isso significa que você pode ter problemas de conexão enquanto usa o Studio. Agradecemos pela paciência enquanto esses problemas são resolvidos. Para saber o status do serviço, por favor visite aqui" -#: contentcuration/utils/publish.py:101 +#: contentcuration/utils/publish.py:103 msgid "Kolibri Studio Channel Published" msgstr "Canal do Kolibri Studio publicado" @@ -731,14 +731,15 @@ msgstr "Relatório de problemas do Kolibri Studio" msgid "Kolibri Studio account deleted" msgstr "Conta do Kolibri Studio excluída" -#: kolibri_public/views.py:223 +#: kolibri_public/views.py:323 msgid "Resource" msgstr "Conteúdo" -#: kolibri_public/views_v1.py:79 kolibri_public/views_v1.py:94 +#: kolibri_public/views_v1.py:152 kolibri_public/views_v1.py:167 msgid "API version is unavailable" msgstr "A versão da API está indisponível" -#: kolibri_public/views_v1.py:97 +#: kolibri_public/views_v1.py:170 msgid "No channel matching {} found" msgstr "Nenhum canal correspondente a {} encontrado" + From 92db4a22d2cc1dcded6bda4ab80203cdbe12762c Mon Sep 17 00:00:00 2001 From: Alex Velez Date: Wed, 22 Apr 2026 07:15:09 -0500 Subject: [PATCH 14/34] Add guards for channel version computed values --- .../components/sidePanels/ReviewSubmissionSidePanel.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contentcuration/contentcuration/frontend/administration/components/sidePanels/ReviewSubmissionSidePanel.vue b/contentcuration/contentcuration/frontend/administration/components/sidePanels/ReviewSubmissionSidePanel.vue index 34d5ff788f..e5436bda75 100644 --- a/contentcuration/contentcuration/frontend/administration/components/sidePanels/ReviewSubmissionSidePanel.vue +++ b/contentcuration/contentcuration/frontend/administration/components/sidePanels/ReviewSubmissionSidePanel.vue @@ -351,7 +351,7 @@ // state more accurately to the developer in case of debugging. // UI code should rely on XXXIsLoading and XXXIsFinished instead. if (!languageCodes) return undefined; - if (languageCodes.length === 0) return null; + if (!languageCodes?.length) return null; return languageCodes.map(code => LanguagesMap.get(code).readable_name).join(', '); }); @@ -362,7 +362,7 @@ const categoriesString = computed(() => { if (!channelVersionIsFinished.value) return undefined; - if (channelVersionData.value.included_categories.length === 0) return null; + if (!channelVersionData.value.included_categories?.length) return null; return channelVersionData.value.included_categories .map(categoryId => categoryIdToName(categoryId)) @@ -371,7 +371,7 @@ const licensesString = computed(() => { if (!channelVersionIsFinished.value) return undefined; - if (channelVersionData.value.included_licenses.length === 0) return null; + if (!channelVersionData.value.included_licenses?.length) return null; return channelVersionData.value.included_licenses .map(licenseId => LicensesMap.get(licenseId).license_name) From d1f4cf87327cba35c16ce5f11d86141c150605dc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 12:23:04 +0000 Subject: [PATCH 15/34] [pre-commit.ci lite] apply automatic fixes --- .../locale/ar/LC_MESSAGES/contentcuration-messages.json | 2 +- contentcuration/locale/ar/LC_MESSAGES/django.po | 1 - .../locale/en/LC_MESSAGES/contentcuration-messages.json | 2 +- .../locale/es_ES/LC_MESSAGES/contentcuration-messages.json | 2 +- contentcuration/locale/es_ES/LC_MESSAGES/django.po | 1 - .../locale/fr_FR/LC_MESSAGES/contentcuration-messages.json | 2 +- contentcuration/locale/fr_FR/LC_MESSAGES/django.po | 1 - .../locale/hi_IN/LC_MESSAGES/contentcuration-messages.json | 2 +- contentcuration/locale/hi_IN/LC_MESSAGES/django.po | 1 - .../locale/pt_BR/LC_MESSAGES/contentcuration-messages.json | 2 +- contentcuration/locale/pt_BR/LC_MESSAGES/django.po | 1 - 11 files changed, 6 insertions(+), 11 deletions(-) diff --git a/contentcuration/locale/ar/LC_MESSAGES/contentcuration-messages.json b/contentcuration/locale/ar/LC_MESSAGES/contentcuration-messages.json index 6fcd11b703..290df548a5 100644 --- a/contentcuration/locale/ar/LC_MESSAGES/contentcuration-messages.json +++ b/contentcuration/locale/ar/LC_MESSAGES/contentcuration-messages.json @@ -2176,4 +2176,4 @@ "sharedVue.masteryModelRequired": "الإتقان مطلوب", "sharedVue.shortActivityLteThirty": "يجب أن تكون القيمة مساوية أو أقل من 30", "sharedVue.titleRequired": "العنوان إلزامي" -} \ No newline at end of file +} diff --git a/contentcuration/locale/ar/LC_MESSAGES/django.po b/contentcuration/locale/ar/LC_MESSAGES/django.po index 34ebd363a0..86c3ef6736 100644 --- a/contentcuration/locale/ar/LC_MESSAGES/django.po +++ b/contentcuration/locale/ar/LC_MESSAGES/django.po @@ -739,4 +739,3 @@ msgstr "إصدار الـ API غير متوفر" #: kolibri_public/views_v1.py:170 msgid "No channel matching {} found" msgstr "لم يتم العثور على قناة مطابقة {}" - diff --git a/contentcuration/locale/en/LC_MESSAGES/contentcuration-messages.json b/contentcuration/locale/en/LC_MESSAGES/contentcuration-messages.json index 1983272e37..90e0a99c07 100644 --- a/contentcuration/locale/en/LC_MESSAGES/contentcuration-messages.json +++ b/contentcuration/locale/en/LC_MESSAGES/contentcuration-messages.json @@ -2176,4 +2176,4 @@ "sharedVue.masteryModelRequired": "Mastery is required", "sharedVue.shortActivityLteThirty": "Value must be equal or less than 30", "sharedVue.titleRequired": "Title is required" -} \ No newline at end of file +} diff --git a/contentcuration/locale/es_ES/LC_MESSAGES/contentcuration-messages.json b/contentcuration/locale/es_ES/LC_MESSAGES/contentcuration-messages.json index a48f7e8d2e..23dcfe6d16 100644 --- a/contentcuration/locale/es_ES/LC_MESSAGES/contentcuration-messages.json +++ b/contentcuration/locale/es_ES/LC_MESSAGES/contentcuration-messages.json @@ -2176,4 +2176,4 @@ "sharedVue.masteryModelRequired": "Tiene que seleccionar el criterio de dominio", "sharedVue.shortActivityLteThirty": "El valor debe ser igual o menor que 30", "sharedVue.titleRequired": "Este campo es obligatorio" -} \ No newline at end of file +} diff --git a/contentcuration/locale/es_ES/LC_MESSAGES/django.po b/contentcuration/locale/es_ES/LC_MESSAGES/django.po index 76fe11bfea..a785ae3603 100644 --- a/contentcuration/locale/es_ES/LC_MESSAGES/django.po +++ b/contentcuration/locale/es_ES/LC_MESSAGES/django.po @@ -742,4 +742,3 @@ msgstr "La versión API no está disponible" #: kolibri_public/views_v1.py:170 msgid "No channel matching {} found" msgstr "Ningún canal con {} encontrado" - diff --git a/contentcuration/locale/fr_FR/LC_MESSAGES/contentcuration-messages.json b/contentcuration/locale/fr_FR/LC_MESSAGES/contentcuration-messages.json index f34068733c..39ef4d5a36 100644 --- a/contentcuration/locale/fr_FR/LC_MESSAGES/contentcuration-messages.json +++ b/contentcuration/locale/fr_FR/LC_MESSAGES/contentcuration-messages.json @@ -2176,4 +2176,4 @@ "sharedVue.masteryModelRequired": "Le critère de maîtrise est obligatoire", "sharedVue.shortActivityLteThirty": "La valeur doit être égale ou inférieure à 30", "sharedVue.titleRequired": "Le titre est obligatoire" -} \ No newline at end of file +} diff --git a/contentcuration/locale/fr_FR/LC_MESSAGES/django.po b/contentcuration/locale/fr_FR/LC_MESSAGES/django.po index 084836360b..32eca8eed2 100644 --- a/contentcuration/locale/fr_FR/LC_MESSAGES/django.po +++ b/contentcuration/locale/fr_FR/LC_MESSAGES/django.po @@ -740,4 +740,3 @@ msgstr "La version de l’API est indisponible" #: kolibri_public/views_v1.py:170 msgid "No channel matching {} found" msgstr "Aucune chaîne correspondant à {} trouvée" - diff --git a/contentcuration/locale/hi_IN/LC_MESSAGES/contentcuration-messages.json b/contentcuration/locale/hi_IN/LC_MESSAGES/contentcuration-messages.json index ea81e3a301..f009ba5071 100644 --- a/contentcuration/locale/hi_IN/LC_MESSAGES/contentcuration-messages.json +++ b/contentcuration/locale/hi_IN/LC_MESSAGES/contentcuration-messages.json @@ -2176,4 +2176,4 @@ "sharedVue.masteryModelRequired": "Mastery is required", "sharedVue.shortActivityLteThirty": "Value must be equal or less than 30", "sharedVue.titleRequired": "Title is required" -} \ No newline at end of file +} diff --git a/contentcuration/locale/hi_IN/LC_MESSAGES/django.po b/contentcuration/locale/hi_IN/LC_MESSAGES/django.po index c8729a9817..8ff10382a1 100644 --- a/contentcuration/locale/hi_IN/LC_MESSAGES/django.po +++ b/contentcuration/locale/hi_IN/LC_MESSAGES/django.po @@ -723,4 +723,3 @@ msgstr "" #: kolibri_public/views_v1.py:170 msgid "No channel matching {} found" msgstr "" - diff --git a/contentcuration/locale/pt_BR/LC_MESSAGES/contentcuration-messages.json b/contentcuration/locale/pt_BR/LC_MESSAGES/contentcuration-messages.json index 6fd2349d0b..311a2a9c95 100644 --- a/contentcuration/locale/pt_BR/LC_MESSAGES/contentcuration-messages.json +++ b/contentcuration/locale/pt_BR/LC_MESSAGES/contentcuration-messages.json @@ -2176,4 +2176,4 @@ "sharedVue.masteryModelRequired": "O critério de domínio é obrigatório", "sharedVue.shortActivityLteThirty": "O número deve ser igual ou menor que 30", "sharedVue.titleRequired": "O título é obrigatório" -} \ No newline at end of file +} diff --git a/contentcuration/locale/pt_BR/LC_MESSAGES/django.po b/contentcuration/locale/pt_BR/LC_MESSAGES/django.po index 7b7633e874..f5d43696b3 100644 --- a/contentcuration/locale/pt_BR/LC_MESSAGES/django.po +++ b/contentcuration/locale/pt_BR/LC_MESSAGES/django.po @@ -742,4 +742,3 @@ msgstr "A versão da API está indisponível" #: kolibri_public/views_v1.py:170 msgid "No channel matching {} found" msgstr "Nenhum canal correspondente a {} encontrado" - From 404105761b34f572ef26cccf573e4a6d66b2b861 Mon Sep 17 00:00:00 2001 From: Alex Velez Date: Fri, 17 Apr 2026 09:01:58 -0500 Subject: [PATCH 16/34] Robust solution for filtering unpublishable changes on frontend --- .../shared/data/__tests__/serverSync.spec.js | 37 ++++++++++++++++++- .../frontend/shared/data/serverSync.js | 7 +++- contentcuration/contentcuration/models.py | 1 + .../test_community_library_submission.py | 10 ++--- .../viewsets/community_library_submission.py | 1 + .../contentcuration/viewsets/sync/endpoint.py | 1 + 6 files changed, 49 insertions(+), 8 deletions(-) diff --git a/contentcuration/contentcuration/frontend/shared/data/__tests__/serverSync.spec.js b/contentcuration/contentcuration/frontend/shared/data/__tests__/serverSync.spec.js index 032b0f507e..636f593411 100644 --- a/contentcuration/contentcuration/frontend/shared/data/__tests__/serverSync.spec.js +++ b/contentcuration/contentcuration/frontend/shared/data/__tests__/serverSync.spec.js @@ -1,9 +1,9 @@ import { queueChange, debouncedSyncChanges } from '../serverSync'; import { CreatedChange } from '../changes'; import db from '../db'; -import { Session, Task } from 'shared/data/resources'; +import { Channel, Session, Task } from 'shared/data/resources'; import client from 'shared/client'; -import { CHANGES_TABLE, CURRENT_USER, TABLE_NAMES } from 'shared/data/constants'; +import { CHANGE_TYPES, CHANGES_TABLE, CURRENT_USER, TABLE_NAMES } from 'shared/data/constants'; import { mockChannelScope, resetMockChannelScope } from 'shared/utils/testing'; async function makeChange(key, server_rev) { @@ -225,4 +225,37 @@ describe('ServerSync tests', () => { expect(dbTask.status).toEqual(task.status); } }); + + it('should not set unpublished_changes when response contains only unpublishable changes', async () => { + const channelId = 'test-channel-unpublishable'; + await Channel.table.put({ id: channelId }); + + client.post.mockResolvedValue({ + data: { + disallowed: [], + allowed: [], + returned: [], + errors: [], + successes: [ + { + channel_id: channelId, + server_rev: 100, + created_by_id: 'some-user-id', + type: CHANGE_TYPES.UPDATED, + unpublishable: true, + }, + ], + maxRevs: [], + tasks: [], + }, + }); + + await debouncedSyncChanges(); + + const channel = await Channel.table.get(channelId); + expect(channel.unpublished_changes).not.toBe(true); + + // Manual clean up + await Channel.table.delete(channelId); + }); }); diff --git a/contentcuration/contentcuration/frontend/shared/data/serverSync.js b/contentcuration/contentcuration/frontend/shared/data/serverSync.js index d8984946ef..db778d2f30 100644 --- a/contentcuration/contentcuration/frontend/shared/data/serverSync.js +++ b/contentcuration/contentcuration/frontend/shared/data/serverSync.js @@ -175,7 +175,12 @@ function handleMaxRevs(response, userId) { maxRevs[`${MAX_REV_KEY}.${channelId}`] = channelChanges[0].server_rev; const lastChannelEditIndex = findLastIndex( channelChanges, - c => !c.errors && !c.user_id && c.created_by_id && c.type !== CHANGE_TYPES.PUBLISHED, + c => + !c.errors && + !c.user_id && + c.created_by_id && + c.type !== CHANGE_TYPES.PUBLISHED && + !c.unpublishable, ); const lastPublishIndex = findLastIndex( channelChanges, diff --git a/contentcuration/contentcuration/models.py b/contentcuration/contentcuration/models.py index c852fa28ac..ae2ab2b615 100644 --- a/contentcuration/contentcuration/models.py +++ b/contentcuration/contentcuration/models.py @@ -3820,6 +3820,7 @@ def serialize(cls, change): "channel_id": get_attribute(change, ["channel_id"]), "user_id": get_attribute(change, ["user_id"]), "created_by_id": get_attribute(change, ["created_by_id"]), + "unpublishable": get_attribute(change, ["unpublishable"]), } ) return datum diff --git a/contentcuration/contentcuration/tests/viewsets/test_community_library_submission.py b/contentcuration/contentcuration/tests/viewsets/test_community_library_submission.py index 5dfe4ae0be..4cd51fb2a1 100644 --- a/contentcuration/contentcuration/tests/viewsets/test_community_library_submission.py +++ b/contentcuration/contentcuration/tests/viewsets/test_community_library_submission.py @@ -720,12 +720,12 @@ def test_resolve_submission__accept_correct(self, apply_task_mock): self.assertEqual(resolved_submission.resolved_by, self.admin_user) self.assertEqual(resolved_submission.date_updated, self.resolved_time) - self.assertTrue( - Change.objects.filter( - channel=self.submission.channel, - change_type=ADDED_TO_COMMUNITY_LIBRARY, - ).exists() + change = Change.objects.get( + channel=self.submission.channel, + change_type=ADDED_TO_COMMUNITY_LIBRARY, ) + self.assertEqual(change.created_by_id, self.admin_user.id) + self.assertTrue(change.unpublishable) apply_task_mock.fetch_or_enqueue.assert_called_once_with( self.admin_user, channel_id=self.submission.channel.id, diff --git a/contentcuration/contentcuration/viewsets/community_library_submission.py b/contentcuration/contentcuration/viewsets/community_library_submission.py index 6a13c89d46..33fc6f9a94 100644 --- a/contentcuration/contentcuration/viewsets/community_library_submission.py +++ b/contentcuration/contentcuration/viewsets/community_library_submission.py @@ -320,6 +320,7 @@ def _add_to_community_library(self, submission): categories=submission.categories, country_codes=country_codes, ), + created_by_id=submission.resolved_by_id, # This change is not publishable and should not trigger publish-related logic unpublishable=True, ) diff --git a/contentcuration/contentcuration/viewsets/sync/endpoint.py b/contentcuration/contentcuration/viewsets/sync/endpoint.py index fcc5bed4fb..33ff296623 100644 --- a/contentcuration/contentcuration/viewsets/sync/endpoint.py +++ b/contentcuration/contentcuration/viewsets/sync/endpoint.py @@ -155,6 +155,7 @@ def return_changes(self, request, channel_revs): "table", "change_type", "kwargs", + "unpublishable", ) .order_by("server_rev")[:CHANGE_RETURN_LIMIT] ) From 6ccfbb082d6377ac02738ecb9dac08bf5e8b43ae Mon Sep 17 00:00:00 2001 From: Richard Tibbles Date: Tue, 21 Apr 2026 15:40:31 -0700 Subject: [PATCH 17/34] Only show draft publish button for draft feature flag. Fix bug in staging draft publishing. --- .../channelEdit/pages/StagingTreePage/index.spec.js | 1 + .../frontend/channelEdit/pages/StagingTreePage/index.vue | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/contentcuration/contentcuration/frontend/channelEdit/pages/StagingTreePage/index.spec.js b/contentcuration/contentcuration/frontend/channelEdit/pages/StagingTreePage/index.spec.js index f68461f7a1..d31ed6b234 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/pages/StagingTreePage/index.spec.js +++ b/contentcuration/contentcuration/frontend/channelEdit/pages/StagingTreePage/index.spec.js @@ -20,6 +20,7 @@ const GETTERS = { global: { isCompactViewMode: jest.fn(), appendChannelName: () => () => jest.fn(), + hasFeatureEnabled: () => () => false, }, currentChannel: { rootId: () => ROOT_ID, diff --git a/contentcuration/contentcuration/frontend/channelEdit/pages/StagingTreePage/index.vue b/contentcuration/contentcuration/frontend/channelEdit/pages/StagingTreePage/index.vue index 42f0d784b9..5ee8cfd0df 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/pages/StagingTreePage/index.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/pages/StagingTreePage/index.vue @@ -227,7 +227,7 @@ Channel.waitForPublishingDraft(publishDraftchange)) .then(() => { this.isPublishingDraft = false; this.showSnackbar({ From 24315510d9085deab0af6648320ee4e1c00fa12a Mon Sep 17 00:00:00 2001 From: Alex Velez Date: Wed, 22 Apr 2026 06:04:35 -0500 Subject: [PATCH 18/34] Fix lookup not finding draft tokens --- contentcuration/kolibri_public/views_v1.py | 1 - 1 file changed, 1 deletion(-) diff --git a/contentcuration/kolibri_public/views_v1.py b/contentcuration/kolibri_public/views_v1.py index 2c0c4ef071..973915a651 100644 --- a/contentcuration/kolibri_public/views_v1.py +++ b/contentcuration/kolibri_public/views_v1.py @@ -108,7 +108,6 @@ def _get_channel_list_v1(params, identifier=None): ).filter( secret_token__token=identifier, channel__deleted=False, - channel__main_tree__published=True, ) if channel_version.exists(): # return early as we won't need to apply the other filters for channel version tokens From 6273be51d804fe27b0625cfa32446a99d54606e5 Mon Sep 17 00:00:00 2001 From: marcellamaki <17235236+marcellamaki@users.noreply.github.com> Date: Fri, 24 Apr 2026 16:20:20 +0000 Subject: [PATCH 19/34] Update translations from Crowdin This includes: - Updated translation files (.po and .json) - Compiled Django messages (.mo files) - Updated frontend i18n files --- .../LC_MESSAGES/contentcuration-messages.json | 524 +++++++++--------- .../locale/ar/LC_MESSAGES/django.po | 3 +- .../LC_MESSAGES/contentcuration-messages.json | 2 +- .../LC_MESSAGES/contentcuration-messages.json | 2 +- .../locale/es_ES/LC_MESSAGES/django.po | 1 + .../LC_MESSAGES/contentcuration-messages.json | 506 ++++++++--------- .../locale/fr_FR/LC_MESSAGES/django.po | 3 +- .../LC_MESSAGES/contentcuration-messages.json | 2 +- .../locale/hi_IN/LC_MESSAGES/django.po | 1 + .../LC_MESSAGES/contentcuration-messages.json | 508 ++++++++--------- .../locale/pt_BR/LC_MESSAGES/django.po | 3 +- 11 files changed, 780 insertions(+), 775 deletions(-) diff --git a/contentcuration/locale/ar/LC_MESSAGES/contentcuration-messages.json b/contentcuration/locale/ar/LC_MESSAGES/contentcuration-messages.json index 290df548a5..fb4f8f3528 100644 --- a/contentcuration/locale/ar/LC_MESSAGES/contentcuration-messages.json +++ b/contentcuration/locale/ar/LC_MESSAGES/contentcuration-messages.json @@ -273,7 +273,7 @@ "ChannelItem.goToWebsite": "التوجّه إلى موقع المصدر الإلكتروني", "ChannelItem.lastPublished": "منشور {last_published}", "ChannelItem.lastUpdated": "{updated} تم تحديثها", - "ChannelItem.removeChannel": "Remove channel", + "ChannelItem.removeChannel": "إزالة القناة", "ChannelItem.resourceCount": "{count, plural, zero {# مصادر} one {# مصادر} two {# مصدران} few {# مصادر} many {# مصدراً}\n =1 {# مصدر}\n other {# مصادر}}", "ChannelItem.unpublishedText": "غير منشور", "ChannelItem.versionText": "نسخة الإصدار {version}", @@ -508,162 +508,162 @@ "CommonMetadataStrings.work": "عمل", "CommonMetadataStrings.writing": "الكتابة", "CommonStrings.backAction": "رجوع", - "CommonStrings.channelDetailsLabel": "Channel Details", + "CommonStrings.channelDetailsLabel": "تفاصيل القناة", "CommonStrings.clearAction": "إزالة", "CommonStrings.closeAction": "إغلاق", "CommonStrings.copyChannelTokenAction": "نسخ معرّف القناة", "CommonStrings.dismissAction": "رفض", "CommonStrings.genericErrorMessage": "عذرًا لقد حدث خطأ ما، يُرجى المحاولة مرة أخرى.", "CommonStrings.previewAction": "معاينة", - "CommonStrings.seeAllAction": "See all", - "CommonStrings.seeLessAction": "See less", - "CommunityChannelsStrings.aboutCommunityLibraryDescription": "Community library contains channels submitted by the community and approved for discovery in Studio.", - "CommunityChannelsStrings.aboutCommunityLibraryTitle": "About Community Library", - "CommunityChannelsStrings.activityHistoryLabel": "Activity history", + "CommonStrings.seeAllAction": "عرض الكل", + "CommonStrings.seeLessAction": "عرض أقل", + "CommunityChannelsStrings.aboutCommunityLibraryDescription": "تحتوي مكتبة المجتمع على قنوات قدّمها المجتمع وقُبلت لتُصبح متاحة للاكتشاف في الاستوديو.", + "CommunityChannelsStrings.aboutCommunityLibraryTitle": "حول مكتبة المجتمع", + "CommunityChannelsStrings.activityHistoryLabel": "تاريخ الأنشطة", "CommunityChannelsStrings.adminLabel": "المشرف", - "CommunityChannelsStrings.allLicensesCompatible": "All licenses are compatible with Community Library.", - "CommunityChannelsStrings.allNotificationsLabel": "All Notifications", - "CommunityChannelsStrings.alreadySubmittedWarningDescription": "Please wait for the channel to be reviewed. You will see a notification in your Studio account after the review is complete.", - "CommunityChannelsStrings.alreadySubmittedWarningTitle": "This version of the channel has already been submitted to the Community Library.", - "CommunityChannelsStrings.approvedNotification": "{author} ({userType}) approved {channelVersion}", - "CommunityChannelsStrings.approvedPrimaryInfo": "A previous version is live in the Community Library. Reviewers will see the latest submission first.", - "CommunityChannelsStrings.approvedStatus": "Approved", - "CommunityChannelsStrings.availableStatus": "Available in Community Library", + "CommunityChannelsStrings.allLicensesCompatible": "جميع التراخيص متوافقة مع مكتبة المجتمع.", + "CommunityChannelsStrings.allNotificationsLabel": "جميع الإشعارات", + "CommunityChannelsStrings.alreadySubmittedWarningDescription": "يرجى الانتظار حتى تخضع القناة للمُراجعة. سترى إشعارًا في حسابك على الاستوديو بعد اكتمالها.", + "CommunityChannelsStrings.alreadySubmittedWarningTitle": "تم بالفعل تقديم هذا الإصدار من القناة إلى مكتبة المجتمع.", + "CommunityChannelsStrings.approvedNotification": "{author} ({userType}) اعتمَدَ {channelVersion}", + "CommunityChannelsStrings.approvedPrimaryInfo": "هناك إصدار سابق مُتاح حاليًا في مكتبة المجتمع. سيرى المراجعون أحدث نسخة مُقدَّمة أولًا.", + "CommunityChannelsStrings.approvedStatus": "مُعتمَد", + "CommunityChannelsStrings.availableStatus": "متاح في مكتبة المجتمع", "CommunityChannelsStrings.cancelAction": "إلغاء", "CommunityChannelsStrings.categoriesLabel": "الفئات", - "CommunityChannelsStrings.channelCannotBeDistributed": "This channel cannot be distributed via Kolibri.", - "CommunityChannelsStrings.channelFitAttributionLabel": "Attribution", - "CommunityChannelsStrings.channelFitChannelInfoLabel": "Channel information", - "CommunityChannelsStrings.channelFitChecklistAttribution": "Does each resource have an author listed?", - "CommunityChannelsStrings.channelFitChecklistChannelInfo": "Is your channel's basic information filled in — title, description, thumbnail, language, category, and level?", - "CommunityChannelsStrings.channelFitChecklistIntro": "Criteria for submitting to the Community Library", - "CommunityChannelsStrings.channelFitChecklistLicense": "Are the resources in your channel openly licensed or in the public domain?", - "CommunityChannelsStrings.channelFitChecklistOfflineUse": "Do the resources in your channel work without an Internet connection?", - "CommunityChannelsStrings.channelFitChecklistQuality": "Has anyone on your team or in your organization reviewed the channel?", + "CommunityChannelsStrings.channelCannotBeDistributed": "لا يمكن توزيع هذه القناة عبر كوليبري.", + "CommunityChannelsStrings.channelFitAttributionLabel": "ذكر المصدر", + "CommunityChannelsStrings.channelFitChannelInfoLabel": "معلومات القناة", + "CommunityChannelsStrings.channelFitChecklistAttribution": "هل لكل مصدر لديه مؤلف مُدرج؟", + "CommunityChannelsStrings.channelFitChecklistChannelInfo": "هل أدخلت المعلومات الأساسية لقناتك مثل العنوان والوصف والصورة المصغّرة واللغة والفئة والمستوى؟", + "CommunityChannelsStrings.channelFitChecklistIntro": "معايير التقديم إلى مكتبة المجتمع", + "CommunityChannelsStrings.channelFitChecklistLicense": "هل المصادر الموجودة في قناتك تتبع لرخصة مفتوحة أو تعتبر من الملكية العامة؟", + "CommunityChannelsStrings.channelFitChecklistOfflineUse": "هل تعمل المصادر في قناتك دون اتصال بالإنترنت؟", + "CommunityChannelsStrings.channelFitChecklistQuality": "هل قام أي شخص في فريقك أو في مؤسستك بمراجعة القناة؟", "CommunityChannelsStrings.channelFitLicenseLabel": "الترخيص", - "CommunityChannelsStrings.channelFitOfflineUseLabel": "Offline use", - "CommunityChannelsStrings.channelFitQualityLabel": "Quality", - "CommunityChannelsStrings.channelTokenDescription": "You can use this token to import and preview the draft channel in Kolibri. Please note that the token for the final published channel will be different.", + "CommunityChannelsStrings.channelFitOfflineUseLabel": "استخدام دون اتصال بالإنترنت", + "CommunityChannelsStrings.channelFitQualityLabel": "الجودة", + "CommunityChannelsStrings.channelTokenDescription": "يمكنك استخدام هذا الرمز لاستيراد القناة التجريبية ومعاينتها في كوليبري. يرجى ملاحظة أن الرمز الخاص بالقناة المنشورة نهائيًا سيكون مختلفًا.", "CommunityChannelsStrings.channelVersion": "{name} v{version}", - "CommunityChannelsStrings.channelVersionTokenLabel": "Channel version token", + "CommunityChannelsStrings.channelVersionTokenLabel": "رمز إصدار القناة", "CommunityChannelsStrings.clearAll": "مسح الكل", "CommunityChannelsStrings.clearAllAction": "مسح الكل", - "CommunityChannelsStrings.communityLibraryCTADescription": "Have a channel worth sharing with other educators and learners? Submit it for review through the Share menu so it can be discovered in Studio.", - "CommunityChannelsStrings.communityLibraryCTATitle": "Help grow the Community Library", - "CommunityChannelsStrings.communityLibraryDescription": "Browse community-submitted channels approved for discovery in Studio. Copy a token to use a channel in Kolibri.", - "CommunityChannelsStrings.communityLibraryLabel": "Community Library", - "CommunityChannelsStrings.communityLibrarySubmissionLabel": "Community Library submission", - "CommunityChannelsStrings.compatibleLicensesDescription": "{licenseNames} - All licenses are compatible with Community Library.", - "CommunityChannelsStrings.confirmDistributionRights": "Please confirm you have the right to distribute these resources via Kolibri.", - "CommunityChannelsStrings.confirmReplacementText": "I understand this will replace my earlier submission on the review queue", - "CommunityChannelsStrings.countriesInfoText": "Select one or more countries to label your channel submission with. For example, if your channel contains materials aligned to a national curriculum or regionally-specific content, selecting the relevant countries will help users find it. Otherwise, leave this blank.", + "CommunityChannelsStrings.communityLibraryCTADescription": "هل لديك قناة تستحق المشاركة مع المعلّمين والمتعلمين الآخرين؟ قدّمها للمراجعة في قائمة \"مشاركة\" لتصبح متاحة في الاستوديو.", + "CommunityChannelsStrings.communityLibraryCTATitle": "ساعد في تنمية مكتبة المجتمع", + "CommunityChannelsStrings.communityLibraryDescription": "تصفّح القنوات التي قدّمها المجتمع وتمت الموافقة عليها لتكون متاحة في الاستوديو. انسخ رمزًا لاستخدام القناة في كوليبري.", + "CommunityChannelsStrings.communityLibraryLabel": "مكتبة المجتمع", + "CommunityChannelsStrings.communityLibrarySubmissionLabel": "تقديم إلى مكتبة المجتمع", + "CommunityChannelsStrings.compatibleLicensesDescription": "{licenseNames} - جميع التراخيص متوافقة مع مكتبة المجتمع.", + "CommunityChannelsStrings.confirmDistributionRights": "يرجى تأكيد أن لديك الحق في توزيع هذه الموارد عبر كوليبري.", + "CommunityChannelsStrings.confirmReplacementText": "أفهم أن هذا سيستبدل ما قدمته سابقًا في قائمة المراجعة", + "CommunityChannelsStrings.countriesInfoText": "اختر دولة واحدة أو أكثر لوضع وسم على ما تقدمه في القناة. على سبيل المثال، إذا كانت قناتك تحتوي على مواد متوافقة مع منهج وطني أو محتوى خاص بمنطقة معيّنة، سيساعد اختيار الدول المناسبة المستخدمين في العثور عليها. اترك هذا الحقل فارغًا خلاف ذلك.", "CommunityChannelsStrings.countryLabel": "البلدان", - "CommunityChannelsStrings.descriptionLabel": "Describe what's included in this submission", + "CommunityChannelsStrings.descriptionLabel": "صِف ما يتضمّنه هذا التقديم", "CommunityChannelsStrings.dismissAction": "رفض", - "CommunityChannelsStrings.draftBeingPublishedNotice": "Draft version is being published", - "CommunityChannelsStrings.draftPublishedNotice": "Draft published successfully", - "CommunityChannelsStrings.draftTokenLabel": "Draft token", - "CommunityChannelsStrings.editorLabel": "Editor", - "CommunityChannelsStrings.emptyNotificationsNotice": "You have no notifications at this time.", - "CommunityChannelsStrings.emptyNotificationsWithFiltersNotice": "No notifications match the applied filters.", - "CommunityChannelsStrings.errorLoadingVersions": "Unable to load version history", - "CommunityChannelsStrings.errorSnackbar": "There was an error submitting the channel", - "CommunityChannelsStrings.feedbackNotesLabel": "Notes from the reviewer", - "CommunityChannelsStrings.filterByDateLabel": "Filter by date", - "CommunityChannelsStrings.filterByStatusLabel": "Filter by status", + "CommunityChannelsStrings.draftBeingPublishedNotice": "جارٍ نشر النسخة التجريبية", + "CommunityChannelsStrings.draftPublishedNotice": "نُشرت النسخة التجريبية بنجاح", + "CommunityChannelsStrings.draftTokenLabel": "رمز النسخة التجريبية", + "CommunityChannelsStrings.editorLabel": "المحرر", + "CommunityChannelsStrings.emptyNotificationsNotice": "ليس لديك أي إشعارات حاليًا.", + "CommunityChannelsStrings.emptyNotificationsWithFiltersNotice": "لا توجد إشعارات تطابق عوامل التصفية المحددة.", + "CommunityChannelsStrings.errorLoadingVersions": "تعذّر تحميل سجل الإصدارات", + "CommunityChannelsStrings.errorSnackbar": "حدث خطأ أثناء إرسال القناة", + "CommunityChannelsStrings.feedbackNotesLabel": "ملاحظات المُراجع", + "CommunityChannelsStrings.filterByDateLabel": "فرز حسب التاريخ", + "CommunityChannelsStrings.filterByStatusLabel": "فرز حسب الحالة", "CommunityChannelsStrings.filterLabel": "فلترة", - "CommunityChannelsStrings.fixLicensingBeforeSubmission": "Please fix licensing before submitting a new version.", - "CommunityChannelsStrings.flaggedNotification": "Changes required for version {channelVersion}", - "CommunityChannelsStrings.flaggedStatus": "Needs changes", - "CommunityChannelsStrings.getDraftTokenAction": "Copy token for draft channel", - "CommunityChannelsStrings.goToMyChannelsAction": "Go to My channels", - "CommunityChannelsStrings.hideCriteriaAction": "Hide criteria", - "CommunityChannelsStrings.hideVersions": "Hide versions", - "CommunityChannelsStrings.incompatibleLicensesDescription": "\"{licenseNames}\" - this channel cannot be distributed via Kolibri. If you cannot change the license, remove all resources with \"{licenseNames}\" before submitting again.", - "CommunityChannelsStrings.incompatibleLicensesDetected": "Incompatible licenses detected.", + "CommunityChannelsStrings.fixLicensingBeforeSubmission": "يرجى تصحيح الترخيص قبل إرسال نسخة جديدة.", + "CommunityChannelsStrings.flaggedNotification": "التغييرات المطلوبة للإصدار {channelVersion}", + "CommunityChannelsStrings.flaggedStatus": "يحتاج إجراء تغييرات", + "CommunityChannelsStrings.getDraftTokenAction": "نسخ رمز القناة التجريبية", + "CommunityChannelsStrings.goToMyChannelsAction": "الانتقال إلى قنواتي", + "CommunityChannelsStrings.hideCriteriaAction": "إخفاء المعايير", + "CommunityChannelsStrings.hideVersions": "إخفاء الإصدارات", + "CommunityChannelsStrings.incompatibleLicensesDescription": "\"{licenseNames}\" - لا يمكن توزيع هذه القناة عبر كوليبري. إذا لم تتمكن من تغيير الترخيص، قم بإزالة جميع المصادر التي تحمل اسم \"{licenseNames}\" قبل إعادة الإرسال.", + "CommunityChannelsStrings.incompatibleLicensesDetected": "تم اكتشاف تراخيص غير متوافقة.", "CommunityChannelsStrings.incompleteResourcesDescription1": "لن يتم نشر المصادر غير المكتملة وإتاحتها للتنزيل في \"كوليبري\".", - "CommunityChannelsStrings.incompleteResourcesWarning": "{count, number} {count, plural, one {incomplete resource} other {incomplete resources}}", - "CommunityChannelsStrings.internalNotesLabel": "Admin notes (internal use only)", - "CommunityChannelsStrings.invalidLicensingReason": "Invalid or non-compliant licenses", - "CommunityChannelsStrings.invalidMetadataReason": "Invalid or missing metadata", + "CommunityChannelsStrings.incompleteResourcesWarning": "{count, number} {count, plural, one {مصدر غير متوافق} other {مصادر غير متوافقة}}", + "CommunityChannelsStrings.internalNotesLabel": "ملاحظات المشرف (للاستخدام الداخلي فقط)", + "CommunityChannelsStrings.invalidLicensingReason": "تم رفض القناة بسبب تراخيص غير صالحة أو غير متوافقة مع المتطلبات", + "CommunityChannelsStrings.invalidMetadataReason": "بيانات وصفية غير صالحة أو مفقودة", "CommunityChannelsStrings.languageLabel": "اللغة", "CommunityChannelsStrings.languagesLabel": "اللغات", - "CommunityChannelsStrings.lessDetailsButton": "Hide details", - "CommunityChannelsStrings.licenseCheckPassed": "License check passed.", + "CommunityChannelsStrings.lessDetailsButton": "إخفاء التفاصيل", + "CommunityChannelsStrings.licenseCheckPassed": "تم اجتياز فحص التراخيص بنجاح.", "CommunityChannelsStrings.licensesLabel": "التراخيص", - "CommunityChannelsStrings.loadError": "There was an error loading channels.", - "CommunityChannelsStrings.loadingVersionHistory": "Loading version history", - "CommunityChannelsStrings.moreDetails": "After your submission is approved, the channel will be available to other Kolibri Studio users on the 'Community Library' page.", - "CommunityChannelsStrings.moreDetailsButton": "More details", - "CommunityChannelsStrings.needKolibriVersionToImport": "You will need Kolibri version 0.19.4 or higher to import channels from the Community Library.", - "CommunityChannelsStrings.needsChangesPrimaryInfo": "Your previously submitted version needs changes. Make sure you have addressed all comments before resubmitting.", + "CommunityChannelsStrings.loadError": "حدث خطأ أثناء تحميل القنوات.", + "CommunityChannelsStrings.loadingVersionHistory": "جاري تحميل سجلّ الإصدارات", + "CommunityChannelsStrings.moreDetails": "بعد الموافقة على تقديمك، ستصبح القناة متاحة لمستخدمي استوديو كوليبري الآخرين في صفحة \"مكتبة المجتمع\".", + "CommunityChannelsStrings.moreDetailsButton": "تفاصيل أكثر", + "CommunityChannelsStrings.needKolibriVersionToImport": "ستحتاج إلى كوليبري بالإصدار 0.19.4 أو أحدث لاستيراد القنوات من مكتبة المجتمع.", + "CommunityChannelsStrings.needsChangesPrimaryInfo": "تحتاج النسخة التي سبق إرسالها إلى تعديلات. تأكّد من معالجة جميع الملاحظات قبل إعادة الإرسال.", "CommunityChannelsStrings.newLabel": "جديد", - "CommunityChannelsStrings.newNotificationsNotice": "New notifications available.", + "CommunityChannelsStrings.newNotificationsNotice": "إشعارات جديدة متوفرة.", "CommunityChannelsStrings.nextPageAction": "التالي", - "CommunityChannelsStrings.noCommunityChannels": "No channels have been published to the Community Library yet.", - "CommunityChannelsStrings.noResultsWithFilters": "No channels match the selected filters.", - "CommunityChannelsStrings.noVersionsAvailable": "No version history available", - "CommunityChannelsStrings.nonePrimaryInfo": "We're inviting members of the Kolibri community to submit channels they've created for offline learning in low-resource settings. ", - "CommunityChannelsStrings.notPublishedWarningDescription": "Publish to Studio first, then submit to the Community Library.", - "CommunityChannelsStrings.notPublishedWarningTitle": "This channel isn't published to Kolibri Studio yet", - "CommunityChannelsStrings.notificationsLabel": "Notifications", - "CommunityChannelsStrings.otherIssuesReason": "Other issues", + "CommunityChannelsStrings.noCommunityChannels": "لم تُنشر أي قنوات في مكتبة المجتمع حتى الآن.", + "CommunityChannelsStrings.noResultsWithFilters": "لا توجد قنوات تطابق عوامل التصفية المحددة.", + "CommunityChannelsStrings.noVersionsAvailable": "لا يتوفر سجلّ للإصدارات", + "CommunityChannelsStrings.nonePrimaryInfo": "ندعو أعضاء مجتمع كوليبري إلى تقديم القنوات التي أنشؤوها للتعلّم دون اتصال بالإنترنت في البيئات محدودة المصادر. ", + "CommunityChannelsStrings.notPublishedWarningDescription": "انشر في الاستوديو أولًا، ثم أرسل إلى مكتبة المجتمع.", + "CommunityChannelsStrings.notPublishedWarningTitle": "لم تُنشر هذه القناة بعد في استوديو كوليبري", + "CommunityChannelsStrings.notificationsLabel": "الإشعارات", + "CommunityChannelsStrings.otherIssuesReason": "مشاكل أخرى", "CommunityChannelsStrings.pageIndicator": "{currentPage} من {totalPages}", - "CommunityChannelsStrings.pendingStatus": "Submitted", - "CommunityChannelsStrings.portabilityIssuesReason": "Portability problems", - "CommunityChannelsStrings.previewYourDraftTitle": "Preview your draft channel in Kolibri", + "CommunityChannelsStrings.pendingStatus": "تم التقديم", + "CommunityChannelsStrings.portabilityIssuesReason": "مشكلات حول قابلية النقل", + "CommunityChannelsStrings.previewYourDraftTitle": "معاينة القناة التجريبية في كوليبري", "CommunityChannelsStrings.previousPageAction": "السؤال السابق", - "CommunityChannelsStrings.publicWarningDescription": "It is not possible to submit public channels to the Community Library.", - "CommunityChannelsStrings.publicWarningTitle": "This channel is currently public in the Kolibri Library.", + "CommunityChannelsStrings.publicWarningDescription": "لا يمكن تقديم القنوات العامة إلى مكتبة المجتمع.", + "CommunityChannelsStrings.publicWarningTitle": "هذه القناة متاحة حاليًا للعموم في مكتبة كوليبري.", "CommunityChannelsStrings.publishAction": "نشر", - "CommunityChannelsStrings.publishChannel": "Publish channel", - "CommunityChannelsStrings.publishChannelDescription": "To see your channel in Kolibri, import using the channel token.", - "CommunityChannelsStrings.publishChannelMode": "Publish channel", - "CommunityChannelsStrings.publishDraftDescription": "Your channel will be saved as a draft, allowing you to review and do quality checks on the draft, without altering the current version available for Kolibri users. To see this draft channel in Kolibri, import using the draft channel token.", - "CommunityChannelsStrings.publishDraftMode": "Publish draft channel", + "CommunityChannelsStrings.publishChannel": "نشر القناة", + "CommunityChannelsStrings.publishChannelDescription": "لعرض قناتك في كوليبري، قم باستيرادها باستخدام رمز القناة.", + "CommunityChannelsStrings.publishChannelMode": "نشر القناة", + "CommunityChannelsStrings.publishDraftDescription": "ستُحفظ قناتك كنسخة تجريبية، مما يتيح لك مراجعتها وإجراء فحوصات الجودة عليها دون التأثير على النسخة الحالية المتاحة لمستخدمي كوليبري. لعرض هذه القناة التجريبية في كوليبري، قم باستيرادها باستخدام رمز القناة التجريبية.", + "CommunityChannelsStrings.publishDraftMode": "نشر القناة التجريبية", "CommunityChannelsStrings.publishedVersionLabel": "الإصدار المنشور:", - "CommunityChannelsStrings.publishingInfo": "You're publishing: Version {version}", - "CommunityChannelsStrings.publishingMessage": "Channel is being published", - "CommunityChannelsStrings.qualityAssuranceReason": "Quality assurance issues", - "CommunityChannelsStrings.reasonLabel": "Reason: {reason}", - "CommunityChannelsStrings.resubmitAction": "Resubmit", - "CommunityChannelsStrings.resubmitModalBodyFirst": "{channelName} v{version} is also published to the Community Library.", - "CommunityChannelsStrings.resubmitModalBodySecond": "Would you like to resubmit this version with your changes for Community Library review?", - "CommunityChannelsStrings.resubmitModalTitle": "Resubmit channel for Community library review?", - "CommunityChannelsStrings.resultsText": "{count, plural, =1 {# result found} other {# results found}}", + "CommunityChannelsStrings.publishingInfo": "أنت تقوم بنشر: الإصدار {version}", + "CommunityChannelsStrings.publishingMessage": "يجري الآن نشر القناة", + "CommunityChannelsStrings.qualityAssuranceReason": "مشكلات تتعلق بضمان الجودة", + "CommunityChannelsStrings.reasonLabel": "السبب: {reason}", + "CommunityChannelsStrings.resubmitAction": "إعادة الإرسال", + "CommunityChannelsStrings.resubmitModalBodyFirst": "تم نشر {channelName} v{version} أيضًا في مكتبة المجتمع.", + "CommunityChannelsStrings.resubmitModalBodySecond": "هل ترغب في إعادة إرسال هذه النسخة بعد التعديلات للمراجعة في مكتبة المجتمع؟", + "CommunityChannelsStrings.resubmitModalTitle": "هل تريد إعادة إرسال القناة للمراجعة في مكتبة المجتمع؟", + "CommunityChannelsStrings.resultsText": "{count, plural, =1 {# نتيجة تم العثور عليها} other {# نتيجة تم العثور عليها}}", "CommunityChannelsStrings.retry": "إعادة المحاولة", "CommunityChannelsStrings.reviewAction": "مراجعة", - "CommunityChannelsStrings.saveDraft": "Save draft", + "CommunityChannelsStrings.saveDraft": "حفظ المسوّدة", "CommunityChannelsStrings.searchLabel": "بحث", - "CommunityChannelsStrings.searchNotificationsLabel": "Search notifications", - "CommunityChannelsStrings.seeAllVersions": "See all versions", + "CommunityChannelsStrings.searchNotificationsLabel": "البحث في الإشعارات", + "CommunityChannelsStrings.seeAllVersions": "مشاهدة جميع الإصدارات", "CommunityChannelsStrings.showMore": "عرض المزيد", - "CommunityChannelsStrings.showOlderAction": "Show older", - "CommunityChannelsStrings.specialPermissionsDetected": "Special Permissions licenses detected", - "CommunityChannelsStrings.submissionCreationNotification": "Your submission to the Community Library was successful and is now under review.", - "CommunityChannelsStrings.submissionNotesLabel": "Submission notes", - "CommunityChannelsStrings.submissionNotification": "{author} ({userType}) submitted {channelVersion}", - "CommunityChannelsStrings.submitButton": "Submit for review", - "CommunityChannelsStrings.submitToCommunityLibrary": "Submit to Community Library", - "CommunityChannelsStrings.submittedPrimaryInfo": "A previous version is still pending review. Reviewers will see the most recent submission by default.", - "CommunityChannelsStrings.submittedSnackbar": "Channel submitted to Community Library", - "CommunityChannelsStrings.submittingSnackbar": "Submitting channel to Community Library...", - "CommunityChannelsStrings.supersededStatus": "Superseded", + "CommunityChannelsStrings.showOlderAction": "عرض الأقدم", + "CommunityChannelsStrings.specialPermissionsDetected": "تم اكتشاف تراخيص ذات أذونات خاصة", + "CommunityChannelsStrings.submissionCreationNotification": "أُرسلت قناتك إلى مكتبة المجتمع بنجاح وهي الآن قيد المراجعة.", + "CommunityChannelsStrings.submissionNotesLabel": "ملاحظات التسليم", + "CommunityChannelsStrings.submissionNotification": "{author} ({userType}) قدّم {channelVersion}", + "CommunityChannelsStrings.submitButton": "تقديم للمراجعة", + "CommunityChannelsStrings.submitToCommunityLibrary": "تقديم إلى مكتبة المجتمع", + "CommunityChannelsStrings.submittedPrimaryInfo": "توجد نسخة سابقة لا تزال قيد المراجعة. سيرى المراجعون افتراضيًا أحدث نسخة مُقدَّمة.", + "CommunityChannelsStrings.submittedSnackbar": "تم تقديم القناة إلى مكتبة المجتمع", + "CommunityChannelsStrings.submittingSnackbar": "جارٍ تقديم القناة إلى مكتبة المجتمع...", + "CommunityChannelsStrings.supersededStatus": "تم استبداله", "CommunityChannelsStrings.thisMonthLabel": "هذا الشهر", - "CommunityChannelsStrings.thisWeekLabel": "This week", - "CommunityChannelsStrings.thisYearLabel": "This year", - "CommunityChannelsStrings.todayLabel": "Today", - "CommunityChannelsStrings.unreadNotificationsLabel": "Unread", + "CommunityChannelsStrings.thisWeekLabel": "هذا الأسبوع", + "CommunityChannelsStrings.thisYearLabel": "هذه السنة", + "CommunityChannelsStrings.todayLabel": "اليوم", + "CommunityChannelsStrings.unreadNotificationsLabel": "غير مقروء", "CommunityChannelsStrings.versionDescriptionLabel": "وصف الإصدار", "CommunityChannelsStrings.versionLabel": "نسخة الإصدار {version}", "CommunityChannelsStrings.versionNotesLabel": "قدّم وصفاً لما هو جديد في هذا الإصدار من القناة", - "CommunityChannelsStrings.viewCriteriaAction": "View criteria", + "CommunityChannelsStrings.viewCriteriaAction": "عرض المعايير", "CommunityChannelsStrings.viewMoreAction": "عرض المزيد", - "CommunityChannelsStrings.whatCanYouDoHere": "What you can do here", - "CommunityChannelsStrings.whatCanYouDoHereItem1": "Browse channels by country, category, and language", - "CommunityChannelsStrings.whatCanYouDoHereItem2": "Copy a channel token to use in Kolibri", - "CommunityChannelsStrings.whatCanYouDoHereItem3": "View channel details including description and metadata", - "CommunityChannelsStrings.whatIsCommunityLibrary": "What is the Community Library?", + "CommunityChannelsStrings.whatCanYouDoHere": "ما الذي يمكنك أن تفعله هنا", + "CommunityChannelsStrings.whatCanYouDoHereItem1": "تصفح القنوات حسب البلد والفئة واللغة", + "CommunityChannelsStrings.whatCanYouDoHereItem2": "نسخ رمز قناة للاستخدام في كوليبري", + "CommunityChannelsStrings.whatCanYouDoHereItem3": "عرض تفاصيل القناة بما في ذلك الوصف والبيانات الوصفية", + "CommunityChannelsStrings.whatIsCommunityLibrary": "ما هي مكتبة المجتمع؟", "CommunityStandardsModal.communityStandardsHeader": "معايير المجتمع", "CommunityStandardsModal.coreValuesLink": "تعرف على المزيد حول القيم الأساسية داخل منظمة Learning Equality", "CommunityStandardsModal.description": "Learning Equality هي منظمة غير ربحية تعمل على تمكين الوصول العادل إلى التجارب التعليمية عالية الجودة. وبالإضافة إلى بيان القيم الأساسية لدينا، تهدف معايير المجتمع هذه إلى تعزيز بيئة داعمة وشاملة لمستخدمينا.", @@ -1313,7 +1313,7 @@ "ImportFromChannelsModal.reviewAction": "مراجعة", "ImportFromChannelsModal.reviewTitle": "تحديد المصدر", "InfoModal.close": "إغلاق", - "InfoModal.open": "More information about licenses", + "InfoModal.open": "معلومات إضافية عن التراخيص", "InheritAncestorMetadataModal.applyResourceDetailsTitle": "تطبيق التفاصيل من المجلد '{folder}'", "InheritAncestorMetadataModal.cancelAction": "إلغاء", "InheritAncestorMetadataModal.categories": "الفئات: {categories}", @@ -1349,45 +1349,45 @@ "MasteryCriteriaGoal.labelText": "الهدف", "MasteryCriteriaMofNFields.mHint": "الإجابات الصحيحة مطلوبة", "MasteryCriteriaMofNFields.nHint": "الإجابات الأخيرة", - "MathLiveA11yStrings.accented": "accented", - "MathLiveA11yStrings.array": "array", - "MathLiveA11yStrings.box": "box", - "MathLiveA11yStrings.chemicalFormula": "chemical formula", + "MathLiveA11yStrings.accented": "رمز رياضي بعلامة فوقه", + "MathLiveA11yStrings.array": "المصفوفة", + "MathLiveA11yStrings.box": "عنصر داخل مربع", + "MathLiveA11yStrings.chemicalFormula": "الصيغة الكيميائية", "MathLiveA11yStrings.crossOut": "شطب", "MathLiveA11yStrings.deleted": "تمّ الحذف: ", - "MathLiveA11yStrings.delimiter": "delimiter", - "MathLiveA11yStrings.denominator": "denominator", - "MathLiveA11yStrings.endOf": "{spokenText}; end of {relationName}", - "MathLiveA11yStrings.endOfMathfield": "{spokenText}; end of mathfield", + "MathLiveA11yStrings.delimiter": "رمز فاصل", + "MathLiveA11yStrings.denominator": "المقام", + "MathLiveA11yStrings.endOf": "{spokenText}؛ نهاية {relationName}", + "MathLiveA11yStrings.endOfMathfield": "{spokenText}؛ نهاية حقل الرياضيات", "MathLiveA11yStrings.error": "خطأ", - "MathLiveA11yStrings.extensibleSymbol": "extensible symbol", - "MathLiveA11yStrings.first": "first", - "MathLiveA11yStrings.fraction": "fraction", - "MathLiveA11yStrings.group": "group", - "MathLiveA11yStrings.index": "index", - "MathLiveA11yStrings.latex": "LaTeX", + "MathLiveA11yStrings.extensibleSymbol": "رمز قابل للتوسّع", + "MathLiveA11yStrings.first": "الأول", + "MathLiveA11yStrings.fraction": "جزء", + "MathLiveA11yStrings.group": "مجموعة", + "MathLiveA11yStrings.index": "دليل الجذر", + "MathLiveA11yStrings.latex": "عنصر بلغة لاتك", "MathLiveA11yStrings.line": "السطر", - "MathLiveA11yStrings.mathField": "math field", - "MathLiveA11yStrings.mathfield": "math field", - "MathLiveA11yStrings.numerator": "numerator", - "MathLiveA11yStrings.operator": "operator", - "MathLiveA11yStrings.outOf": "out of {relationName};", - "MathLiveA11yStrings.overUnder": "over-under", - "MathLiveA11yStrings.parent": "parent", - "MathLiveA11yStrings.placeholder": "placeholder", - "MathLiveA11yStrings.prompt": "prompt", - "MathLiveA11yStrings.radicand": "radicand", - "MathLiveA11yStrings.rule": "rule", + "MathLiveA11yStrings.mathField": "حقل رياضيات", + "MathLiveA11yStrings.mathfield": "حقل الرياضيات", + "MathLiveA11yStrings.numerator": "البسط", + "MathLiveA11yStrings.operator": "عامل رياضي", + "MathLiveA11yStrings.outOf": "خارج {relationName}؛", + "MathLiveA11yStrings.overUnder": "فوق-تحت", + "MathLiveA11yStrings.parent": "العنصر الأب", + "MathLiveA11yStrings.placeholder": "عنصر نائب", + "MathLiveA11yStrings.prompt": "موجّه", + "MathLiveA11yStrings.radicand": "المحتوى داخل الجذر", + "MathLiveA11yStrings.rule": "خط", "MathLiveA11yStrings.selected": "تم التحديد: ", - "MathLiveA11yStrings.space": "space", - "MathLiveA11yStrings.spacing": "spacing", + "MathLiveA11yStrings.space": "مسافة", + "MathLiveA11yStrings.spacing": "تباعد", "MathLiveA11yStrings.squareRoot": "جذر تربيعي", - "MathLiveA11yStrings.startOf": "start of {relationName}: ", - "MathLiveA11yStrings.subscript": "subscript", - "MathLiveA11yStrings.subscriptSuperscript": "subscript-superscript", + "MathLiveA11yStrings.startOf": "بداية {relationName}: ", + "MathLiveA11yStrings.subscript": "رموز سفلية", + "MathLiveA11yStrings.subscriptSuperscript": "رموز سفلية وعلوية", "MathLiveA11yStrings.superscript": "أحرف علوية", - "MathLiveA11yStrings.superscriptAndSubscript": "superscript and subscript", - "MathLiveA11yStrings.text": "text", + "MathLiveA11yStrings.superscriptAndSubscript": "رموز سفلية وعلوية", + "MathLiveA11yStrings.text": "النص", "MessageLayout.backToLogin": "المتابعة إلى صفحة تسجيل الدخول", "MoveModal.addTopic": "إضافة مجلد جديد", "MoveModal.cancel": "إلغاء", @@ -1428,7 +1428,7 @@ "PrivacyPolicyModal.updatedPrivacyHeader": "سياسة الخصوصية المحدثة", "ProgressBar.progressText": "{percent}%", "ProgressModal.defaultErrorText": "فشلت آخر محاولة للنشر", - "ProgressModal.draftHeader": "Saving draft...", + "ProgressModal.draftHeader": "جارٍ حفظ النسخة التجريبية...", "ProgressModal.lastPublished": "منشور {last_published}", "ProgressModal.publishHeader": "نشر القناة", "ProgressModal.syncError": "فشلت آخر محاولة للمزامنة", @@ -1476,7 +1476,7 @@ "RemoveChannelFromListModal.removeTitle": "إزالة من قائمة القنوات", "RemoveChannelModal.cancel": "إلغاء", "RemoveChannelModal.deleteChannel": "حذف القناة التعليمية", - "RemoveChannelModal.deleteChannelWithCLWarning": "This channel has been shared with the Community Library. Deleting it here will not remove it from the Community Library — it may still be approved or remain available there.", + "RemoveChannelModal.deleteChannelWithCLWarning": "تمت مشاركة هذه القناة مع مكتبة المجتمع. لن يؤدي حذفها هنا إلى إزالتها من مكتبة المجتمع وقد تظل بانتظار الاعتماد أو تبقى متاحة هناك.", "RemoveChannelModal.deletePrompt": "سيتم حذف هذه القناة بشكل دائم. لا يمكن التراجع عن هذه الخطوة.", "RemoveChannelModal.deleteTitle": "حذف هذه القناة", "RemoveChannelModal.removeBtn": "إزالة", @@ -1669,11 +1669,11 @@ "SearchRecommendationsStrings.tooAdvancedForLearnersLabel": "مُتقدم جدًا بالنسبة لمستوى معرفة المتعلمين الذي أبحث عنه", "SearchRecommendationsStrings.tooBasicForLearnersLabel": "بسيط جدًا بالنسبة لمستوى معرفة المتعلمين الذي أبحث عنه", "SearchRecommendationsStrings.tryAgainLink": "حاول مرة أخرى", - "SearchRecommendationsStrings.trySearchRecommendationsHeader": "Try our new 'Recommendations' feature!", - "SearchRecommendationsStrings.trySearchRecommendationsText": "Based on the title and description of the folder you are working on, we'll suggest relevant resources from the Kolibri Library. Choose 'Import from channels' in any folder of your channels to see the recommendations.", + "SearchRecommendationsStrings.trySearchRecommendationsHeader": "جرّب ميزة \"التوصيات\" الجديدة!", + "SearchRecommendationsStrings.trySearchRecommendationsText": "استنادًا إلى عنوان ووصف المجلد الذي تعمل عليه، سنقترح مصادر ذات صلة من مكتبة كوليبري. اختر \"استيراد من القنوات\" داخل أي مجلد في قنواتك لعرض التوصيات.", "SearchRecommendationsStrings.undoAction": "تراجع", "SearchRecommendationsStrings.viewMoreLink": "عرض المزيد", - "SearchRecommendationsStrings.viewRecommendationsButton": "View recommendations", + "SearchRecommendationsStrings.viewRecommendationsButton": "عرض التوصيات", "SearchResultsList.failedToLoad": "يتعذر تحميل نتائج البحث", "SearchResultsList.resultsPerPageLabel": "النتائج لكل صفحة", "SearchResultsList.saveSearchAction": "حفظ عملية البحث", @@ -1731,14 +1731,14 @@ "StudioChannelCard.details": "التفاصيل", "StudioChannelCard.lastPublished": "منشور {last_published}", "StudioChannelCard.lastUpdated": "{updated} تم تحديثها", - "StudioChannelCard.multipleCountries": "Multiple countries", + "StudioChannelCard.multipleCountries": "متعدد البلدان", "StudioChannelCard.resourceCount": "{count, plural, zero {# مصادر} one {# مصادر} two {# مصدران} few {# مصادر} many {# مصدراً}\n =1 {# مصدر}\n other {# مصادر}}", - "StudioChannelCard.selectChannel": "Select {name}", + "StudioChannelCard.selectChannel": "اختر {name}", "StudioChannelCard.unpublishedText": "لم يتم النشر", "StudioChannelsPage.invitations": "لديكَ {count, plural, zero {# دعوات} one {# دعوات} two {# دعوتان} few {# دعوات} many {# دعوة}\n =1 {# دعوة}\n other {# دعوات}}", "StudioChannelsPage.noChannelsFound": "لم يتم العثور على أي قناة", "StudioCollectionsTable.aboutChannelSets": "حول المجموعات", - "StudioCollectionsTable.aboutChannelSetsLink": "Learn more about collections", + "StudioCollectionsTable.aboutChannelSetsLink": "تعرّف على المزيد حول المجموعات", "StudioCollectionsTable.addChannelSetTitle": "مجموعة جديدة", "StudioCollectionsTable.cancel": "إلغاء", "StudioCollectionsTable.cancelButtonLabel": "إغلاق", @@ -1746,20 +1746,20 @@ "StudioCollectionsTable.channelSetsDescriptionText": "تحتوي المجموعة على العديد من قنوات استوديو كوليبري التي يمكن استيرادها في دفعة واحدة إلى كوليبري برمز تعريفي واحد.", "StudioCollectionsTable.channelSetsDisclaimer": "ستحتاج إلى إصدار كوليبري 0.12.0 أو ما بعده لاستيراد مجموعات القنوات", "StudioCollectionsTable.channelSetsInstructionsText": "يمكنك إنشاء مجموعة عن طريق تحديد القنوات التي تريد استيرادها معاً.", - "StudioCollectionsTable.collectionDeleted": "Collection deleted", + "StudioCollectionsTable.collectionDeleted": "تم حذف المجموعة", "StudioCollectionsTable.copiedTokenId": "تم نسخ الرمز التعريفي", "StudioCollectionsTable.copyFailed": "فشل النسخ", - "StudioCollectionsTable.copyToken": "Copy token", + "StudioCollectionsTable.copyToken": "نسخ الرمز", "StudioCollectionsTable.delete": "حذف المجموعة", "StudioCollectionsTable.deleteChannelSetText": "هل أنت متأكد من أنك تريد حذف هذه المجموعة؟", "StudioCollectionsTable.deleteChannelSetTitle": "حذف المجموعة", - "StudioCollectionsTable.deleteError": "Error deleting collection", + "StudioCollectionsTable.deleteError": "حدث خطأ أثناء حذف المجموعة", "StudioCollectionsTable.edit": "تحرير المجموعة", "StudioCollectionsTable.noChannelSetsFound": "يمكنك تجميع قنوات متعددة لإنشاء مجموعة. ثم يمكن استيراد المجموعة بأكملها إلى كوليبري دفعة واحدة باستخدام رمز المجموعة التعريفي.", "StudioCollectionsTable.options": "الخيارات", "StudioCollectionsTable.pageTitle": "المجموعات", "StudioCollectionsTable.saving": "جاري الحفظ", - "StudioCollectionsTable.tableCaption": "List of collections", + "StudioCollectionsTable.tableCaption": "قائمة المجموعات", "StudioCollectionsTable.title": "اسم المجموعة", "StudioCollectionsTable.token": "معرّف الرمز التعريفي", "StudioCopyToken.copiedTokenId": "تم نسخ الرمز التعريفي", @@ -1816,32 +1816,32 @@ "StudioStarredChannels.editChannel": "تعديل تفاصيل القناة", "StudioStarredChannels.goToWebsite": "التوجّه إلى موقع المصدر الإلكتروني", "StudioStarredChannels.moreOptions": "خيارات إضافية", - "StudioStarredChannels.removeChannel": "Remove channel", - "StudioStarredChannels.title": "Starred channels", + "StudioStarredChannels.removeChannel": "إزالة القناة", + "StudioStarredChannels.title": "القنوات المفضّلة", "StudioStarredChannels.viewContent": "عرض القناة على كوليبري", "StudioTree.missingTitle": "عنوان مفقود", "StudioTree.optionsTooltip": "الخيارات", "StudioViewOnlyChannels.copyToken": "نسخ معرّف القناة", "StudioViewOnlyChannels.goToWebsite": "التوجّه إلى موقع المصدر الإلكتروني", "StudioViewOnlyChannels.moreOptions": "خيارات أخرى", - "StudioViewOnlyChannels.removeChannel": "Remove channel", - "StudioViewOnlyChannels.title": "View-only channels", + "StudioViewOnlyChannels.removeChannel": "إزالة القناة", + "StudioViewOnlyChannels.title": "قنوات العرض فقط", "StudioViewOnlyChannels.viewContent": "عرض القناة على كوليبري", - "SubscriptionCard.annualPrice": "${price, number}/year", - "SubscriptionCard.cancelNotice": "Your subscription will expire on {date, date, medium}. Storage will be removed after that.", + "SubscriptionCard.annualPrice": "${price, number}/سنة", + "SubscriptionCard.cancelNotice": "ستنتهي صلاحية اشتراكك في {date, date, medium}. سيتم حذف مساحة التخزين بعد ذلك.", "SubscriptionCard.dismiss": "رفض", - "SubscriptionCard.genericError": "There was a problem connecting to the payment provider. Please try again.", - "SubscriptionCard.instantUpgrade": "Upgrade storage now", - "SubscriptionCard.manageSubscription": "Manage subscription", - "SubscriptionCard.renewalNotice": "Your subscription will automatically renew on {date, date, medium}.", - "SubscriptionCard.storageAmount": "Storage (GB)", - "SubscriptionCard.storageIncluded": "{size} included in your subscription", - "SubscriptionCard.storageRange": "Enter a value between 1 and 50", - "SubscriptionCard.subscriptionActive": "Storage subscription active", - "SubscriptionCard.subscriptionCanceling": "Subscription cancelled", - "SubscriptionCard.upgradeDescription": "Purchase additional storage at $15/GB per year.", - "SubscriptionCard.upgradeNow": "Upgrade now", - "SubscriptionCard.upgradeSuccess": "Storage increased to {size}", + "SubscriptionCard.genericError": "حدثت مشكلة أثناء الاتصال بمزوّد الدفع. يُرجى المحاولة مرة أخرى.", + "SubscriptionCard.instantUpgrade": "ترقية مساحة التخزين الآن", + "SubscriptionCard.manageSubscription": "إدارة الإشتراك", + "SubscriptionCard.renewalNotice": "سيتم تجديد اشتراكك تلقائيًا في {date, date, medium}.", + "SubscriptionCard.storageAmount": "التخزين (جيجابايت)", + "SubscriptionCard.storageIncluded": "{size} مشمولة ضمن اشتراكك", + "SubscriptionCard.storageRange": "أدخل قيمة بين 1 و 50", + "SubscriptionCard.subscriptionActive": "اشتراك مساحة التخزين نشط", + "SubscriptionCard.subscriptionCanceling": "تم إلغاء الإشتراك", + "SubscriptionCard.upgradeDescription": "اشتر مساحة تخزين إضافية بسعر 15 دولارًا لكل جيجابايت سنويًا.", + "SubscriptionCard.upgradeNow": "ترقية الآن", + "SubscriptionCard.upgradeSuccess": "توَسّعت مساحة التخزين إلى {size}", "SubtitlesList.acceptedFormatsTooltip": "الصيغ مدعومة: {extensions}", "SubtitlesList.addSubtitleText": "إضافة عناوين للصور", "SubtitlesList.subtitlesHeader": "عناوين الصور والنص السمعي", @@ -1969,95 +1969,95 @@ "ThumbnailGenerator.generatedDefaultFilename": "صورة مصغرة تم إنشاؤها", "ThumbnailGenerator.thumbnailGenerationFailedHeader": "تعذّر إنشاء صورة مصغرة", "ThumbnailGenerator.thumbnailGenerationFailedText": "حدثت مشكلة أثناء إنشاء صورة مصغرة", - "TipTapEditorStrings.TipTapEditorLabel": "text editor - Press Enter to start editing", - "TipTapEditorStrings.TipTapViewerLabel": "text editor content", - "TipTapEditorStrings.addLink": "Add link", - "TipTapEditorStrings.alignLeft": "Align left", - "TipTapEditorStrings.alignRight": "Align right", - "TipTapEditorStrings.altTextDescription": "Alt text is necessary to enable visually impaired learners to answer questions, and it also displays when the image fails to load", - "TipTapEditorStrings.altTextLabel": "Alt text (Optional)", - "TipTapEditorStrings.altTextPlaceholder": "Describe your image...", - "TipTapEditorStrings.bold": "Strong", - "TipTapEditorStrings.bulletList": "Bullet list", + "TipTapEditorStrings.TipTapEditorLabel": "محرر النص - اضغط Enter لبدء التحرير", + "TipTapEditorStrings.TipTapViewerLabel": "محتوى محرر النص", + "TipTapEditorStrings.addLink": "إضافة رابط", + "TipTapEditorStrings.alignLeft": "محاذاة إلى اليسار", + "TipTapEditorStrings.alignRight": "محاذاة إلى اليمين", + "TipTapEditorStrings.altTextDescription": "النص البديل ضروري لتمكين المتعلمين من ذوي الإعاقة البصرية من الإجابة على الأسئلة، كما يظهر أيضًا عند تعذّر تحميل الصورة", + "TipTapEditorStrings.altTextLabel": "النص البديل (اختياري)", + "TipTapEditorStrings.altTextPlaceholder": "صف صورتك...", + "TipTapEditorStrings.bold": "عريض", + "TipTapEditorStrings.bulletList": "قائمة نقطية", "TipTapEditorStrings.cancel": "إلغاء", - "TipTapEditorStrings.cancelLoading": "Cancel loading", - "TipTapEditorStrings.clearFormatting": "Clear formatting", - "TipTapEditorStrings.clipboardAccessFailed": "Clipboard access failed. Try copying again.", + "TipTapEditorStrings.cancelLoading": "إلغاء التحميل", + "TipTapEditorStrings.clearFormatting": "مسح التنسيق", + "TipTapEditorStrings.clipboardAccessFailed": "فشل الوصول إلى الحافظة. حاول النسخ مرة أخرى.", "TipTapEditorStrings.close": "إغلاق", - "TipTapEditorStrings.closeModal": "Close modal", - "TipTapEditorStrings.codeBlock": "Code block", - "TipTapEditorStrings.collapseFormattingBar": "Collapse formatting bar", + "TipTapEditorStrings.closeModal": "إغلاق النافذة المنبثقة", + "TipTapEditorStrings.codeBlock": "كتلة تعليمات برمجية", + "TipTapEditorStrings.collapseFormattingBar": "إخفاء شريط التنسيق", "TipTapEditorStrings.copy": "نسخ", - "TipTapEditorStrings.copyAndPasteActions": "Copy and paste actions", - "TipTapEditorStrings.copyLink": "Copy link", - "TipTapEditorStrings.decreaseFormatSize": "Decrease format size", - "TipTapEditorStrings.defaultImageName": "Image", + "TipTapEditorStrings.copyAndPasteActions": "نسخ ولصق الإجراءات", + "TipTapEditorStrings.copyLink": "نسخ الرابط", + "TipTapEditorStrings.decreaseFormatSize": "تقليل حجم التنسيق", + "TipTapEditorStrings.defaultImageName": "صورة", "TipTapEditorStrings.edit": "تعديل", - "TipTapEditorStrings.editImage": "Edit image", - "TipTapEditorStrings.editLink": "Edit link", - "TipTapEditorStrings.editorControls": "Editor controls", - "TipTapEditorStrings.errorUploadingImage": "Error uploading image", - "TipTapEditorStrings.expandFormattingBar": "Expand formatting bar", - "TipTapEditorStrings.failedToProcessImage": "Failed to process the image file.", + "TipTapEditorStrings.editImage": "تحرير صورة", + "TipTapEditorStrings.editLink": "تعديل الرابط", + "TipTapEditorStrings.editorControls": "عناصر تحكّم المحرّر", + "TipTapEditorStrings.errorUploadingImage": "خطأ في تحميل الصورة", + "TipTapEditorStrings.expandFormattingBar": "توسيع شريط التنسيق", + "TipTapEditorStrings.failedToProcessImage": "فشل في معالجة ملف الصورة.", "TipTapEditorStrings.fileSizeUnit": "ميغا بايت.", - "TipTapEditorStrings.fileTooLarge": "File is too large. Maximum size is ", - "TipTapEditorStrings.formatHeader1": "Header 1", - "TipTapEditorStrings.formatHeader2": "Header 2", - "TipTapEditorStrings.formatHeader3": "Header 3", - "TipTapEditorStrings.formatNormal": "Normal", - "TipTapEditorStrings.formatOptions": "Format options", - "TipTapEditorStrings.formatSize": "Format size", + "TipTapEditorStrings.fileTooLarge": "الملف كبير جداً. أقصى حجم خط هو ", + "TipTapEditorStrings.formatHeader1": "ترويسة 1", + "TipTapEditorStrings.formatHeader2": "ترويسة 2", + "TipTapEditorStrings.formatHeader3": "ترويسة 3", + "TipTapEditorStrings.formatNormal": "عادي", + "TipTapEditorStrings.formatOptions": "خيارات التنسيق", + "TipTapEditorStrings.formatSize": "حجم التنسيق", "TipTapEditorStrings.formatSmall": "صغير", - "TipTapEditorStrings.formulasMenuTitle": "Special Characters", - "TipTapEditorStrings.goToLink": "Go to link", - "TipTapEditorStrings.historyActions": "History actions", - "TipTapEditorStrings.imageDropZoneText": "Drag and drop an image here or upload manually", - "TipTapEditorStrings.imagePreview": "Image preview", - "TipTapEditorStrings.increaseFormatSize": "Increase format size", + "TipTapEditorStrings.formulasMenuTitle": "أحرف خاصة", + "TipTapEditorStrings.goToLink": "الانتقال إلى الرابط", + "TipTapEditorStrings.historyActions": "إجراءات السجل", + "TipTapEditorStrings.imageDropZoneText": "اسحب وأفلت صورة هنا أو قم برفعها يدويًا", + "TipTapEditorStrings.imagePreview": "معاينة الصورة", + "TipTapEditorStrings.increaseFormatSize": "زيادة حجم التنسيق", "TipTapEditorStrings.insert": "إدراج", - "TipTapEditorStrings.insertContent": "Insert content", - "TipTapEditorStrings.insertContentMenu": "Insert content menu", - "TipTapEditorStrings.insertContentOption": "Insert content option", - "TipTapEditorStrings.insertImage": "Insert image", - "TipTapEditorStrings.insertLink": "Insert link", - "TipTapEditorStrings.insertTools": "Insert tools", - "TipTapEditorStrings.invalidFileType": "Invalid file type. Please use: ", - "TipTapEditorStrings.italic": "Italic", - "TipTapEditorStrings.link": "Link", - "TipTapEditorStrings.linkActions": "Link actions", - "TipTapEditorStrings.listFormatting": "List formatting", - "TipTapEditorStrings.loadingFormulas": "Loading math editor", - "TipTapEditorStrings.mathFormula": "Math formula", + "TipTapEditorStrings.insertContent": "إدراج المحتوى", + "TipTapEditorStrings.insertContentMenu": "إدراج قائمة المحتوى", + "TipTapEditorStrings.insertContentOption": "إدراج خيار المحتوى", + "TipTapEditorStrings.insertImage": "إدراج صورة", + "TipTapEditorStrings.insertLink": "إدراج رابط", + "TipTapEditorStrings.insertTools": "إدراج أدوات", + "TipTapEditorStrings.invalidFileType": "نوع الملف غير صالح. يُرجى استخدام: ", + "TipTapEditorStrings.italic": "خط مائل", + "TipTapEditorStrings.link": "الرابط", + "TipTapEditorStrings.linkActions": "إجراءات الرابط", + "TipTapEditorStrings.listFormatting": "تنسيق القائمة", + "TipTapEditorStrings.loadingFormulas": "تحميل محرر الرياضيات", + "TipTapEditorStrings.mathFormula": "المعادلة الرياضية", "TipTapEditorStrings.moreButtonText": "المزيد", - "TipTapEditorStrings.multipleFilesDroppedWarning": "Multiple files were dropped. Only the first file has been selected.", - "TipTapEditorStrings.noEnoughStorageSpace": "Not enough storage space available. File size exceeds remaining storage.", - "TipTapEditorStrings.noFileProvided": "No file provided.", - "TipTapEditorStrings.numberedList": "Numbered list", - "TipTapEditorStrings.opensInNewTab": "(opens in new tab)", - "TipTapEditorStrings.paste": "Paste", - "TipTapEditorStrings.pasteOptions": "Paste options", - "TipTapEditorStrings.pasteOptionsMenu": "Paste options menu", - "TipTapEditorStrings.pasteWithoutFormatting": "Paste without formatting", - "TipTapEditorStrings.redo": "Redo", + "TipTapEditorStrings.multipleFilesDroppedWarning": "تم إسقاط عدة ملفات. تم اختيار الملف الأول فقط.", + "TipTapEditorStrings.noEnoughStorageSpace": "لا توجد مساحة تخزين كافية. يتجاوز حجم الملف مساحة التخزين المتبقية.", + "TipTapEditorStrings.noFileProvided": "لم يتم توفير ملف.", + "TipTapEditorStrings.numberedList": "قائمة مرقّمة", + "TipTapEditorStrings.opensInNewTab": "(يفتح في علامة تبويب جديدة)", + "TipTapEditorStrings.paste": "لصق", + "TipTapEditorStrings.pasteOptions": "خيارات اللصق", + "TipTapEditorStrings.pasteOptionsMenu": "قائمة خيارات اللصق", + "TipTapEditorStrings.pasteWithoutFormatting": "لصق بدون تنسيق", + "TipTapEditorStrings.redo": "إعادة", "TipTapEditorStrings.remove": "إزالة", - "TipTapEditorStrings.removeImage": "Remove image", - "TipTapEditorStrings.removeLink": "Remove link", + "TipTapEditorStrings.removeImage": "إزالة الصورة", + "TipTapEditorStrings.removeLink": "حذف الرابط", "TipTapEditorStrings.replaceFile": "استبدال ملف", "TipTapEditorStrings.save": "حفظ", "TipTapEditorStrings.saveChanges": "حفظ التغييرات", - "TipTapEditorStrings.scriptFormatting": "Script formatting", + "TipTapEditorStrings.scriptFormatting": "تنسيق النص البرمجي", "TipTapEditorStrings.selectFile": "تحديد ملف", - "TipTapEditorStrings.selectFileToUpload": "Select file to upload", - "TipTapEditorStrings.strikethrough": "Strikethrough", - "TipTapEditorStrings.subscript": "Subscript", + "TipTapEditorStrings.selectFileToUpload": "اختر ملف لرفعه", + "TipTapEditorStrings.strikethrough": "شطب", + "TipTapEditorStrings.subscript": "رموز سفلية", "TipTapEditorStrings.superscript": "أحرف علوية", "TipTapEditorStrings.supportedFileTypes": "أنواع الملفات المدعومة: { extensions }", - "TipTapEditorStrings.text": "Text", - "TipTapEditorStrings.textFormatOptions": "Text format options", - "TipTapEditorStrings.textFormattingOptions": "Text formatting options", - "TipTapEditorStrings.textFormattingToolbar": "Text formatting toolbar", - "TipTapEditorStrings.textStyleFormatting": "Text style formatting", - "TipTapEditorStrings.underline": "Underline", + "TipTapEditorStrings.text": "النص", + "TipTapEditorStrings.textFormatOptions": "خيارات تنسيق النص", + "TipTapEditorStrings.textFormattingOptions": "خيارات تنسيق النص", + "TipTapEditorStrings.textFormattingToolbar": "شريط أدوات تنسيق النص", + "TipTapEditorStrings.textStyleFormatting": "تنسيق نمط النص", + "TipTapEditorStrings.underline": "تسطير", "TipTapEditorStrings.undo": "تراجع", "TipTapEditorStrings.uploadImage": "رفع صورة", "TitleStrings.catalogTitle": "دليل مكتبة محتوى كوليبري", @@ -2098,8 +2098,8 @@ "TreeViewBase.publishButtonTitle": "جعل هذه القناة متاحة للاستيراد إلى كوليبري", "TreeViewBase.shareChannel": "شارك القناة", "TreeViewBase.shareMenuButton": "مشاركة", - "TreeViewBase.shareToken": "Share token", - "TreeViewBase.submitToCommunityLibrary": "Submit to Community Library", + "TreeViewBase.shareToken": "مشاركة الرمز", + "TreeViewBase.submitToCommunityLibrary": "تقديم إلى مكتبة المجتمع", "TreeViewBase.syncChannel": "مزامنة المصادر", "TreeViewBase.viewOnly": "قنوات للعرض فقط", "Uploader.closeButtonLabel": "موافقة", @@ -2176,4 +2176,4 @@ "sharedVue.masteryModelRequired": "الإتقان مطلوب", "sharedVue.shortActivityLteThirty": "يجب أن تكون القيمة مساوية أو أقل من 30", "sharedVue.titleRequired": "العنوان إلزامي" -} +} \ No newline at end of file diff --git a/contentcuration/locale/ar/LC_MESSAGES/django.po b/contentcuration/locale/ar/LC_MESSAGES/django.po index 86c3ef6736..e39e365b39 100644 --- a/contentcuration/locale/ar/LC_MESSAGES/django.po +++ b/contentcuration/locale/ar/LC_MESSAGES/django.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: kolibri-studio\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-04-16 00:57+0000\n" -"PO-Revision-Date: 2026-04-20 19:33\n" +"PO-Revision-Date: 2026-04-24 16:20\n" "Last-Translator: \n" "Language-Team: Arabic\n" "Language: ar_SA\n" @@ -739,3 +739,4 @@ msgstr "إصدار الـ API غير متوفر" #: kolibri_public/views_v1.py:170 msgid "No channel matching {} found" msgstr "لم يتم العثور على قناة مطابقة {}" + diff --git a/contentcuration/locale/en/LC_MESSAGES/contentcuration-messages.json b/contentcuration/locale/en/LC_MESSAGES/contentcuration-messages.json index 90e0a99c07..1983272e37 100644 --- a/contentcuration/locale/en/LC_MESSAGES/contentcuration-messages.json +++ b/contentcuration/locale/en/LC_MESSAGES/contentcuration-messages.json @@ -2176,4 +2176,4 @@ "sharedVue.masteryModelRequired": "Mastery is required", "sharedVue.shortActivityLteThirty": "Value must be equal or less than 30", "sharedVue.titleRequired": "Title is required" -} +} \ No newline at end of file diff --git a/contentcuration/locale/es_ES/LC_MESSAGES/contentcuration-messages.json b/contentcuration/locale/es_ES/LC_MESSAGES/contentcuration-messages.json index 23dcfe6d16..a48f7e8d2e 100644 --- a/contentcuration/locale/es_ES/LC_MESSAGES/contentcuration-messages.json +++ b/contentcuration/locale/es_ES/LC_MESSAGES/contentcuration-messages.json @@ -2176,4 +2176,4 @@ "sharedVue.masteryModelRequired": "Tiene que seleccionar el criterio de dominio", "sharedVue.shortActivityLteThirty": "El valor debe ser igual o menor que 30", "sharedVue.titleRequired": "Este campo es obligatorio" -} +} \ No newline at end of file diff --git a/contentcuration/locale/es_ES/LC_MESSAGES/django.po b/contentcuration/locale/es_ES/LC_MESSAGES/django.po index a785ae3603..76fe11bfea 100644 --- a/contentcuration/locale/es_ES/LC_MESSAGES/django.po +++ b/contentcuration/locale/es_ES/LC_MESSAGES/django.po @@ -742,3 +742,4 @@ msgstr "La versión API no está disponible" #: kolibri_public/views_v1.py:170 msgid "No channel matching {} found" msgstr "Ningún canal con {} encontrado" + diff --git a/contentcuration/locale/fr_FR/LC_MESSAGES/contentcuration-messages.json b/contentcuration/locale/fr_FR/LC_MESSAGES/contentcuration-messages.json index 39ef4d5a36..090fde701f 100644 --- a/contentcuration/locale/fr_FR/LC_MESSAGES/contentcuration-messages.json +++ b/contentcuration/locale/fr_FR/LC_MESSAGES/contentcuration-messages.json @@ -273,7 +273,7 @@ "ChannelItem.goToWebsite": "Aller sur le site web source", "ChannelItem.lastPublished": "Publié le {last_published}", "ChannelItem.lastUpdated": "Mis à jour {updated}", - "ChannelItem.removeChannel": "Remove channel", + "ChannelItem.removeChannel": "Supprimer la chaîne", "ChannelItem.resourceCount": "{count, plural, one {}\n =1 {# ressource}\n other {# ressources}}", "ChannelItem.unpublishedText": "Non publié", "ChannelItem.versionText": "Version {version}", @@ -508,162 +508,162 @@ "CommonMetadataStrings.work": "Professionnel", "CommonMetadataStrings.writing": "Écriture", "CommonStrings.backAction": "Retour", - "CommonStrings.channelDetailsLabel": "Channel Details", + "CommonStrings.channelDetailsLabel": "Détails de la chaîne", "CommonStrings.clearAction": "Supprimer", "CommonStrings.closeAction": "Fermer", "CommonStrings.copyChannelTokenAction": "Copier le jeton de la chaîne", "CommonStrings.dismissAction": "Rejeter", "CommonStrings.genericErrorMessage": "Désolé ! Une erreur est survenue. Veuillez réessayer.", "CommonStrings.previewAction": "Aperçu", - "CommonStrings.seeAllAction": "See all", - "CommonStrings.seeLessAction": "See less", - "CommunityChannelsStrings.aboutCommunityLibraryDescription": "Community library contains channels submitted by the community and approved for discovery in Studio.", - "CommunityChannelsStrings.aboutCommunityLibraryTitle": "About Community Library", - "CommunityChannelsStrings.activityHistoryLabel": "Activity history", + "CommonStrings.seeAllAction": "Afficher tout", + "CommonStrings.seeLessAction": "Afficher moins", + "CommunityChannelsStrings.aboutCommunityLibraryDescription": "La bibliothèque communautaire comprend les chaînes proposées par la communauté et approuvées pour être retrouvées dans Studio.", + "CommunityChannelsStrings.aboutCommunityLibraryTitle": "À propos de la bibliothèque communautaire", + "CommunityChannelsStrings.activityHistoryLabel": "Historique des activités", "CommunityChannelsStrings.adminLabel": "Administrateur", - "CommunityChannelsStrings.allLicensesCompatible": "All licenses are compatible with Community Library.", - "CommunityChannelsStrings.allNotificationsLabel": "All Notifications", - "CommunityChannelsStrings.alreadySubmittedWarningDescription": "Please wait for the channel to be reviewed. You will see a notification in your Studio account after the review is complete.", - "CommunityChannelsStrings.alreadySubmittedWarningTitle": "This version of the channel has already been submitted to the Community Library.", - "CommunityChannelsStrings.approvedNotification": "{author} ({userType}) approved {channelVersion}", - "CommunityChannelsStrings.approvedPrimaryInfo": "A previous version is live in the Community Library. Reviewers will see the latest submission first.", - "CommunityChannelsStrings.approvedStatus": "Approved", - "CommunityChannelsStrings.availableStatus": "Available in Community Library", + "CommunityChannelsStrings.allLicensesCompatible": "Toutes les licences sont compatibles avec la bibliothèque communautaire.", + "CommunityChannelsStrings.allNotificationsLabel": "Toutes les notifications", + "CommunityChannelsStrings.alreadySubmittedWarningDescription": "Veuillez patienter pendant l'examen de la chaîne. Vous recevrez une notification dans votre compte Studio une fois l'examen terminé.", + "CommunityChannelsStrings.alreadySubmittedWarningTitle": "Cette version de la chaîne a déjà été soumise à la bibliothèque communautaire.", + "CommunityChannelsStrings.approvedNotification": "{author} ({userType}) a approuvé {channelVersion}", + "CommunityChannelsStrings.approvedPrimaryInfo": "Une version antérieure est disponible dans la bibliothèque communautaire. Les évaluateurs verront d'abord la dernière version soumise.", + "CommunityChannelsStrings.approvedStatus": "Approuvé", + "CommunityChannelsStrings.availableStatus": "Disponible dans la bibliothèque communautaire", "CommunityChannelsStrings.cancelAction": "Annuler", "CommunityChannelsStrings.categoriesLabel": "Catégories", - "CommunityChannelsStrings.channelCannotBeDistributed": "This channel cannot be distributed via Kolibri.", + "CommunityChannelsStrings.channelCannotBeDistributed": "Cette chaîne ne peut pas être diffusée via Kolibri.", "CommunityChannelsStrings.channelFitAttributionLabel": "Attribution", - "CommunityChannelsStrings.channelFitChannelInfoLabel": "Channel information", - "CommunityChannelsStrings.channelFitChecklistAttribution": "Does each resource have an author listed?", - "CommunityChannelsStrings.channelFitChecklistChannelInfo": "Is your channel's basic information filled in — title, description, thumbnail, language, category, and level?", - "CommunityChannelsStrings.channelFitChecklistIntro": "Criteria for submitting to the Community Library", - "CommunityChannelsStrings.channelFitChecklistLicense": "Are the resources in your channel openly licensed or in the public domain?", - "CommunityChannelsStrings.channelFitChecklistOfflineUse": "Do the resources in your channel work without an Internet connection?", - "CommunityChannelsStrings.channelFitChecklistQuality": "Has anyone on your team or in your organization reviewed the channel?", + "CommunityChannelsStrings.channelFitChannelInfoLabel": "Informations sur la chaîne", + "CommunityChannelsStrings.channelFitChecklistAttribution": "Chaque ressource comporte-t-elle un auteur ?", + "CommunityChannelsStrings.channelFitChecklistChannelInfo": "Les informations de base de votre chaîne sont-elles renseignées : titre, description, vignette, langue, catégorie et niveau ?", + "CommunityChannelsStrings.channelFitChecklistIntro": "Critères d'envoi à la bibliothèque communautaire", + "CommunityChannelsStrings.channelFitChecklistLicense": "Les ressources de votre chaîne sont-elles sous licence libre ou relèvent-elles du domaine public ?", + "CommunityChannelsStrings.channelFitChecklistOfflineUse": "Les ressources de votre chaîne sont-elles accessibles sans connexion Internet ?", + "CommunityChannelsStrings.channelFitChecklistQuality": "Y a-t-il quelqu'un dans votre équipe ou au sein de votre organisation qui a examiné cette chaîne ?", "CommunityChannelsStrings.channelFitLicenseLabel": "Licence", - "CommunityChannelsStrings.channelFitOfflineUseLabel": "Offline use", - "CommunityChannelsStrings.channelFitQualityLabel": "Quality", - "CommunityChannelsStrings.channelTokenDescription": "You can use this token to import and preview the draft channel in Kolibri. Please note that the token for the final published channel will be different.", + "CommunityChannelsStrings.channelFitOfflineUseLabel": "Utilisation hors ligne", + "CommunityChannelsStrings.channelFitQualityLabel": "Qualité", + "CommunityChannelsStrings.channelTokenDescription": "Vous pouvez utiliser ce jeton pour importer et obtenir un aperçu de la version préliminaire de la chaîne dans Kolibri. Attention, le jeton utilisé pour la chaîne publiée définitive sera différent.", "CommunityChannelsStrings.channelVersion": "{name} v{version}", - "CommunityChannelsStrings.channelVersionTokenLabel": "Channel version token", + "CommunityChannelsStrings.channelVersionTokenLabel": "Jeton de version de chaîne", "CommunityChannelsStrings.clearAll": "Supprimer tout", "CommunityChannelsStrings.clearAllAction": "Supprimer tout", - "CommunityChannelsStrings.communityLibraryCTADescription": "Have a channel worth sharing with other educators and learners? Submit it for review through the Share menu so it can be discovered in Studio.", - "CommunityChannelsStrings.communityLibraryCTATitle": "Help grow the Community Library", - "CommunityChannelsStrings.communityLibraryDescription": "Browse community-submitted channels approved for discovery in Studio. Copy a token to use a channel in Kolibri.", - "CommunityChannelsStrings.communityLibraryLabel": "Community Library", - "CommunityChannelsStrings.communityLibrarySubmissionLabel": "Community Library submission", - "CommunityChannelsStrings.compatibleLicensesDescription": "{licenseNames} - All licenses are compatible with Community Library.", - "CommunityChannelsStrings.confirmDistributionRights": "Please confirm you have the right to distribute these resources via Kolibri.", - "CommunityChannelsStrings.confirmReplacementText": "I understand this will replace my earlier submission on the review queue", - "CommunityChannelsStrings.countriesInfoText": "Select one or more countries to label your channel submission with. For example, if your channel contains materials aligned to a national curriculum or regionally-specific content, selecting the relevant countries will help users find it. Otherwise, leave this blank.", - "CommunityChannelsStrings.countryLabel": "Countries", - "CommunityChannelsStrings.descriptionLabel": "Describe what's included in this submission", + "CommunityChannelsStrings.communityLibraryCTADescription": "Vous disposez d'une chaîne qui mérite d'être partagée avec d'autres enseignants et apprenants ? Soumettez-la pour examen via le menu « Partager » afin qu'elle puisse être retrouvée dans Studio.", + "CommunityChannelsStrings.communityLibraryCTATitle": "Contribuer au développement de la bibliothèque communautaire", + "CommunityChannelsStrings.communityLibraryDescription": "Parcourez les chaînes soumises par la communauté et approuvées pour être retrouvées dans Studio. Copiez un jeton pour utiliser une chaîne dans Kolibri.", + "CommunityChannelsStrings.communityLibraryLabel": "Bibliothèque communautaire", + "CommunityChannelsStrings.communityLibrarySubmissionLabel": "Envoi à la bibliothèque communautaire", + "CommunityChannelsStrings.compatibleLicensesDescription": "{licenseNames} - Toutes les licences sont compatibles avec la bibliothèque communautaire.", + "CommunityChannelsStrings.confirmDistributionRights": "Veuillez confirmer que vous êtes autorisé à diffuser ces ressources via Kolibri.", + "CommunityChannelsStrings.confirmReplacementText": "Je comprends que cela remplacera ma demande précédente dans la file d'attente pour l'examen", + "CommunityChannelsStrings.countriesInfoText": "Sélectionnez un ou plusieurs pays pour ajouter une mention à votre chaîne. Par exemple, si votre chaîne contient des ressources conformes à un programme scolaire national ou du contenu spécifique à une région, le fait de sélectionner les pays concernés aidera les utilisateurs à la trouver. Sinon, laissez ce champ vide.", + "CommunityChannelsStrings.countryLabel": "Pays", + "CommunityChannelsStrings.descriptionLabel": "Décrivez le contenu de votre demande", "CommunityChannelsStrings.dismissAction": "Rejeter", - "CommunityChannelsStrings.draftBeingPublishedNotice": "Draft version is being published", - "CommunityChannelsStrings.draftPublishedNotice": "Draft published successfully", - "CommunityChannelsStrings.draftTokenLabel": "Draft token", - "CommunityChannelsStrings.editorLabel": "Editor", - "CommunityChannelsStrings.emptyNotificationsNotice": "You have no notifications at this time.", - "CommunityChannelsStrings.emptyNotificationsWithFiltersNotice": "No notifications match the applied filters.", - "CommunityChannelsStrings.errorLoadingVersions": "Unable to load version history", - "CommunityChannelsStrings.errorSnackbar": "There was an error submitting the channel", - "CommunityChannelsStrings.feedbackNotesLabel": "Notes from the reviewer", - "CommunityChannelsStrings.filterByDateLabel": "Filter by date", - "CommunityChannelsStrings.filterByStatusLabel": "Filter by status", + "CommunityChannelsStrings.draftBeingPublishedNotice": "Une version brouillon est en cours de publication", + "CommunityChannelsStrings.draftPublishedNotice": "Brouillon publié avec succès", + "CommunityChannelsStrings.draftTokenLabel": "Jeton de brouillon", + "CommunityChannelsStrings.editorLabel": "Éditeur", + "CommunityChannelsStrings.emptyNotificationsNotice": "Vous n'avez aucune notification pour le moment.", + "CommunityChannelsStrings.emptyNotificationsWithFiltersNotice": "Aucune notification ne correspond aux filtres appliqués.", + "CommunityChannelsStrings.errorLoadingVersions": "Impossible de charger l'historique des versions", + "CommunityChannelsStrings.errorSnackbar": "Une erreur s'est presentée lors de l'envoi de la chaîne", + "CommunityChannelsStrings.feedbackNotesLabel": "Notes de l'évaluateur", + "CommunityChannelsStrings.filterByDateLabel": "Filtrer par date", + "CommunityChannelsStrings.filterByStatusLabel": "Filtrer par statut", "CommunityChannelsStrings.filterLabel": "Filtrer", - "CommunityChannelsStrings.fixLicensingBeforeSubmission": "Please fix licensing before submitting a new version.", - "CommunityChannelsStrings.flaggedNotification": "Changes required for version {channelVersion}", - "CommunityChannelsStrings.flaggedStatus": "Needs changes", - "CommunityChannelsStrings.getDraftTokenAction": "Copy token for draft channel", - "CommunityChannelsStrings.goToMyChannelsAction": "Go to My channels", - "CommunityChannelsStrings.hideCriteriaAction": "Hide criteria", - "CommunityChannelsStrings.hideVersions": "Hide versions", - "CommunityChannelsStrings.incompatibleLicensesDescription": "\"{licenseNames}\" - this channel cannot be distributed via Kolibri. If you cannot change the license, remove all resources with \"{licenseNames}\" before submitting again.", - "CommunityChannelsStrings.incompatibleLicensesDetected": "Incompatible licenses detected.", + "CommunityChannelsStrings.fixLicensingBeforeSubmission": "Veuillez corriger les problèmes liés à la licence avant de soumettre une nouvelle version.", + "CommunityChannelsStrings.flaggedNotification": "Modifications requises pour la version {channelVersion}", + "CommunityChannelsStrings.flaggedStatus": "À modifier", + "CommunityChannelsStrings.getDraftTokenAction": "Copier le jeton pour la chaîne brouillon", + "CommunityChannelsStrings.goToMyChannelsAction": "Aller à Mes chaînes", + "CommunityChannelsStrings.hideCriteriaAction": "Masquer les critères", + "CommunityChannelsStrings.hideVersions": "Masquer les versions", + "CommunityChannelsStrings.incompatibleLicensesDescription": "« {licenseNames} » - cette chaîne ne peut pas être diffusée via Kolibri. Si vous ne pouvez pas modifier la licence, supprimez toutes les ressources contenant « {licenseNames} » avant de soumettre à nouveau votre demande.", + "CommunityChannelsStrings.incompatibleLicensesDetected": "Licences incompatibles détectées.", "CommunityChannelsStrings.incompleteResourcesDescription1": "Les ressources incomplètes ne seront pas publiées et mises à disposition pour le téléchargement dans Kolibri.", - "CommunityChannelsStrings.incompleteResourcesWarning": "{count, number} {count, plural, one {incomplete resource} other {incomplete resources}}", - "CommunityChannelsStrings.internalNotesLabel": "Admin notes (internal use only)", - "CommunityChannelsStrings.invalidLicensingReason": "Invalid or non-compliant licenses", - "CommunityChannelsStrings.invalidMetadataReason": "Invalid or missing metadata", + "CommunityChannelsStrings.incompleteResourcesWarning": "{count, number} {count, plural, one {ressource incomplète} other {ressources incomplètes}}", + "CommunityChannelsStrings.internalNotesLabel": "Notes de l'administrateur (à usage interne uniquement)", + "CommunityChannelsStrings.invalidLicensingReason": "Licences non valides ou non conformes", + "CommunityChannelsStrings.invalidMetadataReason": "Métadonnées non valides ou manquantes", "CommunityChannelsStrings.languageLabel": "Langue", "CommunityChannelsStrings.languagesLabel": "Langages", - "CommunityChannelsStrings.lessDetailsButton": "Hide details", - "CommunityChannelsStrings.licenseCheckPassed": "License check passed.", + "CommunityChannelsStrings.lessDetailsButton": "Masquer les détails", + "CommunityChannelsStrings.licenseCheckPassed": "Vérification de la licence réussie.", "CommunityChannelsStrings.licensesLabel": "Licences", - "CommunityChannelsStrings.loadError": "There was an error loading channels.", - "CommunityChannelsStrings.loadingVersionHistory": "Loading version history", - "CommunityChannelsStrings.moreDetails": "After your submission is approved, the channel will be available to other Kolibri Studio users on the 'Community Library' page.", - "CommunityChannelsStrings.moreDetailsButton": "More details", - "CommunityChannelsStrings.needKolibriVersionToImport": "You will need Kolibri version 0.19.4 or higher to import channels from the Community Library.", - "CommunityChannelsStrings.needsChangesPrimaryInfo": "Your previously submitted version needs changes. Make sure you have addressed all comments before resubmitting.", + "CommunityChannelsStrings.loadError": "Une erreur s'est produite lors du chargement des chaînes.", + "CommunityChannelsStrings.loadingVersionHistory": "Chargement de l'historique des versions", + "CommunityChannelsStrings.moreDetails": "Une fois votre envoi approuvé, la chaîne sera accessible pour les autres utilisateurs de Kolibri Studio sur la page « Bibliothèque communautaire ».", + "CommunityChannelsStrings.moreDetailsButton": "Plus de détails", + "CommunityChannelsStrings.needKolibriVersionToImport": "Vous devez disposer de la version 0.19.4 ou d'une version ultérieure de Kolibri pour importer des chaînes depuis la bibliothèque communautaire.", + "CommunityChannelsStrings.needsChangesPrimaryInfo": "La version que vous avez soumise précédemment doit être modifiée. Assurez-vous d'avoir pris en compte toutes les remarques avant de la soumettre à nouveau.", "CommunityChannelsStrings.newLabel": "Nouveau", - "CommunityChannelsStrings.newNotificationsNotice": "New notifications available.", + "CommunityChannelsStrings.newNotificationsNotice": "Nouvelles notifications disponibles.", "CommunityChannelsStrings.nextPageAction": "Suivant", - "CommunityChannelsStrings.noCommunityChannels": "No channels have been published to the Community Library yet.", - "CommunityChannelsStrings.noResultsWithFilters": "No channels match the selected filters.", - "CommunityChannelsStrings.noVersionsAvailable": "No version history available", - "CommunityChannelsStrings.nonePrimaryInfo": "We're inviting members of the Kolibri community to submit channels they've created for offline learning in low-resource settings. ", - "CommunityChannelsStrings.notPublishedWarningDescription": "Publish to Studio first, then submit to the Community Library.", - "CommunityChannelsStrings.notPublishedWarningTitle": "This channel isn't published to Kolibri Studio yet", + "CommunityChannelsStrings.noCommunityChannels": "Aucune chaîne n'a encore été publiée dans la bibliothèque communautaire.", + "CommunityChannelsStrings.noResultsWithFilters": "Aucune chaîne ne correspond aux filtres sélectionnés.", + "CommunityChannelsStrings.noVersionsAvailable": "Aucun historique des versions n'est disponible", + "CommunityChannelsStrings.nonePrimaryInfo": "Nous invitons les membres de la communauté Kolibri à nous soumettre les chaînes qu'ils ont créées pour l'apprentissage hors ligne dans des contextes où les ressources sont limitées. ", + "CommunityChannelsStrings.notPublishedWarningDescription": "Publiez d'abord sur Studio, puis soumettez votre contenu à la bibliothèque communautaire.", + "CommunityChannelsStrings.notPublishedWarningTitle": "Cette chaîne n'est pas encore publiée sur Kolibri Studio", "CommunityChannelsStrings.notificationsLabel": "Notifications", - "CommunityChannelsStrings.otherIssuesReason": "Other issues", + "CommunityChannelsStrings.otherIssuesReason": "Autres problèmes", "CommunityChannelsStrings.pageIndicator": "{currentPage} sur {totalPages}", - "CommunityChannelsStrings.pendingStatus": "Submitted", - "CommunityChannelsStrings.portabilityIssuesReason": "Portability problems", - "CommunityChannelsStrings.previewYourDraftTitle": "Preview your draft channel in Kolibri", + "CommunityChannelsStrings.pendingStatus": "Envoyé", + "CommunityChannelsStrings.portabilityIssuesReason": "Problèmes de portabilité", + "CommunityChannelsStrings.previewYourDraftTitle": "Visualisez votre brouillon de chaîne dans Kolibri", "CommunityChannelsStrings.previousPageAction": "Question précédente", - "CommunityChannelsStrings.publicWarningDescription": "It is not possible to submit public channels to the Community Library.", - "CommunityChannelsStrings.publicWarningTitle": "This channel is currently public in the Kolibri Library.", + "CommunityChannelsStrings.publicWarningDescription": "Il n'est pas possible de soumettre des chaînes publiques à la bibliothèque communautaire.", + "CommunityChannelsStrings.publicWarningTitle": "Cette chaîne est actuellement accessible à tous dans la bibliothèque Kolibri.", "CommunityChannelsStrings.publishAction": "Publier", - "CommunityChannelsStrings.publishChannel": "Publish channel", - "CommunityChannelsStrings.publishChannelDescription": "To see your channel in Kolibri, import using the channel token.", - "CommunityChannelsStrings.publishChannelMode": "Publish channel", - "CommunityChannelsStrings.publishDraftDescription": "Your channel will be saved as a draft, allowing you to review and do quality checks on the draft, without altering the current version available for Kolibri users. To see this draft channel in Kolibri, import using the draft channel token.", - "CommunityChannelsStrings.publishDraftMode": "Publish draft channel", + "CommunityChannelsStrings.publishChannel": "Publier la chaîne", + "CommunityChannelsStrings.publishChannelDescription": "Pour afficher votre chaîne dans Kolibri, importez-la à l'aide du jeton de la chaîne.", + "CommunityChannelsStrings.publishChannelMode": "Publier la chaîne", + "CommunityChannelsStrings.publishDraftDescription": "Votre chaîne sera enregistrée sous forme de brouillon, ce qui vous permettra de la consulter et de vérifier sa qualité sans modifier la version actuellement disponible pour les utilisateurs de Kolibri. Pour afficher le brouillon en question dans Kolibri, importez-le à l'aide du jeton de brouillon de chaîne.", + "CommunityChannelsStrings.publishDraftMode": "Publier le brouillon de la chaîne", "CommunityChannelsStrings.publishedVersionLabel": "Version publiée:", - "CommunityChannelsStrings.publishingInfo": "You're publishing: Version {version}", - "CommunityChannelsStrings.publishingMessage": "Channel is being published", - "CommunityChannelsStrings.qualityAssuranceReason": "Quality assurance issues", - "CommunityChannelsStrings.reasonLabel": "Reason: {reason}", - "CommunityChannelsStrings.resubmitAction": "Resubmit", - "CommunityChannelsStrings.resubmitModalBodyFirst": "{channelName} v{version} is also published to the Community Library.", - "CommunityChannelsStrings.resubmitModalBodySecond": "Would you like to resubmit this version with your changes for Community Library review?", - "CommunityChannelsStrings.resubmitModalTitle": "Resubmit channel for Community library review?", + "CommunityChannelsStrings.publishingInfo": "Vous publiez : Version {version}", + "CommunityChannelsStrings.publishingMessage": "La chaîne est en cours de publication", + "CommunityChannelsStrings.qualityAssuranceReason": "Problèmes liés à l'assurance qualité", + "CommunityChannelsStrings.reasonLabel": "Raison : {reason}", + "CommunityChannelsStrings.resubmitAction": "Soumettre à nouveau", + "CommunityChannelsStrings.resubmitModalBodyFirst": "La chaîne {channelName} v{version} est également publiée dans la bibliothèque communautaire.", + "CommunityChannelsStrings.resubmitModalBodySecond": "Souhaitez-vous soumettre à nouveau cette version, avec vos modifications, pour qu'elle soit examinée par la bibliothèque communautaire ?", + "CommunityChannelsStrings.resubmitModalTitle": "Soumettre à nouveau la chaîne pour qu'elle soit examinée par la bibliothèque communautaire ?", "CommunityChannelsStrings.resultsText": "{count, plural, =1 {# result found} other {# results found}}", "CommunityChannelsStrings.retry": "Réessayer", "CommunityChannelsStrings.reviewAction": "Passer en revue", - "CommunityChannelsStrings.saveDraft": "Save draft", + "CommunityChannelsStrings.saveDraft": "Enregistrer le brouillon", "CommunityChannelsStrings.searchLabel": "Rechercher", - "CommunityChannelsStrings.searchNotificationsLabel": "Search notifications", - "CommunityChannelsStrings.seeAllVersions": "See all versions", + "CommunityChannelsStrings.searchNotificationsLabel": "Rechercher des notifications", + "CommunityChannelsStrings.seeAllVersions": "Voir toutes les versions", "CommunityChannelsStrings.showMore": "Voir plus", - "CommunityChannelsStrings.showOlderAction": "Show older", - "CommunityChannelsStrings.specialPermissionsDetected": "Special Permissions licenses detected", - "CommunityChannelsStrings.submissionCreationNotification": "Your submission to the Community Library was successful and is now under review.", - "CommunityChannelsStrings.submissionNotesLabel": "Submission notes", - "CommunityChannelsStrings.submissionNotification": "{author} ({userType}) submitted {channelVersion}", - "CommunityChannelsStrings.submitButton": "Submit for review", - "CommunityChannelsStrings.submitToCommunityLibrary": "Submit to Community Library", - "CommunityChannelsStrings.submittedPrimaryInfo": "A previous version is still pending review. Reviewers will see the most recent submission by default.", - "CommunityChannelsStrings.submittedSnackbar": "Channel submitted to Community Library", - "CommunityChannelsStrings.submittingSnackbar": "Submitting channel to Community Library...", - "CommunityChannelsStrings.supersededStatus": "Superseded", + "CommunityChannelsStrings.showOlderAction": "Afficher les plus anciens éléments", + "CommunityChannelsStrings.specialPermissionsDetected": "Licences avec autorisations spéciales détectées", + "CommunityChannelsStrings.submissionCreationNotification": "Votre demande auprès de la bibliothèque communautaire a été enregistrée et est actuellement en cours d'examen.", + "CommunityChannelsStrings.submissionNotesLabel": "Notes annexes", + "CommunityChannelsStrings.submissionNotification": "{author} ({userType}) a soumis {channelVersion}", + "CommunityChannelsStrings.submitButton": "Soumettre pour examen", + "CommunityChannelsStrings.submitToCommunityLibrary": "Soumettre à la bibliothèque communautaire", + "CommunityChannelsStrings.submittedPrimaryInfo": "Une version antérieure est toujours en attente d'examen. Les évaluateurs verront par défaut la dernière version soumise.", + "CommunityChannelsStrings.submittedSnackbar": "Chaîne envoyée à la bibliothèque communautaire", + "CommunityChannelsStrings.submittingSnackbar": "Envoi de la chaîne à la bibliothèque communautaire...", + "CommunityChannelsStrings.supersededStatus": "Remplacé", "CommunityChannelsStrings.thisMonthLabel": "Ce mois-ci", - "CommunityChannelsStrings.thisWeekLabel": "This week", - "CommunityChannelsStrings.thisYearLabel": "This year", - "CommunityChannelsStrings.todayLabel": "Today", - "CommunityChannelsStrings.unreadNotificationsLabel": "Unread", + "CommunityChannelsStrings.thisWeekLabel": "Cette semaine", + "CommunityChannelsStrings.thisYearLabel": "Cette année", + "CommunityChannelsStrings.todayLabel": "Aujourd’hui", + "CommunityChannelsStrings.unreadNotificationsLabel": "Non lu", "CommunityChannelsStrings.versionDescriptionLabel": "Description de la version", "CommunityChannelsStrings.versionLabel": "Version {version}", "CommunityChannelsStrings.versionNotesLabel": "Décrivez les nouveautés de cette version de la chaîne", - "CommunityChannelsStrings.viewCriteriaAction": "View criteria", + "CommunityChannelsStrings.viewCriteriaAction": "Voir les critères", "CommunityChannelsStrings.viewMoreAction": "Afficher davantage", - "CommunityChannelsStrings.whatCanYouDoHere": "What you can do here", - "CommunityChannelsStrings.whatCanYouDoHereItem1": "Browse channels by country, category, and language", - "CommunityChannelsStrings.whatCanYouDoHereItem2": "Copy a channel token to use in Kolibri", - "CommunityChannelsStrings.whatCanYouDoHereItem3": "View channel details including description and metadata", - "CommunityChannelsStrings.whatIsCommunityLibrary": "What is the Community Library?", + "CommunityChannelsStrings.whatCanYouDoHere": "Ce que vous pouvez faire ici", + "CommunityChannelsStrings.whatCanYouDoHereItem1": "Parcourir les chaînes par pays, catégorie et langue", + "CommunityChannelsStrings.whatCanYouDoHereItem2": "Copier un jeton de chaîne à utiliser dans Kolibri", + "CommunityChannelsStrings.whatCanYouDoHereItem3": "Voir les détails de la chaîne, y compris la description et les métadonnées", + "CommunityChannelsStrings.whatIsCommunityLibrary": "Qu'est-ce que la bibliothèque communautaire ?", "CommunityStandardsModal.communityStandardsHeader": "Règles de la communauté", "CommunityStandardsModal.coreValuesLink": "En savoir plus sur les valeurs fondamentales de Learning Equality", "CommunityStandardsModal.description": "Learning Equality est une organisation à but non lucratif qui se consacre à fournir un accès équitable à des expériences éducatives de qualité. En plus des Valeurs fondamentales que nous revendiquons, ces Normes communautaires visent à favoriser un environnement valorisant et inclusif pour nos utilisateurs.", @@ -1313,7 +1313,7 @@ "ImportFromChannelsModal.reviewAction": "Passer en revue", "ImportFromChannelsModal.reviewTitle": "Sélection de ressources", "InfoModal.close": "Fermer", - "InfoModal.open": "More information about licenses", + "InfoModal.open": "Plus d'informations sur les licences", "InheritAncestorMetadataModal.applyResourceDetailsTitle": "Appliquer les détails du dossier \"{folder}\"", "InheritAncestorMetadataModal.cancelAction": "Annuler", "InheritAncestorMetadataModal.categories": "Catégories : {categories}", @@ -1349,45 +1349,45 @@ "MasteryCriteriaGoal.labelText": "Objectif", "MasteryCriteriaMofNFields.mHint": "Bonnes réponses nécessaires", "MasteryCriteriaMofNFields.nHint": "Réponses récentes", - "MathLiveA11yStrings.accented": "accented", - "MathLiveA11yStrings.array": "array", - "MathLiveA11yStrings.box": "box", - "MathLiveA11yStrings.chemicalFormula": "chemical formula", + "MathLiveA11yStrings.accented": "accentué", + "MathLiveA11yStrings.array": "tableau", + "MathLiveA11yStrings.box": "encadré", + "MathLiveA11yStrings.chemicalFormula": "formule chimique", "MathLiveA11yStrings.crossOut": "barrer", "MathLiveA11yStrings.deleted": "supprimé: ", - "MathLiveA11yStrings.delimiter": "delimiter", - "MathLiveA11yStrings.denominator": "denominator", - "MathLiveA11yStrings.endOf": "{spokenText}; end of {relationName}", - "MathLiveA11yStrings.endOfMathfield": "{spokenText}; end of mathfield", + "MathLiveA11yStrings.delimiter": "séparateur", + "MathLiveA11yStrings.denominator": "dénominateur", + "MathLiveA11yStrings.endOf": "{spokenText}; fin de {relationName}", + "MathLiveA11yStrings.endOfMathfield": "{spokenText}; fin du champ mathématique", "MathLiveA11yStrings.error": "erreur", - "MathLiveA11yStrings.extensibleSymbol": "extensible symbol", - "MathLiveA11yStrings.first": "first", + "MathLiveA11yStrings.extensibleSymbol": "symbole extensible", + "MathLiveA11yStrings.first": "premier", "MathLiveA11yStrings.fraction": "fraction", - "MathLiveA11yStrings.group": "group", + "MathLiveA11yStrings.group": "groupe", "MathLiveA11yStrings.index": "index", "MathLiveA11yStrings.latex": "LaTeX", "MathLiveA11yStrings.line": "ligne", - "MathLiveA11yStrings.mathField": "math field", - "MathLiveA11yStrings.mathfield": "math field", - "MathLiveA11yStrings.numerator": "numerator", - "MathLiveA11yStrings.operator": "operator", - "MathLiveA11yStrings.outOf": "out of {relationName};", - "MathLiveA11yStrings.overUnder": "over-under", + "MathLiveA11yStrings.mathField": "champ mathématique", + "MathLiveA11yStrings.mathfield": "champ mathématique", + "MathLiveA11yStrings.numerator": "numérateur", + "MathLiveA11yStrings.operator": "opérateur", + "MathLiveA11yStrings.outOf": "sur {relationName};", + "MathLiveA11yStrings.overUnder": "sur-sous", "MathLiveA11yStrings.parent": "parent", - "MathLiveA11yStrings.placeholder": "placeholder", - "MathLiveA11yStrings.prompt": "prompt", - "MathLiveA11yStrings.radicand": "radicand", - "MathLiveA11yStrings.rule": "rule", + "MathLiveA11yStrings.placeholder": "espace réservé", + "MathLiveA11yStrings.prompt": "invite", + "MathLiveA11yStrings.radicand": "radicande", + "MathLiveA11yStrings.rule": "règle", "MathLiveA11yStrings.selected": "sélectionné: ", - "MathLiveA11yStrings.space": "space", - "MathLiveA11yStrings.spacing": "spacing", + "MathLiveA11yStrings.space": "espace", + "MathLiveA11yStrings.spacing": "espacement", "MathLiveA11yStrings.squareRoot": "racine carrée", - "MathLiveA11yStrings.startOf": "start of {relationName}: ", - "MathLiveA11yStrings.subscript": "subscript", - "MathLiveA11yStrings.subscriptSuperscript": "subscript-superscript", + "MathLiveA11yStrings.startOf": "début de {relationName} : ", + "MathLiveA11yStrings.subscript": "indice", + "MathLiveA11yStrings.subscriptSuperscript": "indice-exposant", "MathLiveA11yStrings.superscript": "exposant", - "MathLiveA11yStrings.superscriptAndSubscript": "superscript and subscript", - "MathLiveA11yStrings.text": "text", + "MathLiveA11yStrings.superscriptAndSubscript": "indice et exposant", + "MathLiveA11yStrings.text": "texte", "MessageLayout.backToLogin": "Continuer vers la page de connexion", "MoveModal.addTopic": "Ajouter un nouveau dossier", "MoveModal.cancel": "Annuler", @@ -1428,7 +1428,7 @@ "PrivacyPolicyModal.updatedPrivacyHeader": "Politique de confidentialité mise à jour", "ProgressBar.progressText": "{percent} %", "ProgressModal.defaultErrorText": "Échec de la dernière tentative de publication", - "ProgressModal.draftHeader": "Saving draft...", + "ProgressModal.draftHeader": "Enregistrement du brouillon...", "ProgressModal.lastPublished": "Publié le {last_published}", "ProgressModal.publishHeader": "Publication de la chaîne", "ProgressModal.syncError": "Échec de la dernière tentative de synchronisation", @@ -1476,7 +1476,7 @@ "RemoveChannelFromListModal.removeTitle": "Retirer de la liste de chaînes", "RemoveChannelModal.cancel": "Annuler", "RemoveChannelModal.deleteChannel": "Supprimer la chaîne", - "RemoveChannelModal.deleteChannelWithCLWarning": "This channel has been shared with the Community Library. Deleting it here will not remove it from the Community Library — it may still be approved or remain available there.", + "RemoveChannelModal.deleteChannelWithCLWarning": "Cette chaîne a été partagée avec la bibliothèque communautaire. Le fait de la supprimer ici ne la supprimera pas de la bibliothèque communautaire : elle pourra toujours y être approuvée ou rester disponible.", "RemoveChannelModal.deletePrompt": "Cette chaîne sera définitivement supprimée. Cette action est irréversible.", "RemoveChannelModal.deleteTitle": "Supprimer cette chaîne", "RemoveChannelModal.removeBtn": "Supprimer", @@ -1669,11 +1669,11 @@ "SearchRecommendationsStrings.tooAdvancedForLearnersLabel": "Trop avancé pour le niveau de connaissances des apprenants que je recherche", "SearchRecommendationsStrings.tooBasicForLearnersLabel": "Trop basique pour le niveau de connaissances des apprenants que je recherche", "SearchRecommendationsStrings.tryAgainLink": "Réessayer", - "SearchRecommendationsStrings.trySearchRecommendationsHeader": "Try our new 'Recommendations' feature!", - "SearchRecommendationsStrings.trySearchRecommendationsText": "Based on the title and description of the folder you are working on, we'll suggest relevant resources from the Kolibri Library. Choose 'Import from channels' in any folder of your channels to see the recommendations.", + "SearchRecommendationsStrings.trySearchRecommendationsHeader": "Essayez notre nouvelle fonctionnalité de « Recommandations » !", + "SearchRecommendationsStrings.trySearchRecommendationsText": "En fonction du titre et de la description du dossier sur lequel vous travaillez, nous vous proposerons des ressources pertinentes issues de la bibliothèque Kolibri. Sélectionnez « Importer depuis les chaînes » dans n'importe quel dossier de vos chaînes pour afficher les recommandations.", "SearchRecommendationsStrings.undoAction": "Annuler", "SearchRecommendationsStrings.viewMoreLink": "Afficher davantage", - "SearchRecommendationsStrings.viewRecommendationsButton": "View recommendations", + "SearchRecommendationsStrings.viewRecommendationsButton": "Voir les recommandations", "SearchResultsList.failedToLoad": "Impossible de charger les résultats de la recherche", "SearchResultsList.resultsPerPageLabel": "Résultats par page", "SearchResultsList.saveSearchAction": "Enregistrer la recherche", @@ -1731,14 +1731,14 @@ "StudioChannelCard.details": "Détails", "StudioChannelCard.lastPublished": "Publié le {last_published}", "StudioChannelCard.lastUpdated": "Mis à jour {updated}", - "StudioChannelCard.multipleCountries": "Multiple countries", + "StudioChannelCard.multipleCountries": "Plusieurs pays", "StudioChannelCard.resourceCount": "{count, plural, one {}\n =1 {# ressource}\n other {# ressources}}", "StudioChannelCard.selectChannel": "Sélectionner {name}", "StudioChannelCard.unpublishedText": "Non publiée", "StudioChannelsPage.invitations": "Vous avez {count, plural, one {}\n =1 {# invitation}\n other {# invitations}}", "StudioChannelsPage.noChannelsFound": "Aucune chaîne trouvée", "StudioCollectionsTable.aboutChannelSets": "À propos des recueils", - "StudioCollectionsTable.aboutChannelSetsLink": "Learn more about collections", + "StudioCollectionsTable.aboutChannelSetsLink": "En savoir plus sur les collections", "StudioCollectionsTable.addChannelSetTitle": "Nouveau recueil", "StudioCollectionsTable.cancel": "Annuler", "StudioCollectionsTable.cancelButtonLabel": "Fermer", @@ -1746,20 +1746,20 @@ "StudioCollectionsTable.channelSetsDescriptionText": "Un recueil contient plusieurs chaînes de Kolibri Studio pouvant être importées en une seule fois dans Kolibri avec un seul jeton de recueil.", "StudioCollectionsTable.channelSetsDisclaimer": "Seule la version 0.12.0 ou supérieure de Kolibri permet d'importer des recueils de chaînes.", "StudioCollectionsTable.channelSetsInstructionsText": "Vous pouvez créer un recueil en sélectionnant les chaînes que vous souhaitez importer ensemble.", - "StudioCollectionsTable.collectionDeleted": "Collection deleted", + "StudioCollectionsTable.collectionDeleted": "Collection supprimée", "StudioCollectionsTable.copiedTokenId": "Jeton copié", "StudioCollectionsTable.copyFailed": "Échec de la copie", - "StudioCollectionsTable.copyToken": "Copy token", + "StudioCollectionsTable.copyToken": "Copier le jeton", "StudioCollectionsTable.delete": "Supprimer le recueil", "StudioCollectionsTable.deleteChannelSetText": "Êtes-vous sûr de vouloir supprimer ce recueil ?", "StudioCollectionsTable.deleteChannelSetTitle": "Supprimer le recueil", - "StudioCollectionsTable.deleteError": "Error deleting collection", + "StudioCollectionsTable.deleteError": "Erreur lors de la suppression de la collection", "StudioCollectionsTable.edit": "Modifier le recueil", "StudioCollectionsTable.noChannelSetsFound": "Il est possible de regrouper plusieurs chaînes pour constituer un recueil. Vous pourrez ensuite importer tout le recueil dans Kolibri en une seule fois à l'aide d'un jeton de recueil.", "StudioCollectionsTable.options": "Options", "StudioCollectionsTable.pageTitle": "Recueils", "StudioCollectionsTable.saving": "Enregistrement en cours", - "StudioCollectionsTable.tableCaption": "List of collections", + "StudioCollectionsTable.tableCaption": "Liste des collections", "StudioCollectionsTable.title": "Nom du recueil", "StudioCollectionsTable.token": "ID du jeton", "StudioCopyToken.copiedTokenId": "Jeton copié", @@ -1816,32 +1816,32 @@ "StudioStarredChannels.editChannel": "Modifier les détails de la chaîne", "StudioStarredChannels.goToWebsite": "Aller sur le site web source", "StudioStarredChannels.moreOptions": "Plus d'options", - "StudioStarredChannels.removeChannel": "Remove channel", - "StudioStarredChannels.title": "Starred channels", + "StudioStarredChannels.removeChannel": "Supprimer la chaîne", + "StudioStarredChannels.title": "Chaînes favorites", "StudioStarredChannels.viewContent": "Afficher la chaîne sur Kolibri", "StudioTree.missingTitle": "Titre manquant", "StudioTree.optionsTooltip": "Options", "StudioViewOnlyChannels.copyToken": "Copier le jeton de la chaîne", "StudioViewOnlyChannels.goToWebsite": "Aller sur le site web source", "StudioViewOnlyChannels.moreOptions": "Plus d'options", - "StudioViewOnlyChannels.removeChannel": "Remove channel", - "StudioViewOnlyChannels.title": "View-only channels", + "StudioViewOnlyChannels.removeChannel": "Supprimer la chaîne", + "StudioViewOnlyChannels.title": "Chaînes en lecture seule", "StudioViewOnlyChannels.viewContent": "Afficher la chaîne sur Kolibri", - "SubscriptionCard.annualPrice": "${price, number}/year", - "SubscriptionCard.cancelNotice": "Your subscription will expire on {date, date, medium}. Storage will be removed after that.", + "SubscriptionCard.annualPrice": "{price, number} $/an", + "SubscriptionCard.cancelNotice": "Votre abonnement expirera le {date, date, medium}. Vos données seront supprimées après cela.", "SubscriptionCard.dismiss": "Rejeter", - "SubscriptionCard.genericError": "There was a problem connecting to the payment provider. Please try again.", - "SubscriptionCard.instantUpgrade": "Upgrade storage now", - "SubscriptionCard.manageSubscription": "Manage subscription", - "SubscriptionCard.renewalNotice": "Your subscription will automatically renew on {date, date, medium}.", - "SubscriptionCard.storageAmount": "Storage (GB)", - "SubscriptionCard.storageIncluded": "{size} included in your subscription", - "SubscriptionCard.storageRange": "Enter a value between 1 and 50", - "SubscriptionCard.subscriptionActive": "Storage subscription active", - "SubscriptionCard.subscriptionCanceling": "Subscription cancelled", - "SubscriptionCard.upgradeDescription": "Purchase additional storage at $15/GB per year.", - "SubscriptionCard.upgradeNow": "Upgrade now", - "SubscriptionCard.upgradeSuccess": "Storage increased to {size}", + "SubscriptionCard.genericError": "Une erreur s'est produite lors de la connexion au prestataire de paiement. Veuillez réessayer.", + "SubscriptionCard.instantUpgrade": "Mettez à niveau votre stockage dès maintenant", + "SubscriptionCard.manageSubscription": "Gérer l'abonnement", + "SubscriptionCard.renewalNotice": "Votre abonnement sera automatiquement renouvelé le {date, date, medium}.", + "SubscriptionCard.storageAmount": "Stockage (Go)", + "SubscriptionCard.storageIncluded": "{size} compris dans votre abonnement", + "SubscriptionCard.storageRange": "Saisir une valeur comprise entre 1 et 50", + "SubscriptionCard.subscriptionActive": "Abonnement de stockage actif", + "SubscriptionCard.subscriptionCanceling": "Abonnement résilié", + "SubscriptionCard.upgradeDescription": "Achetez de l'espace de stockage supplémentaire au prix de 15 $ par Go et par an.", + "SubscriptionCard.upgradeNow": "Mettez à niveau dès maintenant", + "SubscriptionCard.upgradeSuccess": "Stockage augmenté à {size}", "SubtitlesList.acceptedFormatsTooltip": "Formats supportés : {extensions}", "SubtitlesList.addSubtitleText": "Ajouter des légendes", "SubtitlesList.subtitlesHeader": "Légendes et sous-titres", @@ -1969,95 +1969,95 @@ "ThumbnailGenerator.generatedDefaultFilename": "Vignette générée", "ThumbnailGenerator.thumbnailGenerationFailedHeader": "Impossible de générer une vignette", "ThumbnailGenerator.thumbnailGenerationFailedText": "Une erreur s'est produite lors de la génération d'une vignette", - "TipTapEditorStrings.TipTapEditorLabel": "text editor - Press Enter to start editing", - "TipTapEditorStrings.TipTapViewerLabel": "text editor content", - "TipTapEditorStrings.addLink": "Add link", - "TipTapEditorStrings.alignLeft": "Align left", - "TipTapEditorStrings.alignRight": "Align right", - "TipTapEditorStrings.altTextDescription": "Alt text is necessary to enable visually impaired learners to answer questions, and it also displays when the image fails to load", - "TipTapEditorStrings.altTextLabel": "Alt text (Optional)", - "TipTapEditorStrings.altTextPlaceholder": "Describe your image...", - "TipTapEditorStrings.bold": "Strong", - "TipTapEditorStrings.bulletList": "Bullet list", + "TipTapEditorStrings.TipTapEditorLabel": "éditeur de texte - Appuyez sur Entrée pour commencer à modifier", + "TipTapEditorStrings.TipTapViewerLabel": "contenu de l'éditeur de texte", + "TipTapEditorStrings.addLink": "Ajouter un lien", + "TipTapEditorStrings.alignLeft": "Aligner à gauche", + "TipTapEditorStrings.alignRight": "Aligner à droite", + "TipTapEditorStrings.altTextDescription": "Le texte alternatif est indispensable pour permettre aux apprenants malvoyants de répondre aux questions ; il s'affiche également lorsque l'image ne parvient pas à se charger", + "TipTapEditorStrings.altTextLabel": "Texte alternatif (facultatif)", + "TipTapEditorStrings.altTextPlaceholder": "Décrivez votre image...", + "TipTapEditorStrings.bold": "Gras", + "TipTapEditorStrings.bulletList": "Liste à puces", "TipTapEditorStrings.cancel": "Annuler", - "TipTapEditorStrings.cancelLoading": "Cancel loading", - "TipTapEditorStrings.clearFormatting": "Clear formatting", - "TipTapEditorStrings.clipboardAccessFailed": "Clipboard access failed. Try copying again.", + "TipTapEditorStrings.cancelLoading": "Annuler le chargement", + "TipTapEditorStrings.clearFormatting": "Supprimer la mise en forme", + "TipTapEditorStrings.clipboardAccessFailed": "Échec de l'accès au presse-papiers. Veuillez réessayer.", "TipTapEditorStrings.close": "Fermer", - "TipTapEditorStrings.closeModal": "Close modal", - "TipTapEditorStrings.codeBlock": "Code block", - "TipTapEditorStrings.collapseFormattingBar": "Collapse formatting bar", + "TipTapEditorStrings.closeModal": "Fermez la fenêtre", + "TipTapEditorStrings.codeBlock": "Bloc de code", + "TipTapEditorStrings.collapseFormattingBar": "Réduire la barre de mise en forme", "TipTapEditorStrings.copy": "Copie", - "TipTapEditorStrings.copyAndPasteActions": "Copy and paste actions", - "TipTapEditorStrings.copyLink": "Copy link", - "TipTapEditorStrings.decreaseFormatSize": "Decrease format size", + "TipTapEditorStrings.copyAndPasteActions": "Actions de copier-coller", + "TipTapEditorStrings.copyLink": "Copier le lien", + "TipTapEditorStrings.decreaseFormatSize": "Diminuer la taille du format", "TipTapEditorStrings.defaultImageName": "Image", "TipTapEditorStrings.edit": "Modifier", - "TipTapEditorStrings.editImage": "Edit image", - "TipTapEditorStrings.editLink": "Edit link", - "TipTapEditorStrings.editorControls": "Editor controls", - "TipTapEditorStrings.errorUploadingImage": "Error uploading image", - "TipTapEditorStrings.expandFormattingBar": "Expand formatting bar", - "TipTapEditorStrings.failedToProcessImage": "Failed to process the image file.", + "TipTapEditorStrings.editImage": "Modifier l'image", + "TipTapEditorStrings.editLink": "Modifier le lien", + "TipTapEditorStrings.editorControls": "Commandes de l'éditeur", + "TipTapEditorStrings.errorUploadingImage": "Erreur lors du chargement de l'image", + "TipTapEditorStrings.expandFormattingBar": "Étendre la barre de formatage", + "TipTapEditorStrings.failedToProcessImage": "Échec du traitement du fichier image.", "TipTapEditorStrings.fileSizeUnit": "Mo.", - "TipTapEditorStrings.fileTooLarge": "File is too large. Maximum size is ", - "TipTapEditorStrings.formatHeader1": "Header 1", - "TipTapEditorStrings.formatHeader2": "Header 2", - "TipTapEditorStrings.formatHeader3": "Header 3", + "TipTapEditorStrings.fileTooLarge": "Le fichier est trop volumineux. La taille maximale est de ", + "TipTapEditorStrings.formatHeader1": "En-tête 1", + "TipTapEditorStrings.formatHeader2": "En-tête 2", + "TipTapEditorStrings.formatHeader3": "En-tête 3", "TipTapEditorStrings.formatNormal": "Normal", - "TipTapEditorStrings.formatOptions": "Format options", - "TipTapEditorStrings.formatSize": "Format size", + "TipTapEditorStrings.formatOptions": "Options de format", + "TipTapEditorStrings.formatSize": "Taille du format", "TipTapEditorStrings.formatSmall": "Petite", - "TipTapEditorStrings.formulasMenuTitle": "Special Characters", - "TipTapEditorStrings.goToLink": "Go to link", - "TipTapEditorStrings.historyActions": "History actions", - "TipTapEditorStrings.imageDropZoneText": "Drag and drop an image here or upload manually", - "TipTapEditorStrings.imagePreview": "Image preview", - "TipTapEditorStrings.increaseFormatSize": "Increase format size", + "TipTapEditorStrings.formulasMenuTitle": "Caractères spéciaux", + "TipTapEditorStrings.goToLink": "Cliquez sur le lien", + "TipTapEditorStrings.historyActions": "Actions de l'historique", + "TipTapEditorStrings.imageDropZoneText": "Faites glisser et déposez une image ici ou chargez-la manuellement", + "TipTapEditorStrings.imagePreview": "Aperçu de l'image", + "TipTapEditorStrings.increaseFormatSize": "Augmenter la taille du format", "TipTapEditorStrings.insert": "Insérer", - "TipTapEditorStrings.insertContent": "Insert content", - "TipTapEditorStrings.insertContentMenu": "Insert content menu", - "TipTapEditorStrings.insertContentOption": "Insert content option", - "TipTapEditorStrings.insertImage": "Insert image", - "TipTapEditorStrings.insertLink": "Insert link", - "TipTapEditorStrings.insertTools": "Insert tools", - "TipTapEditorStrings.invalidFileType": "Invalid file type. Please use: ", - "TipTapEditorStrings.italic": "Italic", - "TipTapEditorStrings.link": "Link", - "TipTapEditorStrings.linkActions": "Link actions", - "TipTapEditorStrings.listFormatting": "List formatting", - "TipTapEditorStrings.loadingFormulas": "Loading math editor", - "TipTapEditorStrings.mathFormula": "Math formula", + "TipTapEditorStrings.insertContent": "Insérer du contenu", + "TipTapEditorStrings.insertContentMenu": "Insérer le menu de contenu", + "TipTapEditorStrings.insertContentOption": "Insérer l'option de contenu", + "TipTapEditorStrings.insertImage": "Insérer une image", + "TipTapEditorStrings.insertLink": "Insérer un lien", + "TipTapEditorStrings.insertTools": "Insérer des outils", + "TipTapEditorStrings.invalidFileType": "Type de fichier non valide. Veuillez utiliser : ", + "TipTapEditorStrings.italic": "Italique", + "TipTapEditorStrings.link": "Lien", + "TipTapEditorStrings.linkActions": "Actions sur les liens", + "TipTapEditorStrings.listFormatting": "Formatage des listes", + "TipTapEditorStrings.loadingFormulas": "Chargement de l'éditeur mathématique", + "TipTapEditorStrings.mathFormula": "Formule mathématique", "TipTapEditorStrings.moreButtonText": "Plus", - "TipTapEditorStrings.multipleFilesDroppedWarning": "Multiple files were dropped. Only the first file has been selected.", - "TipTapEditorStrings.noEnoughStorageSpace": "Not enough storage space available. File size exceeds remaining storage.", - "TipTapEditorStrings.noFileProvided": "No file provided.", - "TipTapEditorStrings.numberedList": "Numbered list", - "TipTapEditorStrings.opensInNewTab": "(opens in new tab)", - "TipTapEditorStrings.paste": "Paste", - "TipTapEditorStrings.pasteOptions": "Paste options", - "TipTapEditorStrings.pasteOptionsMenu": "Paste options menu", - "TipTapEditorStrings.pasteWithoutFormatting": "Paste without formatting", - "TipTapEditorStrings.redo": "Redo", + "TipTapEditorStrings.multipleFilesDroppedWarning": "Plusieurs fichiers ont été déposés. Seul le premier fichier a été sélectionné.", + "TipTapEditorStrings.noEnoughStorageSpace": "L'espace de stockage disponible est insuffisant. La taille du fichier dépasse l'espace de stockage restant.", + "TipTapEditorStrings.noFileProvided": "Aucun fichier fourni.", + "TipTapEditorStrings.numberedList": "Liste numérotée", + "TipTapEditorStrings.opensInNewTab": "(s'ouvre dans un nouvel onglet)", + "TipTapEditorStrings.paste": "Coller", + "TipTapEditorStrings.pasteOptions": "Options de collage", + "TipTapEditorStrings.pasteOptionsMenu": "Menu des options de collage", + "TipTapEditorStrings.pasteWithoutFormatting": "Coller sans formatage", + "TipTapEditorStrings.redo": "Refaire", "TipTapEditorStrings.remove": "Supprimer", - "TipTapEditorStrings.removeImage": "Remove image", - "TipTapEditorStrings.removeLink": "Remove link", + "TipTapEditorStrings.removeImage": "Supprimer l'image", + "TipTapEditorStrings.removeLink": "Supprimer le lien", "TipTapEditorStrings.replaceFile": "Remplacer le fichier", "TipTapEditorStrings.save": "Enregistrer", "TipTapEditorStrings.saveChanges": "Enregistrer les modifications", - "TipTapEditorStrings.scriptFormatting": "Script formatting", + "TipTapEditorStrings.scriptFormatting": "Formatage du script", "TipTapEditorStrings.selectFile": "Sélectionner un fichier", - "TipTapEditorStrings.selectFileToUpload": "Select file to upload", - "TipTapEditorStrings.strikethrough": "Strikethrough", - "TipTapEditorStrings.subscript": "Subscript", + "TipTapEditorStrings.selectFileToUpload": "Sélectionnez le fichier à télécharger", + "TipTapEditorStrings.strikethrough": "Barré", + "TipTapEditorStrings.subscript": "Indice", "TipTapEditorStrings.superscript": "Exposant", "TipTapEditorStrings.supportedFileTypes": "Types de fichiers supportés : { extensions }", - "TipTapEditorStrings.text": "Text", - "TipTapEditorStrings.textFormatOptions": "Text format options", - "TipTapEditorStrings.textFormattingOptions": "Text formatting options", - "TipTapEditorStrings.textFormattingToolbar": "Text formatting toolbar", - "TipTapEditorStrings.textStyleFormatting": "Text style formatting", - "TipTapEditorStrings.underline": "Underline", + "TipTapEditorStrings.text": "Texte", + "TipTapEditorStrings.textFormatOptions": "Options de format de texte", + "TipTapEditorStrings.textFormattingOptions": "Options de mise en forme du texte", + "TipTapEditorStrings.textFormattingToolbar": "Barre d'outils de mise en forme du texte", + "TipTapEditorStrings.textStyleFormatting": "Formatage du style du texte", + "TipTapEditorStrings.underline": "Souligner", "TipTapEditorStrings.undo": "Annuler", "TipTapEditorStrings.uploadImage": "Téléverser une image", "TitleStrings.catalogTitle": "Catalogue de la bibliothèque de contenus Kolibri", @@ -2098,8 +2098,8 @@ "TreeViewBase.publishButtonTitle": "Rendre cette chaîne disponible pour importation dans Kolibri", "TreeViewBase.shareChannel": "Partager la chaîne", "TreeViewBase.shareMenuButton": "Partager", - "TreeViewBase.shareToken": "Share token", - "TreeViewBase.submitToCommunityLibrary": "Submit to Community Library", + "TreeViewBase.shareToken": "Partager le jeton", + "TreeViewBase.submitToCommunityLibrary": "Soumettre à la bibliothèque communautaire", "TreeViewBase.syncChannel": "Synchroniser les ressources", "TreeViewBase.viewOnly": "Lecture seule", "Uploader.closeButtonLabel": "OK", @@ -2176,4 +2176,4 @@ "sharedVue.masteryModelRequired": "Le critère de maîtrise est obligatoire", "sharedVue.shortActivityLteThirty": "La valeur doit être égale ou inférieure à 30", "sharedVue.titleRequired": "Le titre est obligatoire" -} +} \ No newline at end of file diff --git a/contentcuration/locale/fr_FR/LC_MESSAGES/django.po b/contentcuration/locale/fr_FR/LC_MESSAGES/django.po index 32eca8eed2..309db530dd 100644 --- a/contentcuration/locale/fr_FR/LC_MESSAGES/django.po +++ b/contentcuration/locale/fr_FR/LC_MESSAGES/django.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: kolibri-studio\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-04-16 00:57+0000\n" -"PO-Revision-Date: 2026-04-20 19:33\n" +"PO-Revision-Date: 2026-04-24 16:20\n" "Last-Translator: \n" "Language-Team: French\n" "Language: fr_FR\n" @@ -740,3 +740,4 @@ msgstr "La version de l’API est indisponible" #: kolibri_public/views_v1.py:170 msgid "No channel matching {} found" msgstr "Aucune chaîne correspondant à {} trouvée" + diff --git a/contentcuration/locale/hi_IN/LC_MESSAGES/contentcuration-messages.json b/contentcuration/locale/hi_IN/LC_MESSAGES/contentcuration-messages.json index f009ba5071..ea81e3a301 100644 --- a/contentcuration/locale/hi_IN/LC_MESSAGES/contentcuration-messages.json +++ b/contentcuration/locale/hi_IN/LC_MESSAGES/contentcuration-messages.json @@ -2176,4 +2176,4 @@ "sharedVue.masteryModelRequired": "Mastery is required", "sharedVue.shortActivityLteThirty": "Value must be equal or less than 30", "sharedVue.titleRequired": "Title is required" -} +} \ No newline at end of file diff --git a/contentcuration/locale/hi_IN/LC_MESSAGES/django.po b/contentcuration/locale/hi_IN/LC_MESSAGES/django.po index 8ff10382a1..c8729a9817 100644 --- a/contentcuration/locale/hi_IN/LC_MESSAGES/django.po +++ b/contentcuration/locale/hi_IN/LC_MESSAGES/django.po @@ -723,3 +723,4 @@ msgstr "" #: kolibri_public/views_v1.py:170 msgid "No channel matching {} found" msgstr "" + diff --git a/contentcuration/locale/pt_BR/LC_MESSAGES/contentcuration-messages.json b/contentcuration/locale/pt_BR/LC_MESSAGES/contentcuration-messages.json index 311a2a9c95..2a5a3904fb 100644 --- a/contentcuration/locale/pt_BR/LC_MESSAGES/contentcuration-messages.json +++ b/contentcuration/locale/pt_BR/LC_MESSAGES/contentcuration-messages.json @@ -273,7 +273,7 @@ "ChannelItem.goToWebsite": "Ir para o website de origem", "ChannelItem.lastPublished": "Publicado em {last_published}", "ChannelItem.lastUpdated": "Atualizado {updated}", - "ChannelItem.removeChannel": "Remove channel", + "ChannelItem.removeChannel": "Remover canal", "ChannelItem.resourceCount": "{count, plural, one {}\n =1 {# conteúdo}\n other {# conteúdos}}", "ChannelItem.unpublishedText": "Não publicado", "ChannelItem.versionText": "Versão {version}", @@ -508,162 +508,162 @@ "CommonMetadataStrings.work": "Trabalho", "CommonMetadataStrings.writing": "Escrita", "CommonStrings.backAction": "Voltar", - "CommonStrings.channelDetailsLabel": "Channel Details", + "CommonStrings.channelDetailsLabel": "Detalhes do canal", "CommonStrings.clearAction": "Limpar", "CommonStrings.closeAction": "Fechar", "CommonStrings.copyChannelTokenAction": "Copiar token do canal", "CommonStrings.dismissAction": "Descartar", "CommonStrings.genericErrorMessage": "Desculpe, algo deu errado. Tente novamente.", "CommonStrings.previewAction": "Pré-visualizar", - "CommonStrings.seeAllAction": "See all", - "CommonStrings.seeLessAction": "See less", - "CommunityChannelsStrings.aboutCommunityLibraryDescription": "Community library contains channels submitted by the community and approved for discovery in Studio.", - "CommunityChannelsStrings.aboutCommunityLibraryTitle": "About Community Library", - "CommunityChannelsStrings.activityHistoryLabel": "Activity history", + "CommonStrings.seeAllAction": "Ver tudo", + "CommonStrings.seeLessAction": "Ver menos", + "CommunityChannelsStrings.aboutCommunityLibraryDescription": "A biblioteca da comunidade contém canais enviados pela comunidade e aprovados para descoberta no Studio.", + "CommunityChannelsStrings.aboutCommunityLibraryTitle": "Sobre a Biblioteca da comunidade", + "CommunityChannelsStrings.activityHistoryLabel": "Histórico de atividades", "CommunityChannelsStrings.adminLabel": "Administrador", - "CommunityChannelsStrings.allLicensesCompatible": "All licenses are compatible with Community Library.", - "CommunityChannelsStrings.allNotificationsLabel": "All Notifications", - "CommunityChannelsStrings.alreadySubmittedWarningDescription": "Please wait for the channel to be reviewed. You will see a notification in your Studio account after the review is complete.", - "CommunityChannelsStrings.alreadySubmittedWarningTitle": "This version of the channel has already been submitted to the Community Library.", - "CommunityChannelsStrings.approvedNotification": "{author} ({userType}) approved {channelVersion}", - "CommunityChannelsStrings.approvedPrimaryInfo": "A previous version is live in the Community Library. Reviewers will see the latest submission first.", - "CommunityChannelsStrings.approvedStatus": "Approved", - "CommunityChannelsStrings.availableStatus": "Available in Community Library", + "CommunityChannelsStrings.allLicensesCompatible": "Todas as licenças são compatíveis com a Biblioteca da comunidade.", + "CommunityChannelsStrings.allNotificationsLabel": "Todas as notificações", + "CommunityChannelsStrings.alreadySubmittedWarningDescription": "Aguarde a revisão do canal. Você verá uma notificação na sua conta do Studio após a conclusão da avaliação.", + "CommunityChannelsStrings.alreadySubmittedWarningTitle": "Esta versão do canal já foi enviada à Biblioteca da comunidade.", + "CommunityChannelsStrings.approvedNotification": "{author} ({userType}) aprovado em {channelVersion}", + "CommunityChannelsStrings.approvedPrimaryInfo": "Uma versão anterior está disponível na Biblioteca da comunidade. Os editores verão primeiro os envios mais recentes.", + "CommunityChannelsStrings.approvedStatus": "Aprovado", + "CommunityChannelsStrings.availableStatus": "Disponível na Biblioteca da comunidade", "CommunityChannelsStrings.cancelAction": "Cancelar", "CommunityChannelsStrings.categoriesLabel": "Categorias", - "CommunityChannelsStrings.channelCannotBeDistributed": "This channel cannot be distributed via Kolibri.", - "CommunityChannelsStrings.channelFitAttributionLabel": "Attribution", - "CommunityChannelsStrings.channelFitChannelInfoLabel": "Channel information", - "CommunityChannelsStrings.channelFitChecklistAttribution": "Does each resource have an author listed?", - "CommunityChannelsStrings.channelFitChecklistChannelInfo": "Is your channel's basic information filled in — title, description, thumbnail, language, category, and level?", - "CommunityChannelsStrings.channelFitChecklistIntro": "Criteria for submitting to the Community Library", - "CommunityChannelsStrings.channelFitChecklistLicense": "Are the resources in your channel openly licensed or in the public domain?", - "CommunityChannelsStrings.channelFitChecklistOfflineUse": "Do the resources in your channel work without an Internet connection?", - "CommunityChannelsStrings.channelFitChecklistQuality": "Has anyone on your team or in your organization reviewed the channel?", + "CommunityChannelsStrings.channelCannotBeDistributed": "Este canal não pode ser distribuído pelo Kolibri.", + "CommunityChannelsStrings.channelFitAttributionLabel": "Atribuição", + "CommunityChannelsStrings.channelFitChannelInfoLabel": "Informações do canal", + "CommunityChannelsStrings.channelFitChecklistAttribution": "Cada recurso tem um autor listado?", + "CommunityChannelsStrings.channelFitChecklistChannelInfo": "As informações básicas do seu canal são preenchidas — título, descrição, miniatura, língua, categoria e nível?", + "CommunityChannelsStrings.channelFitChecklistIntro": "Critérios para envio à Biblioteca da comunidade", + "CommunityChannelsStrings.channelFitChecklistLicense": "Os recursos do seu canal estão sob licença aberta ou no domínio público?", + "CommunityChannelsStrings.channelFitChecklistOfflineUse": "Os recursos do seu canal funcionam sem uma conexão com a Internet?", + "CommunityChannelsStrings.channelFitChecklistQuality": "Alguém na sua equipe ou na sua organização revisou o canal?", "CommunityChannelsStrings.channelFitLicenseLabel": "Licença", - "CommunityChannelsStrings.channelFitOfflineUseLabel": "Offline use", - "CommunityChannelsStrings.channelFitQualityLabel": "Quality", - "CommunityChannelsStrings.channelTokenDescription": "You can use this token to import and preview the draft channel in Kolibri. Please note that the token for the final published channel will be different.", + "CommunityChannelsStrings.channelFitOfflineUseLabel": "Uso offline", + "CommunityChannelsStrings.channelFitQualityLabel": "Qualidade", + "CommunityChannelsStrings.channelTokenDescription": "Você pode usar esse token para importar e visualizar o canal de rascunhos no Kolibri. Observe que o token para o canal publicado final será diferente.", "CommunityChannelsStrings.channelVersion": "{name} v{version}", - "CommunityChannelsStrings.channelVersionTokenLabel": "Channel version token", + "CommunityChannelsStrings.channelVersionTokenLabel": "Token da versão do canal", "CommunityChannelsStrings.clearAll": "Limpar tudo", "CommunityChannelsStrings.clearAllAction": "Limpar tudo", - "CommunityChannelsStrings.communityLibraryCTADescription": "Have a channel worth sharing with other educators and learners? Submit it for review through the Share menu so it can be discovered in Studio.", - "CommunityChannelsStrings.communityLibraryCTATitle": "Help grow the Community Library", - "CommunityChannelsStrings.communityLibraryDescription": "Browse community-submitted channels approved for discovery in Studio. Copy a token to use a channel in Kolibri.", - "CommunityChannelsStrings.communityLibraryLabel": "Community Library", - "CommunityChannelsStrings.communityLibrarySubmissionLabel": "Community Library submission", - "CommunityChannelsStrings.compatibleLicensesDescription": "{licenseNames} - All licenses are compatible with Community Library.", - "CommunityChannelsStrings.confirmDistributionRights": "Please confirm you have the right to distribute these resources via Kolibri.", - "CommunityChannelsStrings.confirmReplacementText": "I understand this will replace my earlier submission on the review queue", - "CommunityChannelsStrings.countriesInfoText": "Select one or more countries to label your channel submission with. For example, if your channel contains materials aligned to a national curriculum or regionally-specific content, selecting the relevant countries will help users find it. Otherwise, leave this blank.", + "CommunityChannelsStrings.communityLibraryCTADescription": "Tem um canal que vale a pena compartilhar com outros educadores e alunos? Envie para revisão através do menu Compartilhar para ser descoberto no Studio.", + "CommunityChannelsStrings.communityLibraryCTATitle": "Ajude a aumentar a Biblioteca da comunidade", + "CommunityChannelsStrings.communityLibraryDescription": "Navegue pelos canais enviados pela comunidade aprovados para descoberta no Studio. Copie um token para usar um canal no Kolibri.", + "CommunityChannelsStrings.communityLibraryLabel": "Biblioteca da comunidade", + "CommunityChannelsStrings.communityLibrarySubmissionLabel": "Envio para Biblioteca da comunidade", + "CommunityChannelsStrings.compatibleLicensesDescription": "{licenseNames} - Todas as licenças são compatíveis com a Biblioteca da comunidade.", + "CommunityChannelsStrings.confirmDistributionRights": "Confirme que você tem o direito de distribuir esses recursos através do Kolibri.", + "CommunityChannelsStrings.confirmReplacementText": "Eu entendo que isso vai substituir meu envio anterior na fila de análise", + "CommunityChannelsStrings.countriesInfoText": "Selecione um ou mais países para rotular o que é enviado por seu canal. Por exemplo, se o seu canal contiver materiais alinhados a um currículo nacional ou a um conteúdo regional específico, selecionar os países relevantes ajudará os usuários a encontrá-lo. Caso contrário, deixe em branco.", "CommunityChannelsStrings.countryLabel": "Países", - "CommunityChannelsStrings.descriptionLabel": "Describe what's included in this submission", + "CommunityChannelsStrings.descriptionLabel": "Descreva o que há de novo nesta versão do canal", "CommunityChannelsStrings.dismissAction": "Descartar", - "CommunityChannelsStrings.draftBeingPublishedNotice": "Draft version is being published", - "CommunityChannelsStrings.draftPublishedNotice": "Draft published successfully", - "CommunityChannelsStrings.draftTokenLabel": "Draft token", + "CommunityChannelsStrings.draftBeingPublishedNotice": "Versão do rascunho sendo publicada", + "CommunityChannelsStrings.draftPublishedNotice": "Rascunho publicado com sucesso", + "CommunityChannelsStrings.draftTokenLabel": "Token de rascunho", "CommunityChannelsStrings.editorLabel": "Editor", - "CommunityChannelsStrings.emptyNotificationsNotice": "You have no notifications at this time.", - "CommunityChannelsStrings.emptyNotificationsWithFiltersNotice": "No notifications match the applied filters.", - "CommunityChannelsStrings.errorLoadingVersions": "Unable to load version history", - "CommunityChannelsStrings.errorSnackbar": "There was an error submitting the channel", - "CommunityChannelsStrings.feedbackNotesLabel": "Notes from the reviewer", - "CommunityChannelsStrings.filterByDateLabel": "Filter by date", - "CommunityChannelsStrings.filterByStatusLabel": "Filter by status", + "CommunityChannelsStrings.emptyNotificationsNotice": "Você não tem notificações no momento.", + "CommunityChannelsStrings.emptyNotificationsWithFiltersNotice": "Nenhuma notificação corresponde aos filtros aplicados.", + "CommunityChannelsStrings.errorLoadingVersions": "Não foi possível carregar histórico de versão", + "CommunityChannelsStrings.errorSnackbar": "Ocorreu um erro ao enviar o canal", + "CommunityChannelsStrings.feedbackNotesLabel": "Notas do revisor", + "CommunityChannelsStrings.filterByDateLabel": "Filtrar por data", + "CommunityChannelsStrings.filterByStatusLabel": "Filtrar por status", "CommunityChannelsStrings.filterLabel": "filtrar", - "CommunityChannelsStrings.fixLicensingBeforeSubmission": "Please fix licensing before submitting a new version.", - "CommunityChannelsStrings.flaggedNotification": "Changes required for version {channelVersion}", - "CommunityChannelsStrings.flaggedStatus": "Needs changes", - "CommunityChannelsStrings.getDraftTokenAction": "Copy token for draft channel", - "CommunityChannelsStrings.goToMyChannelsAction": "Go to My channels", - "CommunityChannelsStrings.hideCriteriaAction": "Hide criteria", - "CommunityChannelsStrings.hideVersions": "Hide versions", - "CommunityChannelsStrings.incompatibleLicensesDescription": "\"{licenseNames}\" - this channel cannot be distributed via Kolibri. If you cannot change the license, remove all resources with \"{licenseNames}\" before submitting again.", - "CommunityChannelsStrings.incompatibleLicensesDetected": "Incompatible licenses detected.", + "CommunityChannelsStrings.fixLicensingBeforeSubmission": "Corrija o licenciamento antes de enviar uma nova versão.", + "CommunityChannelsStrings.flaggedNotification": "Alterações necessárias para a versão {channelVersion}", + "CommunityChannelsStrings.flaggedStatus": "Precisa de alterações", + "CommunityChannelsStrings.getDraftTokenAction": "Copiar token para rascunho de canal", + "CommunityChannelsStrings.goToMyChannelsAction": "Ir para Meus canais", + "CommunityChannelsStrings.hideCriteriaAction": "Ocultar critérios", + "CommunityChannelsStrings.hideVersions": "Ocultar versões", + "CommunityChannelsStrings.incompatibleLicensesDescription": "\"{licenseNames}\" - este canal não pode ser distribuído por Kolibri. Se você não pode alterar a licença, remova todos os recursos com \"{licenseNames}\" antes de enviar novamente.", + "CommunityChannelsStrings.incompatibleLicensesDetected": "Licenças incompatíveis detectadas.", "CommunityChannelsStrings.incompleteResourcesDescription1": "Conteúdos incompletos não poderão ser publicados e disponibilizados para uso na Plataforma de Aprendizagem Kolibri.", - "CommunityChannelsStrings.incompleteResourcesWarning": "{count, number} {count, plural, one {incomplete resource} other {incomplete resources}}", - "CommunityChannelsStrings.internalNotesLabel": "Admin notes (internal use only)", - "CommunityChannelsStrings.invalidLicensingReason": "Invalid or non-compliant licenses", - "CommunityChannelsStrings.invalidMetadataReason": "Invalid or missing metadata", + "CommunityChannelsStrings.incompleteResourcesWarning": "{{count, number} {count, plural, one {recurso incompleto} other {recursos incompletos}}", + "CommunityChannelsStrings.internalNotesLabel": "Notas do administrador (apenas uso interno)", + "CommunityChannelsStrings.invalidLicensingReason": "Licenças inválidas ou não conformes", + "CommunityChannelsStrings.invalidMetadataReason": "Metadados inválidos ou faltando", "CommunityChannelsStrings.languageLabel": "Idioma", "CommunityChannelsStrings.languagesLabel": "Idiomas", - "CommunityChannelsStrings.lessDetailsButton": "Hide details", - "CommunityChannelsStrings.licenseCheckPassed": "License check passed.", + "CommunityChannelsStrings.lessDetailsButton": "Ocultar detalhes", + "CommunityChannelsStrings.licenseCheckPassed": "Passou a verificação da licença.", "CommunityChannelsStrings.licensesLabel": "Licenças", - "CommunityChannelsStrings.loadError": "There was an error loading channels.", - "CommunityChannelsStrings.loadingVersionHistory": "Loading version history", - "CommunityChannelsStrings.moreDetails": "After your submission is approved, the channel will be available to other Kolibri Studio users on the 'Community Library' page.", - "CommunityChannelsStrings.moreDetailsButton": "More details", - "CommunityChannelsStrings.needKolibriVersionToImport": "You will need Kolibri version 0.19.4 or higher to import channels from the Community Library.", - "CommunityChannelsStrings.needsChangesPrimaryInfo": "Your previously submitted version needs changes. Make sure you have addressed all comments before resubmitting.", + "CommunityChannelsStrings.loadError": "Ocorreu um erro ao carregar os canais.", + "CommunityChannelsStrings.loadingVersionHistory": "Carregando histórico de versões", + "CommunityChannelsStrings.moreDetails": "Após a aprovação do seu envio, o canal estará disponível para outros usuários do Kolibri Studio na página \"Biblioteca da comunidade\".", + "CommunityChannelsStrings.moreDetailsButton": "Mais detalhes", + "CommunityChannelsStrings.needKolibriVersionToImport": "Você precisará do Kolibri versão 0.19.4 ou superior para importar canais da Biblioteca da comunidade.", + "CommunityChannelsStrings.needsChangesPrimaryInfo": "Sua versão enviada anteriormente precisa de alterações. Certifique-se de que você abordou todos os comentários antes de reenviar.", "CommunityChannelsStrings.newLabel": "Novo", - "CommunityChannelsStrings.newNotificationsNotice": "New notifications available.", + "CommunityChannelsStrings.newNotificationsNotice": "Novas notificações disponíveis.", "CommunityChannelsStrings.nextPageAction": "Próximo", - "CommunityChannelsStrings.noCommunityChannels": "No channels have been published to the Community Library yet.", - "CommunityChannelsStrings.noResultsWithFilters": "No channels match the selected filters.", - "CommunityChannelsStrings.noVersionsAvailable": "No version history available", - "CommunityChannelsStrings.nonePrimaryInfo": "We're inviting members of the Kolibri community to submit channels they've created for offline learning in low-resource settings. ", - "CommunityChannelsStrings.notPublishedWarningDescription": "Publish to Studio first, then submit to the Community Library.", - "CommunityChannelsStrings.notPublishedWarningTitle": "This channel isn't published to Kolibri Studio yet", - "CommunityChannelsStrings.notificationsLabel": "Notifications", - "CommunityChannelsStrings.otherIssuesReason": "Other issues", + "CommunityChannelsStrings.noCommunityChannels": "Nenhum canal foi publicado na Biblioteca da comunidade.", + "CommunityChannelsStrings.noResultsWithFilters": "Nenhum canal corresponde aos filtros selecionados.", + "CommunityChannelsStrings.noVersionsAvailable": "Nenhum histórico de versão disponível", + "CommunityChannelsStrings.nonePrimaryInfo": "Estamos convidando membros da comunidade Kolibri a enviar canais que criaram para aprender offline em configurações de baixo recurso. ", + "CommunityChannelsStrings.notPublishedWarningDescription": "Publique no Studio primeiro, depois envie para a biblioteca da comunidade.", + "CommunityChannelsStrings.notPublishedWarningTitle": "Este canal ainda não está publicado no Kolibri Studio", + "CommunityChannelsStrings.notificationsLabel": "Notificações", + "CommunityChannelsStrings.otherIssuesReason": "Outros problemas", "CommunityChannelsStrings.pageIndicator": "{currentPage} de {totalPages}", - "CommunityChannelsStrings.pendingStatus": "Submitted", - "CommunityChannelsStrings.portabilityIssuesReason": "Portability problems", - "CommunityChannelsStrings.previewYourDraftTitle": "Preview your draft channel in Kolibri", + "CommunityChannelsStrings.pendingStatus": "Enviado", + "CommunityChannelsStrings.portabilityIssuesReason": "Problemas de portabilidade", + "CommunityChannelsStrings.previewYourDraftTitle": "Visualizar seu projeto de canal no Kolibri", "CommunityChannelsStrings.previousPageAction": "Pergunta anterior", - "CommunityChannelsStrings.publicWarningDescription": "It is not possible to submit public channels to the Community Library.", - "CommunityChannelsStrings.publicWarningTitle": "This channel is currently public in the Kolibri Library.", + "CommunityChannelsStrings.publicWarningDescription": "Não é possível enviar canais públicos à Biblioteca da comunidade.", + "CommunityChannelsStrings.publicWarningTitle": "Este canal é atualmente público na biblioteca do Kolibri.", "CommunityChannelsStrings.publishAction": "Publicar", - "CommunityChannelsStrings.publishChannel": "Publish channel", - "CommunityChannelsStrings.publishChannelDescription": "To see your channel in Kolibri, import using the channel token.", - "CommunityChannelsStrings.publishChannelMode": "Publish channel", - "CommunityChannelsStrings.publishDraftDescription": "Your channel will be saved as a draft, allowing you to review and do quality checks on the draft, without altering the current version available for Kolibri users. To see this draft channel in Kolibri, import using the draft channel token.", - "CommunityChannelsStrings.publishDraftMode": "Publish draft channel", + "CommunityChannelsStrings.publishChannel": "Publicar canal", + "CommunityChannelsStrings.publishChannelDescription": "Para ver seu canal no Kolibri, importe usando o token do canal.", + "CommunityChannelsStrings.publishChannelMode": "Publicar canal", + "CommunityChannelsStrings.publishDraftDescription": "Seu canal será salvo como rascunho para que você faça revisões e verificações de qualidade no rascunho, sem alterar a versão atual disponível para usuários Kolibri. Para ver esse rascunho de canal no Kolibri, importe usando o token de canal de rascunho.", + "CommunityChannelsStrings.publishDraftMode": "Publicar rascunho de canal", "CommunityChannelsStrings.publishedVersionLabel": "Versão publicada:", - "CommunityChannelsStrings.publishingInfo": "You're publishing: Version {version}", - "CommunityChannelsStrings.publishingMessage": "Channel is being published", - "CommunityChannelsStrings.qualityAssuranceReason": "Quality assurance issues", - "CommunityChannelsStrings.reasonLabel": "Reason: {reason}", - "CommunityChannelsStrings.resubmitAction": "Resubmit", - "CommunityChannelsStrings.resubmitModalBodyFirst": "{channelName} v{version} is also published to the Community Library.", - "CommunityChannelsStrings.resubmitModalBodySecond": "Would you like to resubmit this version with your changes for Community Library review?", - "CommunityChannelsStrings.resubmitModalTitle": "Resubmit channel for Community library review?", - "CommunityChannelsStrings.resultsText": "{count, plural, =1 {# result found} other {# results found}}", + "CommunityChannelsStrings.publishingInfo": "Você está publicando: Versão {version}", + "CommunityChannelsStrings.publishingMessage": "O canal está sendo publicado", + "CommunityChannelsStrings.qualityAssuranceReason": "Problemas de garantia de qualidade", + "CommunityChannelsStrings.reasonLabel": "Motivo: {reason}", + "CommunityChannelsStrings.resubmitAction": "Enviar novamente", + "CommunityChannelsStrings.resubmitModalBodyFirst": "{channelName} v{version} também é publicado na Biblioteca da comunidade.", + "CommunityChannelsStrings.resubmitModalBodySecond": "Gostaria de reenviar esta versão com as mudanças que você fez para revisão de Biblioteca da comunidade?", + "CommunityChannelsStrings.resubmitModalTitle": "Reenviar canal para revisão da biblioteca da comunidade?", + "CommunityChannelsStrings.resultsText": "{count, plural, =1 {# resultado encontrado} other {# resultados encontrados}}", "CommunityChannelsStrings.retry": "Tentar novamente", "CommunityChannelsStrings.reviewAction": "Revisar", - "CommunityChannelsStrings.saveDraft": "Save draft", + "CommunityChannelsStrings.saveDraft": "Salvar rascunho", "CommunityChannelsStrings.searchLabel": "Buscar", - "CommunityChannelsStrings.searchNotificationsLabel": "Search notifications", - "CommunityChannelsStrings.seeAllVersions": "See all versions", + "CommunityChannelsStrings.searchNotificationsLabel": "Pesquisar notificações", + "CommunityChannelsStrings.seeAllVersions": "Ver todas versões", "CommunityChannelsStrings.showMore": "Mostrar mais", - "CommunityChannelsStrings.showOlderAction": "Show older", - "CommunityChannelsStrings.specialPermissionsDetected": "Special Permissions licenses detected", - "CommunityChannelsStrings.submissionCreationNotification": "Your submission to the Community Library was successful and is now under review.", - "CommunityChannelsStrings.submissionNotesLabel": "Submission notes", - "CommunityChannelsStrings.submissionNotification": "{author} ({userType}) submitted {channelVersion}", - "CommunityChannelsStrings.submitButton": "Submit for review", - "CommunityChannelsStrings.submitToCommunityLibrary": "Submit to Community Library", - "CommunityChannelsStrings.submittedPrimaryInfo": "A previous version is still pending review. Reviewers will see the most recent submission by default.", - "CommunityChannelsStrings.submittedSnackbar": "Channel submitted to Community Library", - "CommunityChannelsStrings.submittingSnackbar": "Submitting channel to Community Library...", - "CommunityChannelsStrings.supersededStatus": "Superseded", + "CommunityChannelsStrings.showOlderAction": "Mostrar mais antigo", + "CommunityChannelsStrings.specialPermissionsDetected": "Licenças de Permissões especiais detectadas", + "CommunityChannelsStrings.submissionCreationNotification": "Seu envio para a Biblioteca da comunidade foi bem-sucedido e está sob revisão.", + "CommunityChannelsStrings.submissionNotesLabel": "Notas de envio", + "CommunityChannelsStrings.submissionNotification": "{author} ({userType}) enviado {channelVersion}", + "CommunityChannelsStrings.submitButton": "Enviar para revisão", + "CommunityChannelsStrings.submitToCommunityLibrary": "Enviar para a Biblioteca da comunidade", + "CommunityChannelsStrings.submittedPrimaryInfo": "Uma versão anterior ainda está pendente de revisão. Os revisores verão o envio mais recente por padrão.", + "CommunityChannelsStrings.submittedSnackbar": "Canal enviado à Biblioteca da comunidade", + "CommunityChannelsStrings.submittingSnackbar": "Enviando canal para Biblioteca da comunidade...", + "CommunityChannelsStrings.supersededStatus": "Substituído", "CommunityChannelsStrings.thisMonthLabel": "Este mês", - "CommunityChannelsStrings.thisWeekLabel": "This week", - "CommunityChannelsStrings.thisYearLabel": "This year", - "CommunityChannelsStrings.todayLabel": "Today", - "CommunityChannelsStrings.unreadNotificationsLabel": "Unread", + "CommunityChannelsStrings.thisWeekLabel": "Esta semana", + "CommunityChannelsStrings.thisYearLabel": "Este ano", + "CommunityChannelsStrings.todayLabel": "Hoje", + "CommunityChannelsStrings.unreadNotificationsLabel": "Não lida", "CommunityChannelsStrings.versionDescriptionLabel": "Descrição da versão", "CommunityChannelsStrings.versionLabel": "Versão {version}", "CommunityChannelsStrings.versionNotesLabel": "Descreva o que há de novo nesta versão do canal", - "CommunityChannelsStrings.viewCriteriaAction": "View criteria", + "CommunityChannelsStrings.viewCriteriaAction": "Ver critérios", "CommunityChannelsStrings.viewMoreAction": "Ver mais", - "CommunityChannelsStrings.whatCanYouDoHere": "What you can do here", - "CommunityChannelsStrings.whatCanYouDoHereItem1": "Browse channels by country, category, and language", - "CommunityChannelsStrings.whatCanYouDoHereItem2": "Copy a channel token to use in Kolibri", - "CommunityChannelsStrings.whatCanYouDoHereItem3": "View channel details including description and metadata", - "CommunityChannelsStrings.whatIsCommunityLibrary": "What is the Community Library?", + "CommunityChannelsStrings.whatCanYouDoHere": "O que você pode fazer aqui", + "CommunityChannelsStrings.whatCanYouDoHereItem1": "Procurar canais por país, categoria e idioma", + "CommunityChannelsStrings.whatCanYouDoHereItem2": "Copiar um token de canal para usar no Kolibri", + "CommunityChannelsStrings.whatCanYouDoHereItem3": "Ver detalhes do canal incluindo descrição e metadados", + "CommunityChannelsStrings.whatIsCommunityLibrary": "O que é a Biblioteca da comunidade?", "CommunityStandardsModal.communityStandardsHeader": "Normas comunitárias", "CommunityStandardsModal.coreValuesLink": "Saiba mais sobre os valores fundamentais da Learning Equality", "CommunityStandardsModal.description": "A Learning Equality é uma organização sem fins lucrativos dedicada a possibilitar o acesso igualitário a experiências de educação de qualidade. Junto à nossa declaração de valores fundamentais, estas normas comunitárias pretendem promover um ambiente solidário e inclusivo para nossos usuários.", @@ -1313,7 +1313,7 @@ "ImportFromChannelsModal.reviewAction": "Revisar", "ImportFromChannelsModal.reviewTitle": "Seleção de conteúdos", "InfoModal.close": "Fechar", - "InfoModal.open": "More information about licenses", + "InfoModal.open": "Mais informações sobre as licenças", "InheritAncestorMetadataModal.applyResourceDetailsTitle": "Aplicar detalhes da pasta '{folder}'", "InheritAncestorMetadataModal.cancelAction": "Cancelar", "InheritAncestorMetadataModal.categories": "Categorias: {categories}", @@ -1349,45 +1349,45 @@ "MasteryCriteriaGoal.labelText": "Objetivo", "MasteryCriteriaMofNFields.mHint": "Respostas corretas são necessárias", "MasteryCriteriaMofNFields.nHint": "Respostas recentes", - "MathLiveA11yStrings.accented": "accented", + "MathLiveA11yStrings.accented": "acentuado", "MathLiveA11yStrings.array": "array", - "MathLiveA11yStrings.box": "box", - "MathLiveA11yStrings.chemicalFormula": "chemical formula", + "MathLiveA11yStrings.box": "caixa", + "MathLiveA11yStrings.chemicalFormula": "fórmula química", "MathLiveA11yStrings.crossOut": "riscar", "MathLiveA11yStrings.deleted": "excluído: ", - "MathLiveA11yStrings.delimiter": "delimiter", - "MathLiveA11yStrings.denominator": "denominator", - "MathLiveA11yStrings.endOf": "{spokenText}; end of {relationName}", - "MathLiveA11yStrings.endOfMathfield": "{spokenText}; end of mathfield", + "MathLiveA11yStrings.delimiter": "delimitador", + "MathLiveA11yStrings.denominator": "denominador", + "MathLiveA11yStrings.endOf": "{spokenText}; fim de {relationName}", + "MathLiveA11yStrings.endOfMathfield": "{spokenText}; fim do campo matemático", "MathLiveA11yStrings.error": "erro", - "MathLiveA11yStrings.extensibleSymbol": "extensible symbol", - "MathLiveA11yStrings.first": "first", - "MathLiveA11yStrings.fraction": "fraction", - "MathLiveA11yStrings.group": "group", - "MathLiveA11yStrings.index": "index", + "MathLiveA11yStrings.extensibleSymbol": "símbolo extensível", + "MathLiveA11yStrings.first": "primeiro", + "MathLiveA11yStrings.fraction": "fração", + "MathLiveA11yStrings.group": "grupo", + "MathLiveA11yStrings.index": "índice", "MathLiveA11yStrings.latex": "LaTeX", "MathLiveA11yStrings.line": "linha", - "MathLiveA11yStrings.mathField": "math field", - "MathLiveA11yStrings.mathfield": "math field", - "MathLiveA11yStrings.numerator": "numerator", - "MathLiveA11yStrings.operator": "operator", - "MathLiveA11yStrings.outOf": "out of {relationName};", - "MathLiveA11yStrings.overUnder": "over-under", - "MathLiveA11yStrings.parent": "parent", + "MathLiveA11yStrings.mathField": "campo matemático", + "MathLiveA11yStrings.mathfield": "campo matemático", + "MathLiveA11yStrings.numerator": "numerador", + "MathLiveA11yStrings.operator": "operador", + "MathLiveA11yStrings.outOf": "de {relationName};", + "MathLiveA11yStrings.overUnder": "superior-inferior", + "MathLiveA11yStrings.parent": "elemento pai", "MathLiveA11yStrings.placeholder": "placeholder", "MathLiveA11yStrings.prompt": "prompt", - "MathLiveA11yStrings.radicand": "radicand", - "MathLiveA11yStrings.rule": "rule", + "MathLiveA11yStrings.radicand": "radicando", + "MathLiveA11yStrings.rule": "regra", "MathLiveA11yStrings.selected": "selecionado: ", - "MathLiveA11yStrings.space": "space", - "MathLiveA11yStrings.spacing": "spacing", + "MathLiveA11yStrings.space": "espaço", + "MathLiveA11yStrings.spacing": "espaçamento", "MathLiveA11yStrings.squareRoot": "raiz quadrada", - "MathLiveA11yStrings.startOf": "start of {relationName}: ", - "MathLiveA11yStrings.subscript": "subscript", - "MathLiveA11yStrings.subscriptSuperscript": "subscript-superscript", + "MathLiveA11yStrings.startOf": "início de {relationName}: ", + "MathLiveA11yStrings.subscript": "subscrito", + "MathLiveA11yStrings.subscriptSuperscript": "subscrito-sobrescrito", "MathLiveA11yStrings.superscript": "exponente", - "MathLiveA11yStrings.superscriptAndSubscript": "superscript and subscript", - "MathLiveA11yStrings.text": "text", + "MathLiveA11yStrings.superscriptAndSubscript": "sobrescrito e subscrito", + "MathLiveA11yStrings.text": "texto", "MessageLayout.backToLogin": "Continuar para a página de login", "MoveModal.addTopic": "Adicionar nova pasta", "MoveModal.cancel": "Cancelar", @@ -1428,7 +1428,7 @@ "PrivacyPolicyModal.updatedPrivacyHeader": "Política de privacidade atualizada", "ProgressBar.progressText": "{percent}%", "ProgressModal.defaultErrorText": "Falha na última tentativa de publicação", - "ProgressModal.draftHeader": "Saving draft...", + "ProgressModal.draftHeader": "Salvando rascunho...", "ProgressModal.lastPublished": "Publicado em {last_published}", "ProgressModal.publishHeader": "Publicando canal", "ProgressModal.syncError": "Falha na última tentativa de sincronização", @@ -1476,7 +1476,7 @@ "RemoveChannelFromListModal.removeTitle": "Remover da lista de canais", "RemoveChannelModal.cancel": "Cancelar", "RemoveChannelModal.deleteChannel": "Excluir canal", - "RemoveChannelModal.deleteChannelWithCLWarning": "This channel has been shared with the Community Library. Deleting it here will not remove it from the Community Library — it may still be approved or remain available there.", + "RemoveChannelModal.deleteChannelWithCLWarning": "Este canal foi compartilhado com a Biblioteca da comunidade. Sua exclusão não o removerá da Biblioteca da comunidade — ele ainda pode ser aprovado ou permanecer disponível.", "RemoveChannelModal.deletePrompt": "Este canal será excluído permanentemente. Esta ação não pode ser desfeita.", "RemoveChannelModal.deleteTitle": "Excluir este canal", "RemoveChannelModal.removeBtn": "Remover", @@ -1669,11 +1669,11 @@ "SearchRecommendationsStrings.tooAdvancedForLearnersLabel": "Muito avançado para o nível de conhecimento de alunos que estou procurando", "SearchRecommendationsStrings.tooBasicForLearnersLabel": "Muito básico para o nível de conhecimento de alunos que estou procurando", "SearchRecommendationsStrings.tryAgainLink": "Tentar novamente", - "SearchRecommendationsStrings.trySearchRecommendationsHeader": "Try our new 'Recommendations' feature!", - "SearchRecommendationsStrings.trySearchRecommendationsText": "Based on the title and description of the folder you are working on, we'll suggest relevant resources from the Kolibri Library. Choose 'Import from channels' in any folder of your channels to see the recommendations.", + "SearchRecommendationsStrings.trySearchRecommendationsHeader": "Experimente a nova funcionalidade de \"Recomendações\"!", + "SearchRecommendationsStrings.trySearchRecommendationsText": "Com base no título e na descrição da pasta em que você está trabalhando, sugerimos recursos relevantes da biblioteca do Kolibri. Escolha \"Importar de canais\" em qualquer pasta de seus canais para ver as recomendações.", "SearchRecommendationsStrings.undoAction": "Desfazer", "SearchRecommendationsStrings.viewMoreLink": "Ver mais", - "SearchRecommendationsStrings.viewRecommendationsButton": "View recommendations", + "SearchRecommendationsStrings.viewRecommendationsButton": "Ver recomendações", "SearchResultsList.failedToLoad": "Falha ao carregar os resultados da busca", "SearchResultsList.resultsPerPageLabel": "Resultados por página", "SearchResultsList.saveSearchAction": "Salvar busca", @@ -1731,14 +1731,14 @@ "StudioChannelCard.details": "Detalhes", "StudioChannelCard.lastPublished": "Publicado em {last_published}", "StudioChannelCard.lastUpdated": "Atualizado {updated}", - "StudioChannelCard.multipleCountries": "Multiple countries", + "StudioChannelCard.multipleCountries": "Vários países", "StudioChannelCard.resourceCount": "{count, plural, one {}\n =1 {# conteúdo}\n other {# conteúdos}}", "StudioChannelCard.selectChannel": "Selecionar {name}", "StudioChannelCard.unpublishedText": "Não publicado", "StudioChannelsPage.invitations": "Você tem {count, plural, one {}\n =1 {# convite}\n other {# convites}}", "StudioChannelsPage.noChannelsFound": "Nenhum canal encontrado", "StudioCollectionsTable.aboutChannelSets": "Sobre os conjuntos", - "StudioCollectionsTable.aboutChannelSetsLink": "Learn more about collections", + "StudioCollectionsTable.aboutChannelSetsLink": "Saiba mais sobre coleções", "StudioCollectionsTable.addChannelSetTitle": "Novo conjunto", "StudioCollectionsTable.cancel": "Cancelar", "StudioCollectionsTable.cancelButtonLabel": "Fechar", @@ -1746,20 +1746,20 @@ "StudioCollectionsTable.channelSetsDescriptionText": "Um conjunto contém vários canais do Kolibri Studio que podem ser importados de uma vez ao Kolibri com um único token de conjunto.", "StudioCollectionsTable.channelSetsDisclaimer": "Você precisará da versão 0.12.0 ou superior do Kolibri para importar conjuntos de canais", "StudioCollectionsTable.channelSetsInstructionsText": "Você pode fazer um conjunto selecionando os canais que quer importar juntos.", - "StudioCollectionsTable.collectionDeleted": "Collection deleted", + "StudioCollectionsTable.collectionDeleted": "Coleção excluída", "StudioCollectionsTable.copiedTokenId": "Token copiado", "StudioCollectionsTable.copyFailed": "Falha ao copiar", - "StudioCollectionsTable.copyToken": "Copy token", + "StudioCollectionsTable.copyToken": "Copiar token", "StudioCollectionsTable.delete": "Excluir conjunto", "StudioCollectionsTable.deleteChannelSetText": "Tem certeza que deseja excluir esta coleção?", "StudioCollectionsTable.deleteChannelSetTitle": "Excluir conjunto", - "StudioCollectionsTable.deleteError": "Error deleting collection", + "StudioCollectionsTable.deleteError": "Erro ao excluir coleção", "StudioCollectionsTable.edit": "Editar conjunto", "StudioCollectionsTable.noChannelSetsFound": "Você pode integrar vários canais para criar um conjunto. O conjunto inteiro pode então ser importado ao Kolibri de uma vez por meio de um token de conjunto.", "StudioCollectionsTable.options": "Opções", "StudioCollectionsTable.pageTitle": "Conjuntos", "StudioCollectionsTable.saving": "Salvando", - "StudioCollectionsTable.tableCaption": "List of collections", + "StudioCollectionsTable.tableCaption": "Lista de coleções", "StudioCollectionsTable.title": "Nome do conjunto", "StudioCollectionsTable.token": "ID do token", "StudioCopyToken.copiedTokenId": "Token copiado", @@ -1816,32 +1816,32 @@ "StudioStarredChannels.editChannel": "Editar detalhes do canal", "StudioStarredChannels.goToWebsite": "Ir para o website de origem", "StudioStarredChannels.moreOptions": "Mais opções", - "StudioStarredChannels.removeChannel": "Remove channel", - "StudioStarredChannels.title": "Starred channels", + "StudioStarredChannels.removeChannel": "Remover canal", + "StudioStarredChannels.title": "Canais favoritos", "StudioStarredChannels.viewContent": "Visualizar canal no Kolibri", "StudioTree.missingTitle": "Falta o título", "StudioTree.optionsTooltip": "Opções", "StudioViewOnlyChannels.copyToken": "Copiar token do canal", "StudioViewOnlyChannels.goToWebsite": "Ir para o website de origem", "StudioViewOnlyChannels.moreOptions": "Outras opções", - "StudioViewOnlyChannels.removeChannel": "Remove channel", - "StudioViewOnlyChannels.title": "View-only channels", + "StudioViewOnlyChannels.removeChannel": "Remover canal", + "StudioViewOnlyChannels.title": "Canais de somente visualização", "StudioViewOnlyChannels.viewContent": "Visualizar canal no Kolibri", - "SubscriptionCard.annualPrice": "${price, number}/year", - "SubscriptionCard.cancelNotice": "Your subscription will expire on {date, date, medium}. Storage will be removed after that.", + "SubscriptionCard.annualPrice": "${price, number}/ano", + "SubscriptionCard.cancelNotice": "Sua assinatura expira em {date, date, medium}. Após esta data, o armazenamento será removido.", "SubscriptionCard.dismiss": "Descartar", - "SubscriptionCard.genericError": "There was a problem connecting to the payment provider. Please try again.", - "SubscriptionCard.instantUpgrade": "Upgrade storage now", - "SubscriptionCard.manageSubscription": "Manage subscription", - "SubscriptionCard.renewalNotice": "Your subscription will automatically renew on {date, date, medium}.", - "SubscriptionCard.storageAmount": "Storage (GB)", - "SubscriptionCard.storageIncluded": "{size} included in your subscription", - "SubscriptionCard.storageRange": "Enter a value between 1 and 50", - "SubscriptionCard.subscriptionActive": "Storage subscription active", - "SubscriptionCard.subscriptionCanceling": "Subscription cancelled", - "SubscriptionCard.upgradeDescription": "Purchase additional storage at $15/GB per year.", - "SubscriptionCard.upgradeNow": "Upgrade now", - "SubscriptionCard.upgradeSuccess": "Storage increased to {size}", + "SubscriptionCard.genericError": "Houve um problema na conexão com o provedor de pagamento. Tente novamente.", + "SubscriptionCard.instantUpgrade": "Atualize o armazenamento agora", + "SubscriptionCard.manageSubscription": "Gerenciar assinatura", + "SubscriptionCard.renewalNotice": "A renovação automática de sua assinatura será em {date, date, medium}.", + "SubscriptionCard.storageAmount": "Armazenamento (GB)", + "SubscriptionCard.storageIncluded": "{size} incluído em sua assinatura", + "SubscriptionCard.storageRange": "Digite um número entre 1 e 50", + "SubscriptionCard.subscriptionActive": "Assinatura de armazenamento ativa", + "SubscriptionCard.subscriptionCanceling": "Assinatura cancelada", + "SubscriptionCard.upgradeDescription": "Comprar armazenamento adicional por US$ 15/GB por ano.", + "SubscriptionCard.upgradeNow": "Atualizar agora", + "SubscriptionCard.upgradeSuccess": "Armazenamento aumentado para {size}", "SubtitlesList.acceptedFormatsTooltip": "Formatos suportados: {extensions}", "SubtitlesList.addSubtitleText": "Adicionar legendas", "SubtitlesList.subtitlesHeader": "Legendas", @@ -1969,95 +1969,95 @@ "ThumbnailGenerator.generatedDefaultFilename": "Miniatura gerada", "ThumbnailGenerator.thumbnailGenerationFailedHeader": "Não foi possível gerar miniatura", "ThumbnailGenerator.thumbnailGenerationFailedText": "Ocorreu um erro ao gerar uma miniatura", - "TipTapEditorStrings.TipTapEditorLabel": "text editor - Press Enter to start editing", - "TipTapEditorStrings.TipTapViewerLabel": "text editor content", - "TipTapEditorStrings.addLink": "Add link", - "TipTapEditorStrings.alignLeft": "Align left", - "TipTapEditorStrings.alignRight": "Align right", - "TipTapEditorStrings.altTextDescription": "Alt text is necessary to enable visually impaired learners to answer questions, and it also displays when the image fails to load", - "TipTapEditorStrings.altTextLabel": "Alt text (Optional)", - "TipTapEditorStrings.altTextPlaceholder": "Describe your image...", - "TipTapEditorStrings.bold": "Strong", - "TipTapEditorStrings.bulletList": "Bullet list", + "TipTapEditorStrings.TipTapEditorLabel": "editor de texto - Pressione Enter para começar a editar", + "TipTapEditorStrings.TipTapViewerLabel": "conteúdo do editor de texto", + "TipTapEditorStrings.addLink": "Adicionar link", + "TipTapEditorStrings.alignLeft": "Alinhar à esquerda", + "TipTapEditorStrings.alignRight": "Alinhar à direita", + "TipTapEditorStrings.altTextDescription": "O texto alternativo é necessário para permitir que alunos com deficiência visual respondam às perguntas, e também é exibido quando a imagem não carregar", + "TipTapEditorStrings.altTextLabel": "Texto alternativo (opcional)", + "TipTapEditorStrings.altTextPlaceholder": "Descreva sua imagem...", + "TipTapEditorStrings.bold": "Negrito", + "TipTapEditorStrings.bulletList": "Lista com marcadores", "TipTapEditorStrings.cancel": "Cancelar", - "TipTapEditorStrings.cancelLoading": "Cancel loading", - "TipTapEditorStrings.clearFormatting": "Clear formatting", - "TipTapEditorStrings.clipboardAccessFailed": "Clipboard access failed. Try copying again.", + "TipTapEditorStrings.cancelLoading": "Cancelar carregamento", + "TipTapEditorStrings.clearFormatting": "Limpar formatação", + "TipTapEditorStrings.clipboardAccessFailed": "Não foi possível acessar a área de transferência. Tente copiar novamente.", "TipTapEditorStrings.close": "Fechar", - "TipTapEditorStrings.closeModal": "Close modal", - "TipTapEditorStrings.codeBlock": "Code block", - "TipTapEditorStrings.collapseFormattingBar": "Collapse formatting bar", + "TipTapEditorStrings.closeModal": "Fechar modal", + "TipTapEditorStrings.codeBlock": "Bloco de código", + "TipTapEditorStrings.collapseFormattingBar": "Minimizar barra de formatação", "TipTapEditorStrings.copy": "Copiar", - "TipTapEditorStrings.copyAndPasteActions": "Copy and paste actions", - "TipTapEditorStrings.copyLink": "Copy link", - "TipTapEditorStrings.decreaseFormatSize": "Decrease format size", - "TipTapEditorStrings.defaultImageName": "Image", + "TipTapEditorStrings.copyAndPasteActions": "Ações de copiar e colar", + "TipTapEditorStrings.copyLink": "Copiar o link", + "TipTapEditorStrings.decreaseFormatSize": "Reduzir tamanho do formato", + "TipTapEditorStrings.defaultImageName": "Imagem", "TipTapEditorStrings.edit": "Editar", - "TipTapEditorStrings.editImage": "Edit image", - "TipTapEditorStrings.editLink": "Edit link", - "TipTapEditorStrings.editorControls": "Editor controls", - "TipTapEditorStrings.errorUploadingImage": "Error uploading image", - "TipTapEditorStrings.expandFormattingBar": "Expand formatting bar", - "TipTapEditorStrings.failedToProcessImage": "Failed to process the image file.", + "TipTapEditorStrings.editImage": "Editar imagem", + "TipTapEditorStrings.editLink": "Editar link", + "TipTapEditorStrings.editorControls": "Controles do editor", + "TipTapEditorStrings.errorUploadingImage": "Erro ao fazer upload da imagem", + "TipTapEditorStrings.expandFormattingBar": "Expandir barra de formatação", + "TipTapEditorStrings.failedToProcessImage": "Falha ao processar o arquivo de imagem.", "TipTapEditorStrings.fileSizeUnit": "MB.", - "TipTapEditorStrings.fileTooLarge": "File is too large. Maximum size is ", - "TipTapEditorStrings.formatHeader1": "Header 1", - "TipTapEditorStrings.formatHeader2": "Header 2", - "TipTapEditorStrings.formatHeader3": "Header 3", + "TipTapEditorStrings.fileTooLarge": "O arquivo é muito grande. O tamanho máximo é ", + "TipTapEditorStrings.formatHeader1": "Cabeçalho 1", + "TipTapEditorStrings.formatHeader2": "Cabeçalho 2", + "TipTapEditorStrings.formatHeader3": "Cabeçalho 3", "TipTapEditorStrings.formatNormal": "Normal", - "TipTapEditorStrings.formatOptions": "Format options", - "TipTapEditorStrings.formatSize": "Format size", + "TipTapEditorStrings.formatOptions": "Opções de formatação", + "TipTapEditorStrings.formatSize": "Tamanho do formato", "TipTapEditorStrings.formatSmall": "Pequeno", - "TipTapEditorStrings.formulasMenuTitle": "Special Characters", - "TipTapEditorStrings.goToLink": "Go to link", - "TipTapEditorStrings.historyActions": "History actions", - "TipTapEditorStrings.imageDropZoneText": "Drag and drop an image here or upload manually", - "TipTapEditorStrings.imagePreview": "Image preview", - "TipTapEditorStrings.increaseFormatSize": "Increase format size", + "TipTapEditorStrings.formulasMenuTitle": "Caracteres especiais", + "TipTapEditorStrings.goToLink": "Ir para o link", + "TipTapEditorStrings.historyActions": "Ações do histórico", + "TipTapEditorStrings.imageDropZoneText": "Arraste e solte uma imagem aqui ou envie manualmente", + "TipTapEditorStrings.imagePreview": "Pré-visualização da imagem", + "TipTapEditorStrings.increaseFormatSize": "Aumentar tamanho do formato", "TipTapEditorStrings.insert": "Inserir", - "TipTapEditorStrings.insertContent": "Insert content", - "TipTapEditorStrings.insertContentMenu": "Insert content menu", - "TipTapEditorStrings.insertContentOption": "Insert content option", - "TipTapEditorStrings.insertImage": "Insert image", - "TipTapEditorStrings.insertLink": "Insert link", - "TipTapEditorStrings.insertTools": "Insert tools", - "TipTapEditorStrings.invalidFileType": "Invalid file type. Please use: ", - "TipTapEditorStrings.italic": "Italic", + "TipTapEditorStrings.insertContent": "Inserir conteúdo", + "TipTapEditorStrings.insertContentMenu": "Inseir menu de conteúdo", + "TipTapEditorStrings.insertContentOption": "Inserir opção de conteúdo", + "TipTapEditorStrings.insertImage": "Inserir imagem", + "TipTapEditorStrings.insertLink": "Inserir link", + "TipTapEditorStrings.insertTools": "Inserir ferramentas", + "TipTapEditorStrings.invalidFileType": "Tipo de arquivo inválido. Use: ", + "TipTapEditorStrings.italic": "Itálico", "TipTapEditorStrings.link": "Link", - "TipTapEditorStrings.linkActions": "Link actions", - "TipTapEditorStrings.listFormatting": "List formatting", - "TipTapEditorStrings.loadingFormulas": "Loading math editor", - "TipTapEditorStrings.mathFormula": "Math formula", + "TipTapEditorStrings.linkActions": "Ações do link", + "TipTapEditorStrings.listFormatting": "Formatação de lista", + "TipTapEditorStrings.loadingFormulas": "Carregando editor de matemática", + "TipTapEditorStrings.mathFormula": "Fórmula matemática", "TipTapEditorStrings.moreButtonText": "Mais", - "TipTapEditorStrings.multipleFilesDroppedWarning": "Multiple files were dropped. Only the first file has been selected.", - "TipTapEditorStrings.noEnoughStorageSpace": "Not enough storage space available. File size exceeds remaining storage.", - "TipTapEditorStrings.noFileProvided": "No file provided.", - "TipTapEditorStrings.numberedList": "Numbered list", - "TipTapEditorStrings.opensInNewTab": "(opens in new tab)", - "TipTapEditorStrings.paste": "Paste", - "TipTapEditorStrings.pasteOptions": "Paste options", - "TipTapEditorStrings.pasteOptionsMenu": "Paste options menu", - "TipTapEditorStrings.pasteWithoutFormatting": "Paste without formatting", - "TipTapEditorStrings.redo": "Redo", + "TipTapEditorStrings.multipleFilesDroppedWarning": "Vários arquivos foram descartados. Apenas o primeiro arquivo foi selecionado.", + "TipTapEditorStrings.noEnoughStorageSpace": "Não há espaço suficiente disponível. O tamanho do arquivo excede o armazenamento restante.", + "TipTapEditorStrings.noFileProvided": "Nenhum arquivo fornecido.", + "TipTapEditorStrings.numberedList": "Lista numerada", + "TipTapEditorStrings.opensInNewTab": "(abre em nova aba)", + "TipTapEditorStrings.paste": "Colar", + "TipTapEditorStrings.pasteOptions": "Opções de colar", + "TipTapEditorStrings.pasteOptionsMenu": "Menu opções de colar", + "TipTapEditorStrings.pasteWithoutFormatting": "Colar sem formatação", + "TipTapEditorStrings.redo": "Refazer", "TipTapEditorStrings.remove": "Remover", - "TipTapEditorStrings.removeImage": "Remove image", - "TipTapEditorStrings.removeLink": "Remove link", + "TipTapEditorStrings.removeImage": "Remover imagem", + "TipTapEditorStrings.removeLink": "Remover link", "TipTapEditorStrings.replaceFile": "Substituir arquivo", "TipTapEditorStrings.save": "Salvar", "TipTapEditorStrings.saveChanges": "Salvar alterações", - "TipTapEditorStrings.scriptFormatting": "Script formatting", + "TipTapEditorStrings.scriptFormatting": "Formatação de script", "TipTapEditorStrings.selectFile": "Selecionar arquivo", - "TipTapEditorStrings.selectFileToUpload": "Select file to upload", - "TipTapEditorStrings.strikethrough": "Strikethrough", - "TipTapEditorStrings.subscript": "Subscript", + "TipTapEditorStrings.selectFileToUpload": "Selecione um arquivo para upload", + "TipTapEditorStrings.strikethrough": "Tachado", + "TipTapEditorStrings.subscript": "Subscrito", "TipTapEditorStrings.superscript": "Exponente", "TipTapEditorStrings.supportedFileTypes": "Tipos de arquivo suportados: { extensions }", - "TipTapEditorStrings.text": "Text", - "TipTapEditorStrings.textFormatOptions": "Text format options", - "TipTapEditorStrings.textFormattingOptions": "Text formatting options", - "TipTapEditorStrings.textFormattingToolbar": "Text formatting toolbar", - "TipTapEditorStrings.textStyleFormatting": "Text style formatting", - "TipTapEditorStrings.underline": "Underline", + "TipTapEditorStrings.text": "Texto", + "TipTapEditorStrings.textFormatOptions": "Opções de formato texto", + "TipTapEditorStrings.textFormattingOptions": "Opções de formatação de texto", + "TipTapEditorStrings.textFormattingToolbar": "Barra de formatação de texto", + "TipTapEditorStrings.textStyleFormatting": "Formatação de estilo texto", + "TipTapEditorStrings.underline": "Sublinhar", "TipTapEditorStrings.undo": "Desfazer", "TipTapEditorStrings.uploadImage": "Enviar imagem", "TitleStrings.catalogTitle": "Catálogo da Biblioteca de Conteúdos Kolibri", @@ -2098,8 +2098,8 @@ "TreeViewBase.publishButtonTitle": "Tornar este canal disponível para importação no Kolibri", "TreeViewBase.shareChannel": "Compartilhar canal", "TreeViewBase.shareMenuButton": "Compartilhar", - "TreeViewBase.shareToken": "Share token", - "TreeViewBase.submitToCommunityLibrary": "Submit to Community Library", + "TreeViewBase.shareToken": "Compartilhar token", + "TreeViewBase.submitToCommunityLibrary": "Enviar para a Biblioteca da comunidade", "TreeViewBase.syncChannel": "Sincronizar conteúdos", "TreeViewBase.viewOnly": "Para visualizar", "Uploader.closeButtonLabel": "OK", @@ -2176,4 +2176,4 @@ "sharedVue.masteryModelRequired": "O critério de domínio é obrigatório", "sharedVue.shortActivityLteThirty": "O número deve ser igual ou menor que 30", "sharedVue.titleRequired": "O título é obrigatório" -} +} \ No newline at end of file diff --git a/contentcuration/locale/pt_BR/LC_MESSAGES/django.po b/contentcuration/locale/pt_BR/LC_MESSAGES/django.po index f5d43696b3..ab71e4d939 100644 --- a/contentcuration/locale/pt_BR/LC_MESSAGES/django.po +++ b/contentcuration/locale/pt_BR/LC_MESSAGES/django.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: kolibri-studio\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-04-16 00:57+0000\n" -"PO-Revision-Date: 2026-04-20 19:33\n" +"PO-Revision-Date: 2026-04-24 16:20\n" "Last-Translator: \n" "Language-Team: Portuguese, Brazilian\n" "Language: pt_BR\n" @@ -742,3 +742,4 @@ msgstr "A versão da API está indisponível" #: kolibri_public/views_v1.py:170 msgid "No channel matching {} found" msgstr "Nenhum canal correspondente a {} encontrado" + From f654ed3c2b6b8e4e499b2734c39f3615d0ef3960 Mon Sep 17 00:00:00 2001 From: Richard Tibbles Date: Fri, 24 Apr 2026 12:52:55 -0700 Subject: [PATCH 20/34] Bring the pre-commit exclude for end-of-file-fixer in line with Kolibri to avoid issues with autoformatting of downloaded i18n files. --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3b663b0af5..970a7a86b9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ repos: exclude: '^.+?\.ttf$' - id: debug-statements - id: end-of-file-fixer - exclude: '^.+?\.json.+?\.yml$' + exclude: '^.+?(\.json|\.po)$' - repo: https://github.com/PyCQA/flake8 rev: 7.1.2 hooks: From 80bb3a16b4c95ee001b917e4ff4047c07531593a Mon Sep 17 00:00:00 2001 From: rtibblesbot Date: Wed, 29 Apr 2026 16:47:26 -0700 Subject: [PATCH 21/34] Remap Unit extra_fields.options node IDs when copying via copy_node (#5860) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remap Unit extra_fields.options node IDs when copying via copy_node When a Unit topic (UNIT modality) is cloned, lesson node IDs and pre/post test node IDs in extra_fields.options are rewritten to the IDs of their copies. Entries whose source nodes were not part of the copy operation (e.g. excluded via excluded_descendants) are dropped. Adds _remap_unit_options() static helper on CustomContentNodeTreeManager, calls it in _recurse_to_create_tree() for the deep-copy path and in _copy() for the shallow-copy path. _deep_copy() and _copy() now return (nodes, source_copy_id_map) so the map is available to callers; copy_node() unwraps the tuple and returns only the node list. * Test Unit extra_fields.options remapping across copy paths Adds UnitCopyExtraFieldsTestCase covering: - lesson_objectives keys remapped to cloned lesson PKs (deep and shallow) - pre_test/post_test values remapped to cloned node PKs (deep and shallow) - excluded lesson entry dropped from cloned Unit's lesson_objectives - excluded Unit: copy succeeds with no remap error (deep and shallow) - resource excluded inside lesson: lesson entry preserved (deep and shallow) - assessment_objectives, learning_objectives, completion_criteria unchanged - standalone Unit copy remaps its lesson children correctly - extra_fields on Course and Lesson topics is copied verbatim * Remove pre/post test remapping — Unit options contain no such node-ID keys The pre_test/post_test entries in a Unit's options are assessment items on the Unit node itself, not separate ContentNode IDs. Remove the dead code in _remap_unit_options() and the fantasy test coverage that accompanied it. Co-Authored-By: Claude Sonnet 4.6 * test: drop test_standalone_unit_copy_remaps_lesson_children Reviewer noted this test is redundant — the same behaviour is already exercised by the deep/shallow copy integration tests for Unit nodes. Co-Authored-By: Claude Sonnet 4.6 --------- Co-authored-by: Claude Sonnet 4.6 --- .../contentcuration/db/models/manager.py | 57 ++- .../tests/test_contentnodes.py | 376 ++++++++++++++++++ 2 files changed, 427 insertions(+), 6 deletions(-) diff --git a/contentcuration/contentcuration/db/models/manager.py b/contentcuration/contentcuration/db/models/manager.py index 5121a9e99f..32da6ada4b 100644 --- a/contentcuration/contentcuration/db/models/manager.py +++ b/contentcuration/contentcuration/db/models/manager.py @@ -9,6 +9,7 @@ from django.db.utils import OperationalError from django_cte import CTEQuerySet from le_utils.constants import content_kinds +from le_utils.constants import modalities from mptt.managers import TreeManager from mptt.signals import node_moved @@ -359,6 +360,15 @@ def _recurse_to_create_tree( ) ) source_copy_id_map[source.id] = copy["id"] + + if self._is_unit_topic(source.kind_id, copy.get("extra_fields")): + copy["extra_fields"] = { + **copy["extra_fields"], + "options": self._remap_unit_options( + copy["extra_fields"]["options"], source_copy_id_map + ), + } + return copy def _all_nodes_to_copy(self, node, excluded_descendants): @@ -394,7 +404,7 @@ def copy_node( if progress_tracker: progress_tracker.set_total(total_nodes) - return self._copy( + nodes, _ = self._copy( node, target, position, @@ -406,6 +416,7 @@ def copy_node( batch_size, progress_tracker=progress_tracker, ) + return nodes def _copy( self, @@ -424,7 +435,7 @@ def _copy( :type progress_tracker: contentcuration.utils.celery.ProgressTracker|None """ if node.rght - node.lft < batch_size: - copied_nodes = self._deep_copy( + copied_nodes, node_map = self._deep_copy( node, target, position, @@ -436,7 +447,7 @@ def _copy( ) if progress_tracker: progress_tracker.increment(len(copied_nodes)) - return copied_nodes + return copied_nodes, node_map node_copy = self._shallow_copy( node, target, @@ -451,8 +462,9 @@ def _copy( children = node.get_children().order_by("lft") if excluded_descendants: children = children.exclude(node_id__in=excluded_descendants.keys()) + combined_map = {node.id: node_copy.id} for child in children: - self._copy( + _, child_map = self._copy( child, node_copy, "last-child", @@ -464,7 +476,19 @@ def _copy( batch_size, progress_tracker=progress_tracker, ) - return [node_copy] + combined_map.update(child_map) + + if self._is_unit_topic(node.kind_id, node.extra_fields): + remapped_extra_fields = { + **node_copy.extra_fields, + "options": self._remap_unit_options( + node.extra_fields["options"], combined_map + ), + } + self.filter(pk=node_copy.pk).update(extra_fields=remapped_extra_fields) + node_copy.extra_fields = remapped_extra_fields + + return [node_copy], combined_map def _copy_tags(self, source_copy_id_map): from contentcuration.models import ContentTag @@ -582,6 +606,27 @@ def _copy_associated_objects(self, source_copy_id_map): self._copy_tags(source_copy_id_map) + @staticmethod + def _is_unit_topic(kind_id, extra_fields): + return ( + kind_id == content_kinds.TOPIC + and isinstance(extra_fields, dict) + and extra_fields.get("options", {}).get("modality") == modalities.UNIT + ) + + @staticmethod + def _remap_unit_options(options, source_copy_id_map): + remapped = dict(options) + + if "lesson_objectives" in remapped: + remapped["lesson_objectives"] = { + source_copy_id_map[old_id]: objectives + for old_id, objectives in remapped["lesson_objectives"].items() + if old_id in source_copy_id_map + } + + return remapped + def _shallow_copy( self, node, @@ -665,4 +710,4 @@ def _deep_copy( self._copy_associated_objects(source_copy_id_map) - return new_nodes + return new_nodes, source_copy_id_map diff --git a/contentcuration/contentcuration/tests/test_contentnodes.py b/contentcuration/contentcuration/tests/test_contentnodes.py index 0de23ab008..329508a186 100644 --- a/contentcuration/contentcuration/tests/test_contentnodes.py +++ b/contentcuration/contentcuration/tests/test_contentnodes.py @@ -1871,3 +1871,379 @@ def test_migrates_string_extra_fields(self): extra_fields["options"]["completion_criteria"], kind=content_kinds.EXERCISE, ) + + +class UnitCopyExtraFieldsTestCase(StudioTestCase): + def setUp(self): + super(UnitCopyExtraFieldsTestCase, self).setUpBase() + self.target_channel = testdata.channel() + + # ------------------------------------------------------------------ # + # helpers + # ------------------------------------------------------------------ # + + def _make_unit_extra_fields(self, lesson_ids): + """Return a full extra_fields dict for a UNIT topic.""" + lo_id = "a" * 32 + assessment_id = "b" * 32 + options = { + "modality": modalities.UNIT, + "completion_criteria": { + "model": "mastery", + "threshold": { + "mastery_model": "pre_post_test", + "pre_post_test": { + "assessment_item_ids": [assessment_id], + "version_a_item_ids": [assessment_id], + "version_b_item_ids": [assessment_id], + }, + }, + }, + "learning_objectives": [{"id": lo_id, "text": "Learn something"}], + "assessment_objectives": {assessment_id: [lo_id]}, + "lesson_objectives": {lesson_id: [lo_id] for lesson_id in lesson_ids}, + } + return {"options": options} + + def _make_course_unit_lessons(self, num_lessons=2): + """ + Build: channel.main_tree > course > unit > [lesson_1, lesson_2, ...] + Returns (course, unit, lessons). + unit.extra_fields is set after lesson nodes are created so it can reference their PKs. + """ + course = ContentNode.objects.create( + title="Course", + kind_id=content_kinds.TOPIC, + parent=self.channel.main_tree, + extra_fields={"options": {"modality": modalities.COURSE}}, + ) + unit = ContentNode.objects.create( + title="Unit", + kind_id=content_kinds.TOPIC, + parent=course, + ) + lessons = [] + for i in range(num_lessons): + lesson = ContentNode.objects.create( + title="Lesson {}".format(i + 1), + kind_id=content_kinds.TOPIC, + parent=unit, + extra_fields={"options": {"modality": modalities.LESSON}}, + ) + lessons.append(lesson) + + unit.extra_fields = self._make_unit_extra_fields( + lesson_ids=[lesson.id for lesson in lessons], + ) + unit.save() + return course, unit, lessons + + # ------------------------------------------------------------------ # + # deep-copy integration tests + # ------------------------------------------------------------------ # + + def test_deep_copy_unit_remaps_lesson_objectives(self): + """Deep copy: lesson_objectives keys point at cloned lessons with correct LO values.""" + course, unit, lessons = self._make_course_unit_lessons(num_lessons=2) + source_lesson_objectives = unit.extra_fields["options"]["lesson_objectives"] + course.copy_to(self.target_channel.main_tree, batch_size=10000) + + copied_course = self.target_channel.main_tree.get_children().last() + copied_unit = copied_course.get_children().first() + copied_lessons = list(copied_unit.get_children().order_by("lft")) + + copied_unit.refresh_from_db() + lesson_objectives = copied_unit.extra_fields["options"]["lesson_objectives"] + + for original_lesson, copied_lesson in zip(lessons, copied_lessons): + self.assertIn( + copied_lesson.id, + lesson_objectives, + "Cloned lesson PK not found in lesson_objectives", + ) + self.assertEqual( + lesson_objectives[copied_lesson.id], + source_lesson_objectives[original_lesson.id], + ) + for original_lesson in lessons: + self.assertNotIn( + original_lesson.id, + lesson_objectives, + "Original lesson PK still present in lesson_objectives", + ) + + def test_deep_copy_non_unit_extra_fields_unchanged(self): + """Deep copy: extra_fields on Lesson and Course topics is copied verbatim.""" + course, unit, lessons = self._make_course_unit_lessons(num_lessons=1) + course.copy_to(self.target_channel.main_tree, batch_size=10000) + + copied_course = self.target_channel.main_tree.get_children().last() + copied_course.refresh_from_db() + copied_unit = copied_course.get_children().first() + copied_lesson = copied_unit.get_children().first() + copied_lesson.refresh_from_db() + + self.assertEqual(copied_course.extra_fields, course.extra_fields) + self.assertEqual(copied_lesson.extra_fields, lessons[0].extra_fields) + + # ------------------------------------------------------------------ # + # shallow-copy integration tests + # ------------------------------------------------------------------ # + + def test_shallow_copy_unit_remaps_lesson_objectives(self): + """Shallow copy: lesson_objectives keys point at cloned lessons with correct LO values.""" + course, unit, lessons = self._make_course_unit_lessons(num_lessons=2) + source_lesson_objectives = unit.extra_fields["options"]["lesson_objectives"] + course.copy_to(self.target_channel.main_tree, batch_size=1) + + copied_course = self.target_channel.main_tree.get_children().last() + copied_unit = copied_course.get_children().first() + copied_lessons = list(copied_unit.get_children().order_by("lft")) + + copied_unit.refresh_from_db() + lesson_objectives = copied_unit.extra_fields["options"]["lesson_objectives"] + + for original_lesson, copied_lesson in zip(lessons, copied_lessons): + self.assertIn(copied_lesson.id, lesson_objectives) + self.assertEqual( + lesson_objectives[copied_lesson.id], + source_lesson_objectives[original_lesson.id], + ) + for original_lesson in lessons: + self.assertNotIn(original_lesson.id, lesson_objectives) + + def test_shallow_copy_non_unit_extra_fields_unchanged(self): + """Shallow copy: extra_fields on Lesson and Course topics is copied verbatim.""" + course, unit, lessons = self._make_course_unit_lessons(num_lessons=1) + course.copy_to(self.target_channel.main_tree, batch_size=1) + + copied_course = self.target_channel.main_tree.get_children().last() + copied_course.refresh_from_db() + copied_unit = copied_course.get_children().first() + copied_lesson = copied_unit.get_children().first() + copied_lesson.refresh_from_db() + + self.assertEqual(copied_course.extra_fields, course.extra_fields) + self.assertEqual(copied_lesson.extra_fields, lessons[0].extra_fields) + + # ------------------------------------------------------------------ # + # excluded_descendants masking tests + # ------------------------------------------------------------------ # + + def test_excluded_lesson_entry_dropped_from_unit_deep(self): + """ + Deep copy: when a lesson is excluded, its entry is dropped from + the cloned Unit's lesson_objectives. + """ + course, unit, lessons = self._make_course_unit_lessons(num_lessons=2) + excluded_lesson = lessons[0] + kept_lesson = lessons[1] + source_lesson_objectives = unit.extra_fields["options"]["lesson_objectives"] + + course.copy_to( + self.target_channel.main_tree, + batch_size=10000, + excluded_descendants={excluded_lesson.node_id: True}, + ) + + copied_course = self.target_channel.main_tree.get_children().last() + copied_unit = copied_course.get_children().first() + copied_unit.refresh_from_db() + lesson_objectives = copied_unit.extra_fields["options"]["lesson_objectives"] + + self.assertNotIn(excluded_lesson.id, lesson_objectives) + copied_kept = copied_unit.get_children().first() + self.assertIn(copied_kept.id, lesson_objectives) + self.assertEqual( + lesson_objectives[copied_kept.id], + source_lesson_objectives[kept_lesson.id], + ) + self.assertEqual(len(lesson_objectives), 1) + + def test_excluded_lesson_entry_dropped_from_unit_shallow(self): + """ + Shallow copy: same behaviour as deep copy when a lesson is excluded. + """ + course, unit, lessons = self._make_course_unit_lessons(num_lessons=2) + excluded_lesson = lessons[0] + kept_lesson = lessons[1] + source_lesson_objectives = unit.extra_fields["options"]["lesson_objectives"] + + course.copy_to( + self.target_channel.main_tree, + batch_size=1, + excluded_descendants={excluded_lesson.node_id: True}, + ) + + copied_course = self.target_channel.main_tree.get_children().last() + copied_unit = copied_course.get_children().first() + copied_unit.refresh_from_db() + lesson_objectives = copied_unit.extra_fields["options"]["lesson_objectives"] + + self.assertNotIn(excluded_lesson.id, lesson_objectives) + copied_kept = copied_unit.get_children().first() + self.assertIn(copied_kept.id, lesson_objectives) + self.assertEqual( + lesson_objectives[copied_kept.id], + source_lesson_objectives[kept_lesson.id], + ) + self.assertEqual(len(lesson_objectives), 1) + + def test_excluded_unit_not_remapped(self): + """ + Deep copy: when the Unit itself is excluded from a Course copy, the Unit is + absent from the clone and no remap runs (no error, Course copy succeeds). + """ + course, unit, lessons = self._make_course_unit_lessons(num_lessons=2) + + course.copy_to( + self.target_channel.main_tree, + batch_size=10000, + excluded_descendants={unit.node_id: True}, + ) + + copied_course = self.target_channel.main_tree.get_children().last() + self.assertEqual(copied_course.get_children().count(), 0) + + def test_excluded_resource_inside_lesson_preserves_lesson_entry(self): + """ + Deep copy: when a resource inside a lesson is excluded (not the lesson + itself), the lesson's entry in lesson_objectives is preserved with the + cloned lesson's PK. + """ + course, unit, lessons = self._make_course_unit_lessons(num_lessons=1) + lesson = lessons[0] + source_lesson_objectives = unit.extra_fields["options"]["lesson_objectives"] + + license_obj = License.objects.filter( + copyright_holder_required=False, is_custom=False + ).first() + resource = ContentNode.objects.create( + title="Video", + kind_id=content_kinds.VIDEO, + parent=lesson, + license=license_obj, + ) + + course.copy_to( + self.target_channel.main_tree, + batch_size=10000, + excluded_descendants={resource.node_id: True}, + ) + + copied_course = self.target_channel.main_tree.get_children().last() + copied_unit = copied_course.get_children().first() + copied_unit.refresh_from_db() + lesson_objectives = copied_unit.extra_fields["options"]["lesson_objectives"] + + copied_lesson = copied_unit.get_children().first() + self.assertIn(copied_lesson.id, lesson_objectives) + self.assertNotIn(lesson.id, lesson_objectives) + self.assertEqual( + lesson_objectives[copied_lesson.id], + source_lesson_objectives[lesson.id], + ) + self.assertEqual(len(lesson_objectives), 1) + + def test_excluded_resource_inside_lesson_preserves_lesson_entry_shallow(self): + """ + Shallow copy: when a resource inside a lesson is excluded (not the lesson + itself), the lesson's entry in lesson_objectives is preserved with the + cloned lesson's PK. + """ + course, unit, lessons = self._make_course_unit_lessons(num_lessons=1) + lesson = lessons[0] + source_lesson_objectives = unit.extra_fields["options"]["lesson_objectives"] + + license_obj = License.objects.filter( + copyright_holder_required=False, is_custom=False + ).first() + resource = ContentNode.objects.create( + title="Video", + kind_id=content_kinds.VIDEO, + parent=lesson, + license=license_obj, + ) + + course.copy_to( + self.target_channel.main_tree, + batch_size=1, + excluded_descendants={resource.node_id: True}, + ) + + copied_course = self.target_channel.main_tree.get_children().last() + copied_unit = copied_course.get_children().first() + copied_unit.refresh_from_db() + lesson_objectives = copied_unit.extra_fields["options"]["lesson_objectives"] + + copied_lesson = copied_unit.get_children().first() + self.assertIn(copied_lesson.id, lesson_objectives) + self.assertNotIn(lesson.id, lesson_objectives) + self.assertEqual( + lesson_objectives[copied_lesson.id], + source_lesson_objectives[lesson.id], + ) + self.assertEqual(len(lesson_objectives), 1) + + # ------------------------------------------------------------------ # + # regression and stability tests + # ------------------------------------------------------------------ # + + def test_assessment_objectives_unchanged_after_copy(self): + """ + assessment_objectives keys (assessment_ids) are not node PKs and must + be preserved verbatim on the cloned Unit. + """ + course, unit, lessons = self._make_course_unit_lessons(num_lessons=1) + source_assessment_objectives = unit.extra_fields["options"][ + "assessment_objectives" + ] + + course.copy_to(self.target_channel.main_tree, batch_size=10000) + + copied_course = self.target_channel.main_tree.get_children().last() + copied_unit = copied_course.get_children().first() + copied_unit.refresh_from_db() + + self.assertEqual( + copied_unit.extra_fields["options"]["assessment_objectives"], + source_assessment_objectives, + ) + + def test_learning_objectives_list_unchanged_after_copy(self): + """ + learning_objectives list (LO IDs, not node IDs) is preserved verbatim + on the cloned Unit. + """ + course, unit, lessons = self._make_course_unit_lessons(num_lessons=1) + source_lo_list = unit.extra_fields["options"]["learning_objectives"] + + course.copy_to(self.target_channel.main_tree, batch_size=10000) + + copied_course = self.target_channel.main_tree.get_children().last() + copied_unit = copied_course.get_children().first() + copied_unit.refresh_from_db() + + self.assertEqual( + copied_unit.extra_fields["options"]["learning_objectives"], + source_lo_list, + ) + + def test_completion_criteria_unchanged_after_copy(self): + """ + completion_criteria (containing assessment_item_ids) is preserved + verbatim on the cloned Unit — assessment_ids are not node PKs. + """ + course, unit, lessons = self._make_course_unit_lessons(num_lessons=1) + source_cc = unit.extra_fields["options"]["completion_criteria"] + + course.copy_to(self.target_channel.main_tree, batch_size=10000) + + copied_course = self.target_channel.main_tree.get_children().last() + copied_unit = copied_course.get_children().first() + copied_unit.refresh_from_db() + + self.assertEqual( + copied_unit.extra_fields["options"]["completion_criteria"], + source_cc, + ) From d568852022cf8c8e94f85c88c1a591529d861bea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 16:52:53 +0000 Subject: [PATCH 22/34] chore(deps): bump ajv from 8.17.1 to 8.18.0 Bumps [ajv](https://github.com/ajv-validator/ajv) from 8.17.1 to 8.18.0. - [Release notes](https://github.com/ajv-validator/ajv/releases) - [Commits](https://github.com/ajv-validator/ajv/compare/v8.17.1...v8.18.0) --- updated-dependencies: - dependency-name: ajv dependency-version: 8.18.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- package.json | 2 +- pnpm-lock.yaml | 68 +++++++++++++++++++++++++------------------------- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/package.json b/package.json index d12d284d00..9bc4525541 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "@tiptap/extension-text-align": "^3.18.0", "@tiptap/starter-kit": "^3.13.0", "@tiptap/vue-2": "^3.13.0", - "ajv": "^8.12.0", + "ajv": "^8.18.0", "axios": "^1.15.0", "broadcast-channel": "^7.1.0", "core-js": "^3.47.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0c39cb507c..0093622756 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -36,8 +36,8 @@ importers: specifier: ^3.13.0 version: 3.13.0(@floating-ui/dom@1.7.4)(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)(vue@2.7.16) ajv: - specifier: ^8.12.0 - version: 8.17.1 + specifier: ^8.18.0 + version: 8.18.0 axios: specifier: ^1.15.0 version: 1.15.0 @@ -2227,11 +2227,11 @@ packages: peerDependencies: ajv: ^8.8.2 - ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ajv@6.14.0: + resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} - ajv@8.17.1: - resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + ajv@8.18.0: + resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} ansi-escapes@4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} @@ -3898,8 +3898,8 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fast-uri@3.0.6: - resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==} + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} fastest-levenshtein@1.0.16: resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} @@ -7943,9 +7943,9 @@ snapshots: '@adobe/css-tools@4.4.3': {} - '@apideck/better-ajv-errors@0.3.6(ajv@8.17.1)': + '@apideck/better-ajv-errors@0.3.6(ajv@8.18.0)': dependencies: - ajv: 8.17.1 + ajv: 8.18.0 json-schema: 0.4.0 jsonpointer: 5.0.1 leven: 3.1.0 @@ -8813,7 +8813,7 @@ snapshots: '@eslint/eslintrc@2.1.4': dependencies: - ajv: 6.12.6 + ajv: 6.14.0 debug: 4.4.1 espree: 9.6.1 globals: 13.24.0 @@ -10273,30 +10273,30 @@ snapshots: clean-stack: 2.2.0 indent-string: 4.0.0 - ajv-formats@2.1.1(ajv@8.17.1): + ajv-formats@2.1.1(ajv@8.18.0): optionalDependencies: - ajv: 8.17.1 + ajv: 8.18.0 - ajv-keywords@3.5.2(ajv@6.12.6): + ajv-keywords@3.5.2(ajv@6.14.0): dependencies: - ajv: 6.12.6 + ajv: 6.14.0 - ajv-keywords@5.1.0(ajv@8.17.1): + ajv-keywords@5.1.0(ajv@8.18.0): dependencies: - ajv: 8.17.1 + ajv: 8.18.0 fast-deep-equal: 3.1.3 - ajv@6.12.6: + ajv@6.14.0: dependencies: fast-deep-equal: 3.1.3 fast-json-stable-stringify: 2.1.0 json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ajv@8.17.1: + ajv@8.18.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.0.6 + fast-uri: 3.1.0 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -11954,7 +11954,7 @@ snapshots: '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 '@ungap/structured-clone': 1.3.0 - ajv: 6.12.6 + ajv: 6.14.0 chalk: 4.1.2 cross-spawn: 7.0.6 debug: 4.4.1 @@ -12126,7 +12126,7 @@ snapshots: fast-levenshtein@2.0.6: {} - fast-uri@3.0.6: {} + fast-uri@3.1.0: {} fastest-levenshtein@1.0.16: {} @@ -12464,7 +12464,7 @@ snapshots: har-validator@5.1.5: dependencies: - ajv: 6.12.6 + ajv: 6.14.0 har-schema: 2.0.0 hard-rejection@2.1.0: {} @@ -15344,22 +15344,22 @@ snapshots: schema-utils@3.3.0: dependencies: '@types/json-schema': 7.0.15 - ajv: 6.12.6 - ajv-keywords: 3.5.2(ajv@6.12.6) + ajv: 6.14.0 + ajv-keywords: 3.5.2(ajv@6.14.0) schema-utils@4.3.2: dependencies: '@types/json-schema': 7.0.15 - ajv: 8.17.1 - ajv-formats: 2.1.1(ajv@8.17.1) - ajv-keywords: 5.1.0(ajv@8.17.1) + ajv: 8.18.0 + ajv-formats: 2.1.1(ajv@8.18.0) + ajv-keywords: 5.1.0(ajv@8.18.0) schema-utils@4.3.3: dependencies: '@types/json-schema': 7.0.15 - ajv: 8.17.1 - ajv-formats: 2.1.1(ajv@8.17.1) - ajv-keywords: 5.1.0(ajv@8.17.1) + ajv: 8.18.0 + ajv-formats: 2.1.1(ajv@8.18.0) + ajv-keywords: 5.1.0(ajv@8.18.0) scss-tokenizer@0.4.3: dependencies: @@ -16007,7 +16007,7 @@ snapshots: table@6.9.0: dependencies: - ajv: 8.17.1 + ajv: 8.18.0 lodash.truncate: 4.4.2 slice-ansi: 4.0.0 string-width: 4.2.3 @@ -16853,7 +16853,7 @@ snapshots: workbox-build@7.4.0(@types/babel__core@7.20.5): dependencies: - '@apideck/better-ajv-errors': 0.3.6(ajv@8.17.1) + '@apideck/better-ajv-errors': 0.3.6(ajv@8.18.0) '@babel/core': 7.29.0 '@babel/preset-env': 7.29.2(@babel/core@7.29.0) '@babel/runtime': 7.28.6 @@ -16862,7 +16862,7 @@ snapshots: '@rollup/plugin-replace': 2.4.2(rollup@2.79.2) '@rollup/plugin-terser': 0.4.4(rollup@2.79.2) '@surma/rollup-plugin-off-main-thread': 2.2.3 - ajv: 8.17.1 + ajv: 8.18.0 common-tags: 1.8.2 fast-json-stable-stringify: 2.1.0 fs-extra: 9.1.0 From 730f7c9274b4d6c0a66663cbc3f523b15ee08d85 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 00:08:06 +0000 Subject: [PATCH 23/34] chore(deps): bump pnpm/action-setup from 5 to 6.0.3 Bumps [pnpm/action-setup](https://github.com/pnpm/action-setup) from 5 to 6.0.3. - [Release notes](https://github.com/pnpm/action-setup/releases) - [Commits](https://github.com/pnpm/action-setup/compare/v5...v6.0.3) --- updated-dependencies: - dependency-name: pnpm/action-setup dependency-version: 6.0.3 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/deploytest.yml | 4 ++-- .github/workflows/frontendtest.yml | 2 +- .github/workflows/i18n-download.yml | 2 +- .github/workflows/i18n-upload.yml | 2 +- .github/workflows/pre-commit.yml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/deploytest.yml b/.github/workflows/deploytest.yml index 7aacf1983c..03f71f6ce7 100644 --- a/.github/workflows/deploytest.yml +++ b/.github/workflows/deploytest.yml @@ -29,7 +29,7 @@ jobs: steps: - uses: actions/checkout@v6 - name: Use pnpm - uses: pnpm/action-setup@v5 + uses: pnpm/action-setup@v6.0.3 - name: Use Node.js uses: actions/setup-node@v6 with: @@ -59,7 +59,7 @@ jobs: # Use uv to install dependencies directly from requirements files uv pip sync requirements.txt - name: Use pnpm - uses: pnpm/action-setup@v5 + uses: pnpm/action-setup@v6.0.3 - name: Use Node.js uses: actions/setup-node@v6 with: diff --git a/.github/workflows/frontendtest.yml b/.github/workflows/frontendtest.yml index a3ece0db72..76de0dad94 100644 --- a/.github/workflows/frontendtest.yml +++ b/.github/workflows/frontendtest.yml @@ -29,7 +29,7 @@ jobs: steps: - uses: actions/checkout@v6 - name: Use pnpm - uses: pnpm/action-setup@v5 + uses: pnpm/action-setup@v6.0.3 - name: Use Node.js uses: actions/setup-node@v6 with: diff --git a/.github/workflows/i18n-download.yml b/.github/workflows/i18n-download.yml index 09761e7255..b4810d331d 100644 --- a/.github/workflows/i18n-download.yml +++ b/.github/workflows/i18n-download.yml @@ -22,7 +22,7 @@ jobs: run: uv pip sync requirements.txt - name: Use pnpm - uses: pnpm/action-setup@v5 + uses: pnpm/action-setup@v6.0.3 - name: Use Node.js uses: actions/setup-node@v6 diff --git a/.github/workflows/i18n-upload.yml b/.github/workflows/i18n-upload.yml index 0e087c82c6..38816e944d 100644 --- a/.github/workflows/i18n-upload.yml +++ b/.github/workflows/i18n-upload.yml @@ -22,7 +22,7 @@ jobs: run: uv pip sync requirements.txt - name: Use pnpm - uses: pnpm/action-setup@v5 + uses: pnpm/action-setup@v6.0.3 - name: Use Node.js uses: actions/setup-node@v6 diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 61478b2d7d..14ff4ac2fd 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -38,7 +38,7 @@ jobs: python-version: '3.10' ignore-nothing-to-cache: 'true' - name: Use pnpm - uses: pnpm/action-setup@v5 + uses: pnpm/action-setup@v6.0.3 - name: Use Node.js uses: actions/setup-node@v6 with: From 73cb9ff6d55c0e5fd80bc3c3b7f2b9035a7ec16f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 00:24:04 +0000 Subject: [PATCH 24/34] chore(deps-dev): bump pytest-django from 4.11.1 to 4.12.0 Bumps [pytest-django](https://github.com/pytest-dev/pytest-django) from 4.11.1 to 4.12.0. - [Release notes](https://github.com/pytest-dev/pytest-django/releases) - [Changelog](https://github.com/pytest-dev/pytest-django/blob/main/docs/changelog.rst) - [Commits](https://github.com/pytest-dev/pytest-django/compare/v4.11.1...v4.12.0) --- updated-dependencies: - dependency-name: pytest-django dependency-version: 4.12.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 24892483ac..ac6539f932 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -63,7 +63,7 @@ pytest==9.0.3 # -r requirements-dev.in # pytest-django # pytest-timeout -pytest-django==4.11.1 +pytest-django==4.12.0 # via -r requirements-dev.in pytest-timeout==2.4.0 # via -r requirements-dev.in From bfda49c101060a8f01482d03afda05569a7dd91f Mon Sep 17 00:00:00 2001 From: Richard Tibbles Date: Thu, 30 Apr 2026 18:03:14 -0700 Subject: [PATCH 25/34] Add 'opened' to pull-request-target trigger types --- .github/workflows/call-pull-request-target.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/call-pull-request-target.yml b/.github/workflows/call-pull-request-target.yml index 737c149000..e499bcddf8 100644 --- a/.github/workflows/call-pull-request-target.yml +++ b/.github/workflows/call-pull-request-target.yml @@ -1,7 +1,7 @@ name: Handle pull request events on: pull_request_target: - types: [review_requested, labeled] + types: [opened, review_requested, labeled] jobs: call-workflow: name: Call shared workflow From ec1c1d1b0e1dab6862beeb9552902da5ffd2bdb0 Mon Sep 17 00:00:00 2001 From: Alex Velez Date: Mon, 4 May 2026 11:20:26 -0500 Subject: [PATCH 26/34] Add deploy migrate commands --- Makefile | 2 +- ...ed_databse_exists.py => ensure_versioned_databases_exist.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename contentcuration/contentcuration/management/commands/{ensure_versioned_databse_exists.py => ensure_versioned_databases_exist.py} (100%) diff --git a/Makefile b/Makefile index 27c222a7d2..292efc9c99 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,7 @@ migrate: # 4) Remove the management command from this `deploy-migrate` recipe # 5) Repeat! deploy-migrate: - echo "Nothing to do here!" + python contentcuration/manage.py ensure_versioned_databases_exist & python contentcuration/manage.py create_channel_versions & wait contentnodegc: python contentcuration/manage.py garbage_collect diff --git a/contentcuration/contentcuration/management/commands/ensure_versioned_databse_exists.py b/contentcuration/contentcuration/management/commands/ensure_versioned_databases_exist.py similarity index 100% rename from contentcuration/contentcuration/management/commands/ensure_versioned_databse_exists.py rename to contentcuration/contentcuration/management/commands/ensure_versioned_databases_exist.py From 69df32164f8b74043249b0994cb6b4279909e80b Mon Sep 17 00:00:00 2001 From: Blaine Jester Date: Mon, 16 Mar 2026 15:52:01 -0700 Subject: [PATCH 27/34] Prevent syncing from incomplete source node --- .../tests/test_contentnodes.py | 28 +++++++++++++++++++ contentcuration/contentcuration/utils/sync.py | 9 ++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/contentcuration/contentcuration/tests/test_contentnodes.py b/contentcuration/contentcuration/tests/test_contentnodes.py index 329508a186..1f063264b3 100644 --- a/contentcuration/contentcuration/tests/test_contentnodes.py +++ b/contentcuration/contentcuration/tests/test_contentnodes.py @@ -824,6 +824,27 @@ def test_sync_after_no_changes(self): ) self._assert_same_files(orig_video, cloned_video) + def test_sync_but_incomplete(self): + orig_video, cloned_video = self._setup_original_and_deriative_nodes() + orig_video.license_id = None + orig_video.mark_complete() + self.assertFalse(orig_video.complete) + orig_video.save() + + self.assertTrue(cloned_video.complete) + + sync_node( + cloned_video, + sync_titles_and_descriptions=True, + sync_resource_details=True, + sync_files=True, + sync_assessment_items=True, + ) + + self.assertIsNotNone(cloned_video.license_id) + cloned_video.mark_complete() + self.assertTrue(cloned_video.complete) + def test_sync_with_subs(self): orig_video, cloned_video = self._setup_original_and_deriative_nodes() self._add_subs_to_video_node(orig_video, "fr") @@ -868,6 +889,13 @@ def _create_video_node(self, title, parent, withsubs=False): node_id="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", ) video_node = testdata.node(data, parent=parent) + video_node.license_id = 9 # Special Permissions + video_node.license_description = "Special permissions for testing" + video_node.copyright_holder = "LE" + # ensure the node is complete according to our logic + video_node.mark_complete() + self.assertTrue(video_node.complete) + video_node.save() if withsubs: self._add_subs_to_video_node(video_node, "fr") diff --git a/contentcuration/contentcuration/utils/sync.py b/contentcuration/contentcuration/utils/sync.py index 2987d1c75b..6f175d9a4f 100644 --- a/contentcuration/contentcuration/utils/sync.py +++ b/contentcuration/contentcuration/utils/sync.py @@ -53,11 +53,14 @@ def sync_node( sync_assessment_items=False, ): original_node = node.get_original_node() + if not original_node.complete: + logging.warning( + f"Refusing to sync node {node.pk} from incomplete source node: {original_node.pk}" + ) + return node if original_node.node_id != node.node_id: # Only update if node is not original logging.info( - "----- Syncing: {} from {}".format( - node.title, original_node.get_channel().name - ) + f"----- Syncing: {node.title} from {original_node.get_channel().name}" ) if sync_titles_and_descriptions: fields = [ From 216cf59c11592e941934e7951a3dac8c4d2f097c Mon Sep 17 00:00:00 2001 From: Richard Tibbles Date: Mon, 23 Mar 2026 16:13:37 -0700 Subject: [PATCH 28/34] Upgrade le-utils. --- .../frontend/shared/leUtils/Languages.js | 26 ++++++++++++++----- .../frontend/shared/leUtils/MasteryModels.js | 2 ++ requirements.in | 5 ++++ requirements.txt | 4 +++ 4 files changed, 30 insertions(+), 7 deletions(-) diff --git a/contentcuration/contentcuration/frontend/shared/leUtils/Languages.js b/contentcuration/contentcuration/frontend/shared/leUtils/Languages.js index 32365b7132..7c95090633 100644 --- a/contentcuration/contentcuration/frontend/shared/leUtils/Languages.js +++ b/contentcuration/contentcuration/frontend/shared/leUtils/Languages.js @@ -184,7 +184,7 @@ const LanguagesMap = new Map([ lang_subcode: null, readable_name: 'Brahui', native_name: 'Brahui', - lang_direction: 'ltr', + lang_direction: 'rtl', }, ], [ @@ -1130,7 +1130,7 @@ const LanguagesMap = new Map([ lang_subcode: null, readable_name: 'Northern Pashto', native_name: 'Northern Pashto', - lang_direction: 'ltr', + lang_direction: 'rtl', }, ], [ @@ -1471,7 +1471,7 @@ const LanguagesMap = new Map([ lang_subcode: null, readable_name: 'Southern Balochi', native_name: 'Southern Balochi', - lang_direction: 'ltr', + lang_direction: 'rtl', }, ], [ @@ -1626,7 +1626,7 @@ const LanguagesMap = new Map([ lang_subcode: null, readable_name: 'Uighur; Uyghur', native_name: 'Uy\u01a3urq\u0259, \u0626\u06c7\u064a\u063a\u06c7\u0631\u0686\u06d5\u200e', - lang_direction: 'ltr', + lang_direction: 'rtl', }, ], [ @@ -2540,6 +2540,17 @@ const LanguagesMap = new Map([ lang_direction: 'rtl', }, ], + [ + 'prs', + { + id: 'prs', + lang_code: 'prs', + lang_subcode: null, + readable_name: 'Dari', + native_name: '\u062f\u0631\u06cc', + lang_direction: 'rtl', + }, + ], [ 'arq', { @@ -2604,7 +2615,7 @@ const LanguagesMap = new Map([ readable_name: 'Kashmiri', native_name: '\u0915\u0936\u094d\u092e\u0940\u0930\u0940, \u0643\u0634\u0645\u064a\u0631\u064a\u200e', - lang_direction: 'ltr', + lang_direction: 'rtl', }, ], [ @@ -2740,7 +2751,7 @@ const LanguagesMap = new Map([ readable_name: 'Sindhi', native_name: '\u0938\u093f\u0928\u094d\u0927\u0940, \u0633\u0646\u068c\u064a\u060c \u0633\u0646\u062f\u06be\u06cc\u200e', - lang_direction: 'ltr', + lang_direction: 'rtl', }, ], [ @@ -2784,7 +2795,7 @@ const LanguagesMap = new Map([ lang_subcode: null, readable_name: 'Punjabi', native_name: '\u0a2a\u0a70\u0a1c\u0a3e\u0a2c\u0a40', - lang_direction: 'ltr', + lang_direction: 'rtl', }, ], [ @@ -3390,6 +3401,7 @@ export const LanguagesNames = { HE: 'he', UR: 'ur', AR: 'ar', + PRS: 'prs', ARQ: 'arq', FA: 'fa', PS: 'ps', diff --git a/contentcuration/contentcuration/frontend/shared/leUtils/MasteryModels.js b/contentcuration/contentcuration/frontend/shared/leUtils/MasteryModels.js index 57240f5c47..a909b5eefb 100644 --- a/contentcuration/contentcuration/frontend/shared/leUtils/MasteryModels.js +++ b/contentcuration/contentcuration/frontend/shared/leUtils/MasteryModels.js @@ -2,6 +2,7 @@ const MasteryModels = new Set([ 'do_all', 'm_of_n', + 'pre_post_test', 'num_correct_in_a_row_2', 'num_correct_in_a_row_3', 'num_correct_in_a_row_5', @@ -15,6 +16,7 @@ export const MasteryModelsList = Array.from(MasteryModels); export const MasteryModelsNames = { DO_ALL: 'do_all', M_OF_N: 'm_of_n', + PRE_POST_TEST: 'pre_post_test', NUM_CORRECT_IN_A_ROW_2: 'num_correct_in_a_row_2', NUM_CORRECT_IN_A_ROW_3: 'num_correct_in_a_row_3', NUM_CORRECT_IN_A_ROW_5: 'num_correct_in_a_row_5', diff --git a/requirements.in b/requirements.in index 2d59962414..7f525e3eec 100644 --- a/requirements.in +++ b/requirements.in @@ -5,8 +5,13 @@ djangorestframework==3.15.1 psycopg2-binary==2.9.11 django-js-reverse==0.10.2 django-registration==3.4 +<<<<<<< HEAD le-utils==0.2.17 gunicorn==25.1.0 +======= +le-utils==0.2.16 +gunicorn==23.0.0 +>>>>>>> ec61cd201 (Upgrade le-utils.) django-postmark==0.1.6 jsonfield==3.1.0 celery==5.6.3 diff --git a/requirements.txt b/requirements.txt index 0d66015b7a..fd13eb91ba 100644 --- a/requirements.txt +++ b/requirements.txt @@ -177,7 +177,11 @@ langcodes==3.5.1 # via -r requirements.in latex2mathml==3.78.1 # via -r requirements.in +<<<<<<< HEAD le-utils==0.2.17 +======= +le-utils==0.2.16 +>>>>>>> ec61cd201 (Upgrade le-utils.) # via -r requirements.in markdown-it-py==4.0.0 # via -r requirements.in From b8729e64c9340ce7d4c5ecf5e0bbc3ad37255fe2 Mon Sep 17 00:00:00 2001 From: Samson Akol Date: Mon, 13 Apr 2026 15:21:08 +0300 Subject: [PATCH 29/34] Fix TypeError when languageText called with non-language object VAutocomplete eagerly evaluates getText(internalValue) as the fallback argument to getValue, even when the fallback is never used. In multiple mode (used in SearchFilters), internalValue is an Array of selected ids. Arrays are objects in JS, so getPropertyFromItem does not short-circuit on the primitive guard and calls languageText with the array directly. Since arrays have no native_name property, this throws a TypeError. Guard languageText against item being null/undefined or lacking a native_name (covers arrays, partial objects, and any future edge cases). Fixes #5740. Co-Authored-By: Claude Sonnet 4.6 --- .../frontend/shared/views/LanguageDropdown.vue | 7 +++++++ .../shared/views/__tests__/languageDropdown.spec.js | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/contentcuration/contentcuration/frontend/shared/views/LanguageDropdown.vue b/contentcuration/contentcuration/frontend/shared/views/LanguageDropdown.vue index ddf39fdfee..2ab041dfa6 100644 --- a/contentcuration/contentcuration/frontend/shared/views/LanguageDropdown.vue +++ b/contentcuration/contentcuration/frontend/shared/views/LanguageDropdown.vue @@ -113,6 +113,13 @@ }, methods: { languageText(item) { + // VAutocomplete eagerly evaluates getText(internalValue) as a fallback arg to + // getValue, even when that fallback isn't needed. In multiple mode, internalValue + // is an Array, so languageText receives the array directly. Return early to avoid + // calling .split() on undefined. + if (Array.isArray(item)) { + return ''; + } const firstNativeName = item.native_name.split(',')[0].trim(); return this.$tr('languageItemText', { language: firstNativeName, code: item.id }); }, diff --git a/contentcuration/contentcuration/frontend/shared/views/__tests__/languageDropdown.spec.js b/contentcuration/contentcuration/frontend/shared/views/__tests__/languageDropdown.spec.js index 8bdcd165fd..13244d39b2 100644 --- a/contentcuration/contentcuration/frontend/shared/views/__tests__/languageDropdown.spec.js +++ b/contentcuration/contentcuration/frontend/shared/views/__tests__/languageDropdown.spec.js @@ -81,4 +81,15 @@ describe('languageDropdown', () => { const item = { native_name: '', id: 'de' }; expect(wrapper.vm.languageText(item)).toBe(' (de)'); }); + + it('returns empty string when called with an array (multiple mode VAutocomplete internal call)', () => { + const wrapper = shallowMount(LanguageDropdown, { + mocks: { + $tr: (key, params) => `${params.language} (${params.code})`, + }, + }); + // VAutocomplete eagerly evaluates getText(internalValue) as a fallback to getValue. + // In multiple mode, internalValue is an Array, so languageText receives the array. + expect(wrapper.vm.languageText(['en', 'fr'])).toBe(''); + }); }); From 1ae8ff40de3b345edbc40cc668ba7fa65919c4dc Mon Sep 17 00:00:00 2001 From: Blaine Jester Date: Mon, 13 Apr 2026 14:40:34 -0700 Subject: [PATCH 30/34] Set a hardcoded cache-control for /content/storage --- docker/nginx/includes/content/_cache.conf | 8 +++++ .../content/develop-studio-content.conf | 34 +++++++++++++++++-- .../includes/content/studio-content.conf | 11 ++++++ 3 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 docker/nginx/includes/content/_cache.conf diff --git a/docker/nginx/includes/content/_cache.conf b/docker/nginx/includes/content/_cache.conf new file mode 100644 index 0000000000..942b1a74ef --- /dev/null +++ b/docker/nginx/includes/content/_cache.conf @@ -0,0 +1,8 @@ +# location {} settings for /content caching +# used by files in this directory, via `include` directive + +# ignore cache-control from upstream so this value is authoritative +proxy_hide_header Cache-Control; + +# content is md5-addressed, so cache aggressively for successful responses +add_header Cache-Control "public, max-age=31536000, immutable, no-transform"; diff --git a/docker/nginx/includes/content/develop-studio-content.conf b/docker/nginx/includes/content/develop-studio-content.conf index 5a1c2ed181..19f528a7be 100644 --- a/docker/nginx/includes/content/develop-studio-content.conf +++ b/docker/nginx/includes/content/develop-studio-content.conf @@ -17,16 +17,46 @@ location @production { proxy_pass https://studio-content.storage.googleapis.com; } +location @hotfixes_storage { + include /etc/nginx/includes/content/_proxy.conf; + include /etc/nginx/includes/content/_cache.conf; + + # this is the magic that allows us to intercept errors and try the next location + proxy_intercept_errors on; + recursive_error_pages on; + error_page 404 = @production_storage; + + proxy_pass https://develop-studio-content.storage.googleapis.com; +} + +location @production_storage { + include /etc/nginx/includes/content/_proxy.conf; + include /etc/nginx/includes/content/_cache.conf; + + proxy_pass https://studio-content.storage.googleapis.com; +} + location @nowhere { return 404; } +# Note on try_files +# ----------------- +# try_files will only use one named route, and it uses the last one. Although, we can't just pass +# one named route, because it fails. + +location ^~ /content/storage/ { + # ensure that the /content/ prefix is stripped from the request + rewrite ^/content/(.*)$ /$1 break; + + # check staging bucket first, then fall back to production + try_files @nowhere @hotfixes_storage; +} + location /content/ { # ensure that the /content/ prefix is stripped from the request rewrite ^/content/(.*)$ /$1 break; # check the emulator bucket first, then cloud development bucket, then fall back to production - # try_files will only use one named route, and it uses the last one. Although, we can just - # pass one named route, because it fails. try_files @nowhere @hotfixes; } diff --git a/docker/nginx/includes/content/studio-content.conf b/docker/nginx/includes/content/studio-content.conf index f59650f659..0b17852681 100644 --- a/docker/nginx/includes/content/studio-content.conf +++ b/docker/nginx/includes/content/studio-content.conf @@ -1,5 +1,16 @@ # DO NOT RENAME: this file is named after the primary bucket it proxies to +location ^~ /content/storage/ { + include /etc/nginx/includes/content/_proxy.conf; + include /etc/nginx/includes/content/_cache.conf; + + # ensure that the /content/ prefix is stripped from the request + rewrite ^/content/(.*)$ /$1 break; + + # just direct proxy to the bucket + proxy_pass https://studio-content.storage.googleapis.com; +} + location /content/ { include /etc/nginx/includes/content/_proxy.conf; From aac19667a8aea360919bae179659e72f59a3bffd Mon Sep 17 00:00:00 2001 From: Blaine Jester Date: Mon, 13 Apr 2026 15:17:19 -0700 Subject: [PATCH 31/34] Correct comment in other file --- docker/nginx/includes/content/default.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/nginx/includes/content/default.conf b/docker/nginx/includes/content/default.conf index c2c95df613..2966c3a7f6 100644 --- a/docker/nginx/includes/content/default.conf +++ b/docker/nginx/includes/content/default.conf @@ -39,7 +39,7 @@ location @nowhere { location /content/ { # check the emulator bucket first, then cloud development bucket, then fall back to production - # try_files will only use one named route, and it uses the last one. Although, we can just + # try_files will only use one named route, and it uses the last one. Although, we can't just # pass one named route, because it fails. try_files @nowhere @emulator; } From 9d8f8744db9bb9e6cd564fb1bad5ae6710df879d Mon Sep 17 00:00:00 2001 From: Blaine Jester Date: Mon, 20 Apr 2026 13:10:18 -0700 Subject: [PATCH 32/34] Remove expires header from GCS response --- docker/nginx/includes/content/_cache.conf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/nginx/includes/content/_cache.conf b/docker/nginx/includes/content/_cache.conf index 942b1a74ef..8f83f73cd8 100644 --- a/docker/nginx/includes/content/_cache.conf +++ b/docker/nginx/includes/content/_cache.conf @@ -1,8 +1,9 @@ # location {} settings for /content caching # used by files in this directory, via `include` directive -# ignore cache-control from upstream so this value is authoritative +# ignore 'expires' and 'cache-control' headers from upstream so this value is authoritative proxy_hide_header Cache-Control; +proxy_hide_header Expires; # content is md5-addressed, so cache aggressively for successful responses add_header Cache-Control "public, max-age=31536000, immutable, no-transform"; From 970519287944b0f89a2e4c0b3c44b6b8f1265a55 Mon Sep 17 00:00:00 2001 From: Blaine Jester Date: Mon, 16 Mar 2026 12:14:17 -0700 Subject: [PATCH 33/34] Convert management command to address licensing metadata issues --- .../commands/fix_missing_import_sources.py | 200 ++++++++++++---- .../commands/licensing_fixes_lookup.csv | 67 ++++++ .../test_fix_missing_import_sources.py | 220 +++++++++++++----- 3 files changed, 379 insertions(+), 108 deletions(-) create mode 100644 contentcuration/contentcuration/management/commands/licensing_fixes_lookup.csv diff --git a/contentcuration/contentcuration/management/commands/fix_missing_import_sources.py b/contentcuration/contentcuration/management/commands/fix_missing_import_sources.py index 6f40cb569a..0adbf1baca 100644 --- a/contentcuration/contentcuration/management/commands/fix_missing_import_sources.py +++ b/contentcuration/contentcuration/management/commands/fix_missing_import_sources.py @@ -1,6 +1,11 @@ import csv +import io import logging import time +import uuid +from pathlib import Path +from typing import Optional +from typing import Tuple from django.core.management.base import BaseCommand from django.db.models import Exists @@ -12,17 +17,97 @@ from contentcuration.models import Channel from contentcuration.models import ContentNode +from contentcuration.models import License logger = logging.getLogger(__name__) +class LicensingFixesLookup(object): + """Consolidates logic for reading and processing the licensing fixes from the CSV""" + + def __init__(self): + self._lookup = {} + self._license_lookup = {} + + def load(self, fp: io.TextIOWrapper): + """Loads the data from the CSV file, and the necessary license data from the database""" + reader = csv.DictReader(fp) + license_names = set() + + # create a lookup index by channel ID from the CSV data + for row in reader: + lookup_key = f"{uuid.UUID(row['channel_id']).hex}:{row.get('kind', '')}" + self._lookup[lookup_key] = row + if row["license_name"]: + license_names.add(row["license_name"]) + + # load all licenses, regardless of whether they are named in the CSV + license_lookup_by_name = {} + for lic in License.objects.all(): + self._license_lookup[lic.id] = lic + license_lookup_by_name[lic.license_name] = lic + license_names.discard(lic.license_name) + + # ensure we've found all the licenses + if len(license_names): + raise ValueError(f"Could not find all licenses: {license_names}") + + # we now are certain all licenses are found + for info in self._lookup.values(): + if info["license_name"]: + info["license_id"] = license_lookup_by_name[info["license_name"]].id + + def get_info( + self, + channel_id: str, + kind: str, + license_id: Optional[int], + license_description: Optional[str], + copyright_holder: Optional[str], + ) -> Tuple[Optional[int], Optional[str], Optional[str]]: + """ + Determines the complete licensing metadata, given the current metadata, and comparing it + with what would make the node complete. + + :param channel_id: The channel the node was sourced from + :param kind: The content kind of the node + :param license_id: The current license_id of the node + :param license_description: The current license_description of the node + :param copyright_holder: The current copyright_holder of the node + :return: A tuple of (license_id, license_description, copyright_holder) to use on the node + """ + # first check kind-specific metadata, fallback to channel-wide (no kind) + info = self._lookup.get(f"{channel_id}:{kind}", None) + if info is None: + info = self._lookup.get(f"{channel_id}:", None) + + if info is None: + logger.warning(f"Failed to find licensing info for channel: {channel_id}") + return license_id, license_description, copyright_holder + + if not license_id: + license_id = info["license_id"] + + if not license_id: + return None, license_description, copyright_holder + + license_obj = self._license_lookup.get(license_id) + + if license_obj.is_custom and not license_description: + license_description = info["license_description"] + + if license_obj.copyright_holder_required and not copyright_holder: + copyright_holder = info["copyright_holder"] + + return license_id, license_description, copyright_holder + + class Command(BaseCommand): """ Audits nodes that have imported content from public channels and whether the imported content - has a missing source node. - - TODO: this does not yet FIX them + has a missing source node. We've determined that pretty much all of these have incomplete + licensing data """ def handle(self, *args, **options): @@ -71,32 +156,27 @@ def handle(self, *args, **options): logger.info("=== Iterating over private destination channels. ===") channel_count = 0 - total_node_count = 0 - - with open("fix_missing_import_sources.csv", "w", newline="") as csv_file: - csv_writer = csv.DictWriter( - csv_file, - fieldnames=[ - "channel_id", - "channel_name", - "contentnode_id", - "contentnode_title", - "public_channel_id", - "public_channel_name", - "public_channel_deleted", - ], - ) - csv_writer.writeheader() + total_fixed = 0 + lookup = LicensingFixesLookup() + + command_dir = Path(__file__).parent + csv_path = command_dir / "licensing_fixes_lookup.csv" + + with csv_path.open("r", encoding="utf-8", newline="") as csv_file: + lookup.load(csv_file) - for channel in destination_channels.iterator(): - node_count = self.handle_channel(csv_writer, channel) + # skip using an iterator here, to limit transaction duration to `handle_channel` + for channel in destination_channels: + node_count = self.handle_channel(lookup, channel) - if node_count > 0: - total_node_count += node_count - channel_count += 1 + if node_count > 0: + total_fixed += node_count + channel_count += 1 logger.info("=== Done iterating over private destination channels. ===") - logger.info(f"Found {total_node_count} nodes across {channel_count} channels.") + logger.info( + f"Fixed incomplete licensing data on {total_fixed} nodes across {channel_count} channels." + ) logger.info(f"Finished in {time.time() - start}") def get_public_cte(self) -> With: @@ -110,7 +190,15 @@ def get_public_cte(self) -> With: name="public_cte", ) - def handle_channel(self, csv_writer: csv.DictWriter, channel: dict) -> int: + def handle_channel(self, lookup: LicensingFixesLookup, channel: dict) -> int: + """ + Goes through the nodes of the channel, that were imported from public channels, but no + longer have a valid source node. For each node, it applies license metadata as necessary + + :param lookup: The lookup utility to pull licensing data from + :param channel: The channel to fix + :return: The total node count that are now marked complete as a result of the fixes + """ public_cte = self.get_public_cte() channel_id = channel["id"] channel_name = channel["name"] @@ -136,29 +224,51 @@ def handle_channel(self, csv_writer: csv.DictWriter, channel: dict) -> int: ) ) ) - .values( - "public_channel_id", - "public_channel_name", - "public_channel_deleted", - contentnode_id=F("id"), - contentnode_title=F("title"), - ) ) # Count and log results node_count = missing_source_nodes.count() + processed = 0 + was_complete = 0 + unfixed = 0 + now_complete = 0 - # TODO: this will be replaced with logic to correct the missing source nodes - if node_count > 0: + def _log(): logger.info( - f"{channel_id}:{channel_name}\t{node_count} node(s) with missing source nodes." + f"Fixing {channel_id}:{channel_name}\ttotal: {node_count}; before: {was_complete} unfixed: {unfixed}; after: {now_complete};" ) - row_dict = { - "channel_id": channel_id, - "channel_name": channel_name, - } - for node_dict in missing_source_nodes.iterator(): - row_dict.update(node_dict) - csv_writer.writerow(row_dict) - - return node_count + + if node_count > 0: + for node in missing_source_nodes.iterator(): + # determine the new license metadata + license_id, license_description, copyright_holder = lookup.get_info( + node.original_channel_id, + node.kind_id, + node.license_id, + node.license_description, + node.copyright_holder, + ) + + # if there isn't a license, there's nothing to do + if not license_id: + unfixed += 1 + # cannot fix + continue + + if node.complete: + was_complete += 1 + + # apply updates + node.license_id = license_id + node.license_description = license_description + node.copyright_holder = copyright_holder + if not node.mark_complete(): + now_complete += 1 + node.save() + processed += 1 + if processed % 100 == 0: + _log() + + _log() + + return now_complete - was_complete diff --git a/contentcuration/contentcuration/management/commands/licensing_fixes_lookup.csv b/contentcuration/contentcuration/management/commands/licensing_fixes_lookup.csv new file mode 100644 index 0000000000..c510984037 --- /dev/null +++ b/contentcuration/contentcuration/management/commands/licensing_fixes_lookup.csv @@ -0,0 +1,67 @@ +channel_id,channel_name,kind,license_id,license_name,license_description,copyright_holder +f9d3e0e4-6ea2-5789-bbed-672ff6a399ed,African Storybook Library (multiple languages),,,CC BY,,African Storybook Initiative +d0ef6f71-e4fe-4e54-bb87-d7dab5eeaae2,Be Strong: Internet safety resources,,,CC BY-NC-ND,,Vodafone +2d7b056d-668a-58ee-9244-ccf76108cbdb,Book Dash,,,CC BY,,http://bookdash.org/ +922e9c57-6c2f-59e5-9389-142b136308ff,Career Girls,,,Special Permissions,For use on Kolibri,Career Girls +da53f90b-1be2-5752-a046-82bbc353659f,Ciencia NASA,,,CC BY,,NASA +0294a064-f722-4899-887c-e07bd47f9991,Citoyennes de la Terre,,,CC BY,,Florence Piron +604ad3b8-5d84-4dd8-9ee7-0fa12a9a5a6e,CREE+,,,CC BY-NC-SA,,"Publicado por el Lic. Edelberto Andino(edelberto.andino.ea@gmail.com) para ser utilizado con fines educativos únicamente, no debe ser utilizado con fines lucrativos de ninguna índole." +ef2ead65-de76-4ea4-a27b-ba6df5282c74,CSpathshala - सीएसपाठशाला (हिंदी),,,CC BY,,ए सि एम् इंडिया +7e68bc59-d430-4e71-8a07-50b1b87125ad,Cultura Emprendedora,,,CC BY-NC-SA,"The Attribution-NonCommercial-ShareAlike License lets others remix, tweak, and build upon your work non-commercially, as long as they credit you and license their new creations under the identical terms.",Junta de Andalucia +c51a0f84-2fed-427c-95ac-ff9bb4a21e3c,EENET Inclusive Education Training Materials,,,CC BY-NC-SA,,Enabling Education Network (EENET) +0e173fca-6e90-52f8-a474-a2fb84055faf,Global Digital Library - Book Catalog,,,CC BY,,Enabling Writers Initiative +624e09bb-5eeb-4d20-aa8d-e62e7b4778a0,How to get started with Kolibri,,,CC BY-NC,,Learning Equality +378cf412-8c85-4c27-95c1-00b5aca7a3ed,Inclusive Home Learning Activities,,,CC BY,"The Attribution License lets others distribute, remix, tweak, and build upon your work, even commercially, as long as they credit you for the original creation. This is the most accommodating of licenses offered. Recommended for maximum dissemination and use of licensed materials.",EENET – Enabling Education Network +d76da4d3-6cfd-5927-9b57-5dfc6017aa13,Kamkalima (العربيّة),,,CC BY-NC-ND,,Kamkalima +2fd54ca4-7a8f-59c9-9fce-faaa3894c19e,Khan Academy (English - CBSE India Curriculum),video,,CC BY-NC-SA,,Khan Academy +2fd54ca4-7a8f-59c9-9fce-faaa3894c19e,Khan Academy (English - CBSE India Curriculum),exercise,,Special Permissions,Permission granted to distribute through Kolibri for non-commercial use,Khan Academy +c9d7f950-ab6b-5a11-99e3-d6c10d7f0103,Khan Academy (English - US curriculum),video,,CC BY-NC-SA,,Khan Academy +c9d7f950-ab6b-5a11-99e3-d6c10d7f0103,Khan Academy (English - US curriculum),exercise,,Special Permissions,Permission granted to distribute through Kolibri for non-commercial use,Khan Academy +c1f2b7e6-ac9f-56a2-bb44-fa7a48b66dce,Khan Academy (Español),video,,CC BY-NC-SA,,Khan Academy +c1f2b7e6-ac9f-56a2-bb44-fa7a48b66dce,Khan Academy (Español),exercise,,Special Permissions,Permission granted to distribute through Kolibri for non-commercial use,Khan Academy +878ec2e6-f88c-5c26-8b1b-e6f202833cd4,Khan Academy (Français),video,,CC BY-NC-SA,,Khan Academy +878ec2e6-f88c-5c26-8b1b-e6f202833cd4,Khan Academy (Français),exercise,,Special Permissions,Permission granted to distribute through Kolibri for non-commercial use,Khan Academy +801a5f02-9420-5569-8918-edcff6494185,Khan Academy (Italiano),video,,CC BY-NC-SA,,Khan Academy +801a5f02-9420-5569-8918-edcff6494185,Khan Academy (Italiano),exercise,,Special Permissions,Permission granted to distribute through Kolibri for non-commercial use,Khan Academy +ec164fee-25ee-5262-96e6-8f7c10b1e169,Khan Academy (Kiswahili),video,,CC BY-NC-SA,,Khan Academy +ec164fee-25ee-5262-96e6-8f7c10b1e169,Khan Academy (Kiswahili),exercise,,Special Permissions,Permission granted to distribute through Kolibri for non-commercial use,Khan Academy +2ac071c4-6723-54f2-aa78-953448f81e50,Khan Academy (Português - Brasil),video,,CC BY-NC-SA,,Khan Academy +2ac071c4-6723-54f2-aa78-953448f81e50,Khan Academy (Português - Brasil),exercise,,Special Permissions,Permission granted to distribute through Kolibri for non-commercial use,Khan Academy +c3231d84-4f8d-5bb1-b4cb-c6a7ddd91eb7,Khan Academy (Português (Portugal)),video,,CC BY-NC-SA,,Khan Academy +c3231d84-4f8d-5bb1-b4cb-c6a7ddd91eb7,Khan Academy (Português (Portugal)),exercise,,Special Permissions,Permission granted to distribute through Kolibri for non-commercial use,Khan Academy +09ee940e-1069-53a2-b671-6e1020a0ce3f,Khan Academy (български език),video,,CC BY-NC-SA,,Khan Academy +09ee940e-1069-53a2-b671-6e1020a0ce3f,Khan Academy (български език),exercise,,Special Permissions,Permission granted to distribute through Kolibri for non-commercial use,Khan Academy +a53592c9-72a8-594e-9b69-5aa127493ff6,Khan Academy (हिन्दी),video,,CC BY-NC-SA,,Khan Academy +a53592c9-72a8-594e-9b69-5aa127493ff6,Khan Academy (हिन्दी),exercise,,Special Permissions,Permission granted to distribute through Kolibri for non-commercial use,Khan Academy +a03496a6-de09-5e7b-a9d2-4291a487c78d,Khan Academy (বাংলা),video,,CC BY-NC-SA,,Khan Academy +a03496a6-de09-5e7b-a9d2-4291a487c78d,Khan Academy (বাংলা),exercise,,Special Permissions,Permission granted to distribute through Kolibri for non-commercial use,Khan Academy +5357e525-81c3-567d-a4f5-6d56badfeac7,Khan Academy (ગુજરાતી),video,,CC BY-NC-SA,,Khan Academy +5357e525-81c3-567d-a4f5-6d56badfeac7,Khan Academy (ગુજરાતી),exercise,,Special Permissions,Permission granted to distribute through Kolibri for non-commercial use,Khan Academy +2b608c6f-d4c3-5c34-b738-7e3dd7b53265,Khan Academy (ဗမာစာ),video,,CC BY-NC-SA,,Khan Academy +2b608c6f-d4c3-5c34-b738-7e3dd7b53265,Khan Academy (ဗမာစာ),exercise,,Special Permissions,Permission granted to distribute through Kolibri for non-commercial use,Khan Academy +f5b71417-b1f6-57fc-a4d1-aaecd23e4067,Khan Academy (ភាសាខ្មែរ),video,,CC BY-NC-SA,,Khan Academy +f5b71417-b1f6-57fc-a4d1-aaecd23e4067,Khan Academy (ភាសាខ្មែរ),exercise,,Special Permissions,Permission granted to distribute through Kolibri for non-commercial use,Khan Academy +ec599e77-f9ad-5802-8975-e8a26e6f1821,Khan Academy (中文(中国)),video,,CC BY-NC-SA,,Khan Academy +ec599e77-f9ad-5802-8975-e8a26e6f1822,Khan Academy (中文(中国)),exercise,,Special Permissions,Permission granted to distribute through Kolibri for non-commercial use,Khan Academy +913efe9f-14c6-5cb1-b234-02f21f056e99,MIT Blossoms,,,CC BY-NC-SA,,MIT Blossoms +fc47aee8-2e01-53e2-a301-97d3fdee1128,Open Stax,,,CC BY,"The Attribution License lets others distribute, remix, tweak, and build upon your work, even commercially, as long as they credit you for the original creation. This is the most accommodating of licenses offered. Recommended for maximum dissemination and use of licensed materials.",Rice University +b8bd7770-063d-40a8-bd9b-30d4703927b5,PBS SoCal: Family Math,,,All Rights Reserved,,PBS SoCal +197934f1-4430-5350-b582-0c7c4dd8e194,PhET Interactive Simulations,,,CC BY,,"PhET Interactive Simulations, University of Colorado Boulder" +aa254505-59b5-5bd7-9bc9-0c09dfb805d2,PhET simulações interativas,,,CC BY,,"PhET Interactive Simulations, University of Colorado Boulder" +889f0c34-b275-507a-b8d3-7d2da2d03aa9,PhET – інтерактивне моделювання,,,CC BY,,"PhET Interactive Simulations, University of Colorado Boulder" +f6cb302e-f659-4db4-b4a0-4b4991a595c2,Plan Educativo TIC Basico,,,CC BY-NC-SA,"The Attribution-NonCommercial-ShareAlike License lets others remix, tweak, and build upon your work non-commercially, as long as they credit you and license their new creations under the identical terms.",Junta de Andalucia +e832106c-6398-54e1-8161-6015a8b87910,PraDigi,,,CC BY-NC-SA,"The Attribution-NonCommercial-ShareAlike License lets others remix, tweak, and build upon your work non-commercially, as long as they credit you and license their new creations under the identical terms.",PraDigi +131e543d-becf-5776-bb13-cfcfddf05605,Pratham Books' StoryWeaver,,,CC BY,"The Attribution License lets others distribute, remix, tweak, and build upon your work, even commercially, as long as they credit you for the original creation. This is the most accommodating of licenses offered. Recommended for maximum dissemination and use of licensed materials.",Pratham Books +f758ac6a-d39c-452f-9566-58da6ad7d3cc,Project Based Learning with Kolibri,,,CC BY,,Learning Equality +305b12ea-5ea8-4fa1-8f93-3705c23f5ee0,School of Thought,,,CC BY,,School of Thought +3e464ee1-2f6a-50a7-81cd-df59147b48b1,Sikana (English),,,CC BY-NC-ND,,Sikana Education +30c71c99-c42c-57d1-81e8-aeafd2e15e5f,Sikana (Español),,,CC BY-NC-ND,"The Attribution-NonCommercial-NoDerivs License is the most restrictive of our six main licenses, only allowing others to download your works and share them with others as long as they credit you, but they can't change them in any way or use them commercially.",Sikana Education +8ef625db-6e86-506c-9a3b-ac891e413fff,Sikana (Français),,,CC BY-NC-ND,"The Attribution-NonCommercial-NoDerivs License is the most restrictive of our six main licenses, only allowing others to download your works and share them with others as long as they credit you, but they can't change them in any way or use them commercially.",Sikana Education +f4715a77-6972-5c72-9d25-d29977b8b308,Similasyon Enteraktif PhET,,,CC BY,,"PhET Interactive Simulations, University of Colorado Boulder" +8fa678af-1dd0-5329-bf32-18c549b84996,Simulaciones interactivas PhET,,,CC BY,,"PhET Interactive Simulations, University of Colorado Boulder" +a9b25ac9-8147-42c8-83ce-1b0579448337,TESSA - Teacher Resources,,,CC BY-NC-SA,,Open University +74f36493-bb47-5b62-935f-a8705ed59fed,Thoughtful Learning,,,CC BY-NC-SA,"The Attribution-NonCommercial-ShareAlike License lets others remix, tweak, and build upon your work non-commercially, as long as they credit you and license their new creations under the identical terms.",Thoughtful Learning +000409f8-1dbe-5d1b-a671-01cb9fed4530,Touchable Earth (en),,,Special Permissions,Permission has been granted by Touchable Earth to distribute this content through Kolibri.,Touchable Earth Foundation (New Zealand) +b336c2e2-c45c-53d5-b24e-5c476a54b077,Touchable Earth (fr),,,Special Permissions,Permission has been granted by Touchable Earth to distribute this content through Kolibri.,Touchable Earth Foundation (New Zealand) +08a53136-a155-5f64-b049-6b3e1318b0cd,Ubongo Kids,,,CC BY-NC-ND,"The Attribution-NonCommercial-NoDerivs License is the most restrictive of our six main licenses, only allowing others to download your works and share them with others as long as they credit you, but they can't change them in any way or use them commercially.",Ubongo Media +237e5975-bce2-5bf6-aff3-98f4c17516f3,,,,,, diff --git a/contentcuration/contentcuration/tests/management/commands/test_fix_missing_import_sources.py b/contentcuration/contentcuration/tests/management/commands/test_fix_missing_import_sources.py index e624313ff8..8e9fa0f716 100644 --- a/contentcuration/contentcuration/tests/management/commands/test_fix_missing_import_sources.py +++ b/contentcuration/contentcuration/tests/management/commands/test_fix_missing_import_sources.py @@ -1,8 +1,11 @@ -from unittest.mock import mock_open +from pathlib import Path from unittest.mock import patch from django.core.management import call_command +from contentcuration.management.commands.fix_missing_import_sources import ( + LicensingFixesLookup, +) from contentcuration.tests import testdata from contentcuration.tests.base import StudioTestCase @@ -11,22 +14,7 @@ class CommandTestCase(StudioTestCase): """Test suite for the fix_missing_import_sources management command""" def setUp(self): - open_patcher = patch( - "contentcuration.management.commands.fix_missing_import_sources.open", - mock_open(), - ) - self.mock_open = open_patcher.start() - self.mock_file = self.mock_open.return_value - self.mock_file.__enter__.return_value = self.mock_file - self.addCleanup(open_patcher.stop) - - csv_writer_patcher = patch( - "contentcuration.management.commands.fix_missing_import_sources.csv.DictWriter" - ) - self.mock_csv_writer = csv_writer_patcher.start() - self.mock_csv_writer_instance = self.mock_csv_writer.return_value - self.addCleanup(csv_writer_patcher.stop) - + super().setUp() self.public_channel = testdata.channel("Public Channel") self.public_channel.public = True self.public_channel.save() @@ -43,58 +31,164 @@ def setUp(self): target=self.private_channel.main_tree ) - def test_handle__opens_csv_file(self): - call_command("fix_missing_import_sources") - - self.mock_open.assert_called_once_with( - "fix_missing_import_sources.csv", "w", newline="" + def test_handle__uses_lookup_and_applies_fix_for_missing_source(self): + self.original_node.delete() + special_permissions_id = 9 + self.copied_node.refresh_from_db() + self.copied_node.license = None + self.copied_node.license_description = "" + self.copied_node.copyright_holder = "" + self.copied_node.save() + + with patch( + "contentcuration.management.commands.fix_missing_import_sources.LicensingFixesLookup" + ) as lookup_cls: + lookup = lookup_cls.return_value + lookup.get_info.return_value = ( + special_permissions_id, + "Permission granted to distribute through Kolibri for non-commercial use", + "Khan Academy", + ) + + call_command("fix_missing_import_sources") + + lookup_cls.assert_called_once() + lookup.load.assert_called_once() + lookup.get_info.assert_called_once() + + self.copied_node.refresh_from_db() + self.assertEqual(self.copied_node.license_id, special_permissions_id) + self.assertEqual( + self.copied_node.license_description, + "Permission granted to distribute through Kolibri for non-commercial use", ) + self.assertEqual(self.copied_node.copyright_holder, "Khan Academy") - self.mock_csv_writer.assert_called_once_with( - self.mock_file, - fieldnames=[ - "channel_id", - "channel_name", - "contentnode_id", - "contentnode_title", - "public_channel_id", - "public_channel_name", - "public_channel_deleted", - ], + def test_handle__applies_fix_for_deleted_public_channel(self): + cc_by_nc_sa_id = 5 + self.public_channel.deleted = True + self.public_channel.save(actor_id=testdata.user().id) + self.copied_node.license = None + self.copied_node.license_description = "" + self.copied_node.copyright_holder = "" + self.copied_node.save() + + with patch( + "contentcuration.management.commands.fix_missing_import_sources.LicensingFixesLookup" + ) as lookup_cls: + lookup = lookup_cls.return_value + lookup.get_info.return_value = ( + cc_by_nc_sa_id, + "", + "Khan Academy", + ) + + call_command("fix_missing_import_sources") + + lookup.get_info.assert_called_once_with( + self.public_channel.id, "video", None, "", "" ) - self.mock_csv_writer_instance.writeheader.assert_called_once() - self.mock_csv_writer_instance.writerow.assert_not_called() + self.copied_node.refresh_from_db() + self.assertEqual(self.copied_node.license_id, cc_by_nc_sa_id) + self.assertEqual(self.copied_node.license_description, "") + self.assertEqual(self.copied_node.copyright_holder, "Khan Academy") - def test_handle__finds_missing(self): + def test_handle__skips_node_when_lookup_returns_no_license(self): self.original_node.delete() - call_command("fix_missing_import_sources") - - self.mock_csv_writer_instance.writerow.assert_called_once_with( - { - "channel_id": self.private_channel.id, - "channel_name": self.private_channel.name, - "contentnode_id": self.copied_node.id, - "contentnode_title": self.copied_node.title, - "public_channel_id": self.public_channel.id, - "public_channel_name": self.public_channel.name, - "public_channel_deleted": False, - } + self.copied_node.refresh_from_db() + original_license_id = self.copied_node.license_id + self.copied_node.license_description = "Nothing" + self.copied_node.copyright_holder = "Nothing" + self.copied_node.save() + + with patch( + "contentcuration.management.commands.fix_missing_import_sources.LicensingFixesLookup" + ) as lookup_cls: + lookup = lookup_cls.return_value + lookup.get_info.return_value = (None, "Nothing", "Nothing") + + call_command("fix_missing_import_sources") + + lookup.get_info.assert_called_once() + + self.copied_node.refresh_from_db() + self.assertEqual(self.copied_node.license_id, original_license_id) + self.assertEqual(self.copied_node.license_description, "Nothing") + self.assertEqual(self.copied_node.copyright_holder, "Nothing") + + +class LicensingFixesLookupTestCase(StudioTestCase): + @classmethod + def setUpTestData(cls): + cls.csv_path = ( + Path(__file__).resolve().parents[3] + / "management" + / "commands" + / "licensing_fixes_lookup.csv" ) - def test_handle__finds_for_deleted_channel(self): - self.public_channel.deleted = True - self.public_channel.save(actor_id=testdata.user().id) - call_command("fix_missing_import_sources") - - self.mock_csv_writer_instance.writerow.assert_called_once_with( - { - "channel_id": self.private_channel.id, - "channel_name": self.private_channel.name, - "contentnode_id": self.copied_node.id, - "contentnode_title": self.copied_node.title, - "public_channel_id": self.public_channel.id, - "public_channel_name": self.public_channel.name, - "public_channel_deleted": True, - } + def setUp(self): + self.lookup = LicensingFixesLookup() + + def test_load__reads_csv_and_resolves_all_licenses(self): + with self.csv_path.open("r", encoding="utf-8", newline="") as csv_file: + self.lookup.load(csv_file) + + for info in self.lookup._lookup.values(): + if info["license_name"]: + self.assertIsNotNone(info["license_id"]) + self.assertIsNotNone( + self.lookup._license_lookup.get(info["license_id"]) + ) + + def test_get_info__special_permissions(self): + with self.csv_path.open("r", encoding="utf-8", newline="") as csv_file: + self.lookup.load(csv_file) + + license_id, license_description, copyright_holder = self.lookup.get_info( + "a53592c972a8594e9b695aa127493ff6", + "exercise", + 9, + "", + "", # Special Permissions + ) + self.assertEqual(license_id, 9) + self.assertEqual( + license_description, + "Permission granted to distribute through Kolibri for non-commercial use", + ) + self.assertEqual(copyright_holder, "Khan Academy") + + def test_get_info__requires_copyright_holder(self): + with self.csv_path.open("r", encoding="utf-8", newline="") as csv_file: + self.lookup.load(csv_file) + + license_id, license_description, copyright_holder = self.lookup.get_info( + "c1f2b7e6ac9f56a2bb44fa7a48b66dce", "video", 5, "", "" # CC BY-NC-SA + ) + self.assertEqual(license_id, 5) + self.assertEqual(license_description, "") + self.assertEqual(copyright_holder, "Khan Academy") + + def test_get_info__defaults(self): + with self.csv_path.open("r", encoding="utf-8", newline="") as csv_file: + self.lookup.load(csv_file) + + license_id, license_description, copyright_holder = self.lookup.get_info( + "c51a0f842fed427c95acff9bb4a21e3c", "", None, "", "" + ) + self.assertEqual(license_id, 5) + self.assertEqual(license_description, "") + self.assertEqual(copyright_holder, "Enabling Education Network (EENET)") + + def test_get_info__broken(self): + with self.csv_path.open("r", encoding="utf-8", newline="") as csv_file: + self.lookup.load(csv_file) + + license_id, license_description, copyright_holder = self.lookup.get_info( + "237e5975bce25bf6aff398f4c17516f3", "", None, "Nothing", "Nothing" ) + self.assertIsNone(license_id) + self.assertEqual(license_description, "Nothing") + self.assertEqual(copyright_holder, "Nothing") From d619fe3eb84de48afa19cb4ddde44254dcea0f26 Mon Sep 17 00:00:00 2001 From: Blaine Jester Date: Thu, 30 Apr 2026 12:37:38 -0700 Subject: [PATCH 34/34] Ignore topics since they will not be present in the CSV for Khan channels --- .../commands/fix_missing_import_sources.py | 2 ++ .../test_fix_missing_import_sources.py | 26 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/contentcuration/contentcuration/management/commands/fix_missing_import_sources.py b/contentcuration/contentcuration/management/commands/fix_missing_import_sources.py index 0adbf1baca..6c5210ef7a 100644 --- a/contentcuration/contentcuration/management/commands/fix_missing_import_sources.py +++ b/contentcuration/contentcuration/management/commands/fix_missing_import_sources.py @@ -14,6 +14,7 @@ from django.db.models import Q from django.db.models.expressions import F from django_cte import With +from le_utils.constants import content_kinds from contentcuration.models import Channel from contentcuration.models import ContentNode @@ -215,6 +216,7 @@ def handle_channel(self, lookup: LicensingFixesLookup, channel: dict) -> int: public_channel_name=public_cte.col.name, public_channel_deleted=public_cte.col.deleted, ) + .exclude(kind=content_kinds.TOPIC) .filter( Q(public_channel_deleted=True) | ~Exists( diff --git a/contentcuration/contentcuration/tests/management/commands/test_fix_missing_import_sources.py b/contentcuration/contentcuration/tests/management/commands/test_fix_missing_import_sources.py index 8e9fa0f716..bdaa7f0c57 100644 --- a/contentcuration/contentcuration/tests/management/commands/test_fix_missing_import_sources.py +++ b/contentcuration/contentcuration/tests/management/commands/test_fix_missing_import_sources.py @@ -2,6 +2,7 @@ from unittest.mock import patch from django.core.management import call_command +from le_utils.constants import content_kinds from contentcuration.management.commands.fix_missing_import_sources import ( LicensingFixesLookup, @@ -117,6 +118,31 @@ def test_handle__skips_node_when_lookup_returns_no_license(self): self.assertEqual(self.copied_node.license_description, "Nothing") self.assertEqual(self.copied_node.copyright_holder, "Nothing") + def test_handle__skips_topic_nodes_with_missing_source(self): + self.original_node.delete() + self.copied_node.refresh_from_db() + self.copied_node.kind_id = content_kinds.TOPIC + self.copied_node.license = None + self.copied_node.license_description = "" + self.copied_node.copyright_holder = "" + self.copied_node.save() + + with patch( + "contentcuration.management.commands.fix_missing_import_sources.LicensingFixesLookup" + ) as lookup_cls: + lookup = lookup_cls.return_value + lookup.get_info.return_value = (5, "", "Khan Academy") + + call_command("fix_missing_import_sources") + + lookup.get_info.assert_not_called() + + self.copied_node.refresh_from_db() + self.assertIsNone(self.copied_node.license_id) + self.assertEqual(self.copied_node.kind_id, content_kinds.TOPIC) + self.assertEqual(self.copied_node.license_description, "") + self.assertEqual(self.copied_node.copyright_holder, "") + class LicensingFixesLookupTestCase(StudioTestCase): @classmethod