initial commit
This commit is contained in:
parent
d066133bd4
commit
e6f71e3706
55 changed files with 11928 additions and 0 deletions
19
native/wordpress/maple-fonts-wp/.claude/settings.local.json
Normal file
19
native/wordpress/maple-fonts-wp/.claude/settings.local.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(mkdir:*)",
|
||||
"Bash(for po in *.po)",
|
||||
"Bash(do mo=\"$po%.po.mo\")",
|
||||
"Bash(msgfmt:*)",
|
||||
"Bash(echo:*)",
|
||||
"Bash(done)",
|
||||
"Bash(ls:*)",
|
||||
"Bash(xargs:*)",
|
||||
"Bash(chmod:*)",
|
||||
"Bash(composer install:*)",
|
||||
"Bash(php:*)",
|
||||
"Bash(docker --version:*)",
|
||||
"Bash(brew list:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
2
native/wordpress/maple-fonts-wp/.github/index.php
vendored
Normal file
2
native/wordpress/maple-fonts-wp/.github/index.php
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<?php
|
||||
// Silence is golden.
|
||||
191
native/wordpress/maple-fonts-wp/.github/workflows/ci.yml
vendored
Normal file
191
native/wordpress/maple-fonts-wp/.github/workflows/ci.yml
vendored
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop]
|
||||
paths:
|
||||
- 'native/wordpress/maple-fonts-wp/**'
|
||||
pull_request:
|
||||
branches: [main, develop]
|
||||
paths:
|
||||
- 'native/wordpress/maple-fonts-wp/**'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: native/wordpress/maple-fonts-wp
|
||||
|
||||
jobs:
|
||||
phpcs:
|
||||
name: PHP Coding Standards
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.2'
|
||||
tools: composer, cs2pr
|
||||
coverage: none
|
||||
|
||||
- name: Get Composer cache directory
|
||||
id: composer-cache
|
||||
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache Composer dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: ${{ runner.os }}-composer-
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer install --prefer-dist --no-progress
|
||||
|
||||
- name: Run PHP_CodeSniffer
|
||||
run: ./vendor/bin/phpcs --report=checkstyle | cs2pr
|
||||
continue-on-error: true
|
||||
|
||||
phpcompat:
|
||||
name: PHP Compatibility
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.2'
|
||||
tools: composer
|
||||
coverage: none
|
||||
|
||||
- name: Get Composer cache directory
|
||||
id: composer-cache
|
||||
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache Composer dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: ${{ runner.os }}-composer-
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer install --prefer-dist --no-progress
|
||||
|
||||
- name: Run PHPCompatibility
|
||||
run: ./vendor/bin/phpcs -p --standard=PHPCompatibilityWP --runtime-set testVersion 7.4- --extensions=php --ignore=vendor,tests .
|
||||
|
||||
test:
|
||||
name: PHP ${{ matrix.php }} / WP ${{ matrix.wordpress }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: [phpcs, phpcompat]
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php: ['7.4', '8.0', '8.1', '8.2', '8.3']
|
||||
wordpress: ['6.5', '6.6', 'latest']
|
||||
exclude:
|
||||
# PHP 8.3 not fully compatible with older WP versions
|
||||
- php: '8.3'
|
||||
wordpress: '6.5'
|
||||
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
env:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
MYSQL_DATABASE: wordpress_test
|
||||
ports:
|
||||
- 3306:3306
|
||||
options: >-
|
||||
--health-cmd="mysqladmin ping"
|
||||
--health-interval=10s
|
||||
--health-timeout=5s
|
||||
--health-retries=5
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, pdo_mysql, mysqli, gd, exif, intl
|
||||
tools: composer
|
||||
coverage: none
|
||||
|
||||
- name: Get Composer cache directory
|
||||
id: composer-cache
|
||||
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache Composer dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-php${{ matrix.php }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: ${{ runner.os }}-php${{ matrix.php }}-composer-
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer install --prefer-dist --no-progress
|
||||
|
||||
- name: Install WordPress test suite
|
||||
run: bash bin/install-wp-tests.sh wordpress_test root root 127.0.0.1 ${{ matrix.wordpress }} true
|
||||
|
||||
- name: Run PHPUnit
|
||||
run: ./vendor/bin/phpunit --configuration phpunit.xml.dist
|
||||
|
||||
build:
|
||||
name: Build Plugin
|
||||
runs-on: ubuntu-latest
|
||||
needs: test
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.2'
|
||||
tools: composer
|
||||
coverage: none
|
||||
|
||||
- name: Install dependencies (production)
|
||||
run: composer install --prefer-dist --no-dev --optimize-autoloader --no-progress
|
||||
|
||||
- name: Create build directory
|
||||
run: mkdir -p build
|
||||
|
||||
- name: Build plugin zip
|
||||
run: |
|
||||
zip -r build/maple-local-fonts.zip . \
|
||||
-x ".git/*" \
|
||||
-x ".github/*" \
|
||||
-x "tests/*" \
|
||||
-x "bin/*" \
|
||||
-x "build/*" \
|
||||
-x "vendor/bin/*" \
|
||||
-x "*.xml" \
|
||||
-x "*.xml.dist" \
|
||||
-x "composer.*" \
|
||||
-x "phpcs.xml*" \
|
||||
-x ".editorconfig" \
|
||||
-x ".gitignore" \
|
||||
-x "CLAUDE.md" \
|
||||
-x "SECURITY.md" \
|
||||
-x "GOOGLE_FONTS_API.md" \
|
||||
-x "WORDPRESS_COMPATIBILITY.md"
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: maple-local-fonts
|
||||
path: native/wordpress/maple-fonts-wp/build/maple-local-fonts.zip
|
||||
retention-days: 30
|
||||
2
native/wordpress/maple-fonts-wp/.github/workflows/index.php
vendored
Normal file
2
native/wordpress/maple-fonts-wp/.github/workflows/index.php
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<?php
|
||||
// Silence is golden.
|
||||
53
native/wordpress/maple-fonts-wp/.woodpecker.yml
Normal file
53
native/wordpress/maple-fonts-wp/.woodpecker.yml
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
when:
|
||||
- event: [push, pull_request]
|
||||
path: 'native/wordpress/maple-fonts-wp/**'
|
||||
|
||||
steps:
|
||||
install:
|
||||
image: composer:2
|
||||
commands:
|
||||
- cd native/wordpress/maple-fonts-wp
|
||||
- composer install --prefer-dist --no-interaction
|
||||
|
||||
phpcs:
|
||||
image: php:8.2-cli
|
||||
commands:
|
||||
- cd native/wordpress/maple-fonts-wp
|
||||
- ./vendor/bin/phpcs --standard=phpcs.xml.dist --report=summary || true
|
||||
depends_on:
|
||||
- install
|
||||
|
||||
phpcompat:
|
||||
image: php:8.2-cli
|
||||
commands:
|
||||
- cd native/wordpress/maple-fonts-wp
|
||||
- ./vendor/bin/phpcs -p --standard=PHPCompatibilityWP --runtime-set testVersion 7.4- --extensions=php --ignore=vendor,tests . || true
|
||||
depends_on:
|
||||
- install
|
||||
|
||||
test-php74:
|
||||
image: wordpressdevelop/phpunit:9-php-7.4-fpm
|
||||
commands:
|
||||
- cd native/wordpress/maple-fonts-wp
|
||||
- composer install --prefer-dist --no-interaction
|
||||
- bash bin/install-wp-tests.sh wordpress_test root root database latest true
|
||||
- ./vendor/bin/phpunit
|
||||
depends_on:
|
||||
- install
|
||||
|
||||
test-php82:
|
||||
image: wordpressdevelop/phpunit:9-php-8.2-fpm
|
||||
commands:
|
||||
- cd native/wordpress/maple-fonts-wp
|
||||
- composer install --prefer-dist --no-interaction
|
||||
- bash bin/install-wp-tests.sh wordpress_test root root database latest true
|
||||
- ./vendor/bin/phpunit
|
||||
depends_on:
|
||||
- install
|
||||
|
||||
services:
|
||||
database:
|
||||
image: mysql:8.0
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
MYSQL_DATABASE: wordpress_test
|
||||
264
native/wordpress/maple-fonts-wp/CLAUDE.md
Normal file
264
native/wordpress/maple-fonts-wp/CLAUDE.md
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
# CLAUDE.md — Maple Local Fonts WordPress Plugin
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Document | When to Reference |
|
||||
|----------|-------------------|
|
||||
| **CLAUDE.md** (this file) | Architecture, file structure, build order |
|
||||
| **SECURITY.md** | Writing ANY PHP code |
|
||||
| **GOOGLE_FONTS_API.md** | Building the font downloader class |
|
||||
| **WORDPRESS_COMPATIBILITY.md** | Building the font registry, FSE integration |
|
||||
|
||||
---
|
||||
|
||||
## Project Overview
|
||||
|
||||
Build a WordPress plugin called **Maple Local Fonts** that:
|
||||
1. Imports Google Fonts to local storage (one-time download)
|
||||
2. Registers them with WordPress's native Font Library API
|
||||
3. Fonts appear in FSE typography dropdowns alongside theme fonts
|
||||
|
||||
**Key principle:** Work WITH WordPress, not around it. We're a font importer — WordPress handles everything else.
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
- **Minimum PHP:** 7.4
|
||||
- **Minimum WordPress:** 6.5 (required for Font Library API)
|
||||
- **License:** GPL-2.0-or-later
|
||||
|
||||
---
|
||||
|
||||
## User Flow
|
||||
|
||||
```
|
||||
Admin enters "Open Sans" → selects weights (400, 700) → selects styles (normal, italic)
|
||||
↓
|
||||
Plugin hits Google Fonts CSS2 API (ONE TIME)
|
||||
↓
|
||||
Downloads WOFF2 files to wp-content/fonts/
|
||||
↓
|
||||
Registers font via WP Font Library API
|
||||
↓
|
||||
Font appears in Appearance → Editor → Styles → Typography dropdown
|
||||
↓
|
||||
User applies font using standard WordPress FSE controls
|
||||
↓
|
||||
GOOGLE NEVER CONTACTED AGAIN — fonts served locally
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
maple-local-fonts/
|
||||
├── maple-local-fonts.php # Main plugin file
|
||||
├── index.php # Silence is golden
|
||||
├── uninstall.php # Clean removal
|
||||
├── readme.txt # WordPress.org readme
|
||||
├── includes/
|
||||
│ ├── index.php # Silence is golden
|
||||
│ ├── class-mlf-font-downloader.php
|
||||
│ ├── class-mlf-font-registry.php
|
||||
│ ├── class-mlf-admin-page.php
|
||||
│ └── class-mlf-ajax-handler.php
|
||||
├── assets/
|
||||
│ ├── index.php # Silence is golden
|
||||
│ ├── admin.css
|
||||
│ └── admin.js
|
||||
└── languages/
|
||||
├── index.php # Silence is golden
|
||||
└── maple-local-fonts.pot
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Class Responsibilities
|
||||
|
||||
### MLF_Font_Downloader
|
||||
- Build Google Fonts CSS2 URL
|
||||
- Fetch CSS (with correct user-agent for WOFF2)
|
||||
- Parse CSS to extract font face data
|
||||
- Download WOFF2 files to wp-content/fonts/
|
||||
- **Reference:** GOOGLE_FONTS_API.md
|
||||
|
||||
### MLF_Font_Registry
|
||||
- Register fonts with WP Font Library (wp_font_family, wp_font_face post types)
|
||||
- Delete fonts (remove posts and files)
|
||||
- List imported fonts
|
||||
- **Reference:** WORDPRESS_COMPATIBILITY.md
|
||||
|
||||
### MLF_Admin_Page
|
||||
- Render settings page under Appearance menu
|
||||
- Font name input, weight checkboxes, style checkboxes
|
||||
- Display installed fonts with delete buttons
|
||||
- **Reference:** SECURITY.md for output escaping
|
||||
|
||||
### MLF_Ajax_Handler
|
||||
- Handle download requests
|
||||
- Handle delete requests
|
||||
- **Reference:** SECURITY.md for nonce/capability checks
|
||||
|
||||
---
|
||||
|
||||
## Admin Page UI
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Maple Local Fonts │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ IMPORT FROM GOOGLE FONTS │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ Font Name: [Open Sans_________________________] │ │
|
||||
│ │ │ │
|
||||
│ │ Weights: │ │
|
||||
│ │ ☑ 400 (Regular) ☐ 300 (Light) ☐ 500 (Medium) │ │
|
||||
│ │ ☑ 700 (Bold) ☐ 600 (Semi) ☐ 800 (Extra) │ │
|
||||
│ │ │ │
|
||||
│ │ Styles: │ │
|
||||
│ │ ☑ Normal ☑ Italic │ │
|
||||
│ │ │ │
|
||||
│ │ Files to download: 4 │ │
|
||||
│ │ [Download & Install] │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ INSTALLED FONTS │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ Open Sans │ │
|
||||
│ │ 400 normal, 400 italic, 700 normal, 700 italic │ │
|
||||
│ │ [Delete] │ │
|
||||
│ ├─────────────────────────────────────────────────────┤ │
|
||||
│ │ Roboto │ │
|
||||
│ │ 400 normal, 500 normal, 700 normal │ │
|
||||
│ │ [Delete] │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ℹ️ Use Appearance → Editor → Styles → Typography to │
|
||||
│ apply fonts to your site. │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Build Order
|
||||
|
||||
### Phase 1: Foundation
|
||||
1. `maple-local-fonts.php` — Plugin header, constants, autoloader, activation hook
|
||||
2. `index.php` files in all directories (silence is golden)
|
||||
3. `includes/class-mlf-ajax-handler.php` — With full security checks (nonce, capability, validation)
|
||||
|
||||
### Phase 2: Core Functionality
|
||||
4. `includes/class-mlf-font-downloader.php` — Google Fonts fetching/parsing/downloading
|
||||
5. `includes/class-mlf-font-registry.php` — WP Font Library integration
|
||||
|
||||
### Phase 3: Admin Interface
|
||||
6. `includes/class-mlf-admin-page.php` — Settings page render
|
||||
7. `assets/admin.css` — Admin page styles
|
||||
8. `assets/admin.js` — AJAX handling for download/delete
|
||||
|
||||
### Phase 4: Cleanup & Polish
|
||||
9. `uninstall.php` — Clean removal of fonts and data
|
||||
10. `readme.txt` — WordPress.org readme with privacy section
|
||||
11. Testing against all compatibility targets
|
||||
|
||||
---
|
||||
|
||||
## Critical Reminders
|
||||
|
||||
### Security (see SECURITY.md)
|
||||
- ABSPATH check on EVERY PHP file
|
||||
- index.php in EVERY directory
|
||||
- Nonce verification FIRST in every AJAX handler
|
||||
- Capability check SECOND
|
||||
- Input validation THIRD
|
||||
- Escape ALL output
|
||||
|
||||
### Performance
|
||||
- Zero JavaScript on frontend
|
||||
- Zero CSS on frontend
|
||||
- All plugin code is admin-side only (except lightweight font registration)
|
||||
- Timeout handling on all external requests
|
||||
- Maximum limits to prevent infinite loops
|
||||
|
||||
### GDPR
|
||||
- No user data collected
|
||||
- External requests only during admin import
|
||||
- Fonts served locally after import
|
||||
- Document in readme.txt
|
||||
|
||||
### Compatibility (see WORDPRESS_COMPATIBILITY.md)
|
||||
- Declare WooCommerce HPOS compatibility
|
||||
- Use wp_get_font_dir() not hardcoded paths
|
||||
- Use Font Library API (post types) not theme.json filtering
|
||||
- Don't touch frontend, let Global Styles handle everything
|
||||
|
||||
---
|
||||
|
||||
## Constants
|
||||
|
||||
```php
|
||||
define('MLF_VERSION', '1.0.0');
|
||||
define('MLF_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
||||
define('MLF_PLUGIN_URL', plugin_dir_url(__FILE__));
|
||||
define('MLF_PLUGIN_BASENAME', plugin_basename(__FILE__));
|
||||
|
||||
// Limits
|
||||
define('MLF_MAX_FONTS_PER_REQUEST', 10);
|
||||
define('MLF_MAX_WEIGHTS_PER_FONT', 9);
|
||||
define('MLF_REQUEST_TIMEOUT', 30);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## WordPress Hooks Used
|
||||
|
||||
```php
|
||||
// Activation
|
||||
register_activation_hook(__FILE__, 'mlf_activate');
|
||||
|
||||
// Admin menu
|
||||
add_action('admin_menu', 'mlf_register_menu');
|
||||
|
||||
// Admin assets (our page only)
|
||||
add_action('admin_enqueue_scripts', 'mlf_enqueue_admin_assets');
|
||||
|
||||
// AJAX (admin only, no nopriv)
|
||||
add_action('wp_ajax_mlf_download_font', 'mlf_ajax_download');
|
||||
add_action('wp_ajax_mlf_delete_font', 'mlf_ajax_delete');
|
||||
|
||||
// WooCommerce HPOS
|
||||
add_action('before_woocommerce_init', 'mlf_declare_hpos_compatibility');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
### Functional
|
||||
- [ ] Import Open Sans 400, 700 normal + italic → 4 files created
|
||||
- [ ] Font appears in FSE typography dropdown
|
||||
- [ ] Apply font in Global Styles → frontend shows local font
|
||||
- [ ] Delete font → files and posts removed
|
||||
- [ ] Re-import same font → appropriate error message
|
||||
|
||||
### Security
|
||||
- [ ] AJAX without nonce → 403
|
||||
- [ ] AJAX as subscriber → 403
|
||||
- [ ] Path traversal in font name → rejected
|
||||
- [ ] XSS in font name → sanitized
|
||||
- [ ] Direct PHP file access → blank/exit
|
||||
|
||||
### Performance
|
||||
- [ ] No frontend network requests from plugin
|
||||
- [ ] Import 5 fonts → completes without timeout
|
||||
- [ ] Admin page load < 500ms added time
|
||||
|
||||
### Compatibility
|
||||
- [ ] WordPress 6.5, 6.6+
|
||||
- [ ] Twenty Twenty-Five theme
|
||||
- [ ] WooCommerce (HPOS enabled)
|
||||
- [ ] Wordfence (enabled)
|
||||
- [ ] LearnDash
|
||||
- [ ] WPForms
|
||||
707
native/wordpress/maple-fonts-wp/GOOGLE_FONTS_API.md
Normal file
707
native/wordpress/maple-fonts-wp/GOOGLE_FONTS_API.md
Normal file
|
|
@ -0,0 +1,707 @@
|
|||
# GOOGLE_FONTS_API.md — Google Fonts Retrieval Guide
|
||||
|
||||
## Overview
|
||||
|
||||
This document covers how to retrieve fonts from Google Fonts CSS2 API. This is a **one-time retrieval** during admin import — after download, fonts are served locally and Google is never contacted again.
|
||||
|
||||
---
|
||||
|
||||
## The Retrieval Flow
|
||||
|
||||
```
|
||||
1. Admin enters "Open Sans" + selects weights/styles
|
||||
↓
|
||||
2. Plugin constructs Google Fonts CSS2 URL
|
||||
↓
|
||||
3. Plugin fetches CSS (contains @font-face rules with WOFF2 URLs)
|
||||
↓
|
||||
4. Plugin parses CSS to extract WOFF2 URLs
|
||||
↓
|
||||
5. Plugin downloads each WOFF2 file to wp-content/fonts/
|
||||
↓
|
||||
6. Plugin registers font with WordPress Font Library
|
||||
↓
|
||||
7. DONE - Google never contacted again
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Google Fonts CSS2 API
|
||||
|
||||
### Base URL
|
||||
|
||||
```
|
||||
https://fonts.googleapis.com/css2
|
||||
```
|
||||
|
||||
### URL Construction
|
||||
|
||||
**Pattern:**
|
||||
```
|
||||
https://fonts.googleapis.com/css2?family={Font+Name}:ital,wght@{variations}&display=swap
|
||||
```
|
||||
|
||||
**Variations format:**
|
||||
```
|
||||
ital,wght@{italic},{weight};{italic},{weight};...
|
||||
```
|
||||
|
||||
Where:
|
||||
- `ital` = 0 (normal) or 1 (italic)
|
||||
- `wght` = weight (100-900)
|
||||
|
||||
### Examples
|
||||
|
||||
**Open Sans 400 normal only:**
|
||||
```
|
||||
https://fonts.googleapis.com/css2?family=Open+Sans:wght@400&display=swap
|
||||
```
|
||||
|
||||
**Open Sans 400 + 700 normal:**
|
||||
```
|
||||
https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;700&display=swap
|
||||
```
|
||||
|
||||
**Open Sans 400 + 700, both normal and italic:**
|
||||
```
|
||||
https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,400;0,700;1,400;1,700&display=swap
|
||||
```
|
||||
|
||||
**Breakdown of `ital,wght@0,400;0,700;1,400;1,700`:**
|
||||
- `0,400` = normal 400
|
||||
- `0,700` = normal 700
|
||||
- `1,400` = italic 400
|
||||
- `1,700` = italic 700
|
||||
|
||||
### URL Builder Function
|
||||
|
||||
```php
|
||||
/**
|
||||
* Build Google Fonts CSS2 API URL.
|
||||
*
|
||||
* @param string $font_name Font family name (e.g., "Open Sans")
|
||||
* @param array $weights Array of weights (e.g., [400, 700])
|
||||
* @param array $styles Array of styles (e.g., ['normal', 'italic'])
|
||||
* @return string Google Fonts CSS2 URL
|
||||
*/
|
||||
function mlf_build_google_fonts_url($font_name, $weights, $styles) {
|
||||
// URL-encode font name (spaces become +)
|
||||
$family = str_replace(' ', '+', $font_name);
|
||||
|
||||
// Build variations
|
||||
$variations = [];
|
||||
|
||||
// Sort for consistent URLs
|
||||
sort($weights);
|
||||
sort($styles);
|
||||
|
||||
$has_italic = in_array('italic', $styles, true);
|
||||
$has_normal = in_array('normal', $styles, true);
|
||||
|
||||
foreach ($weights as $weight) {
|
||||
if ($has_normal) {
|
||||
$variations[] = "0,{$weight}";
|
||||
}
|
||||
if ($has_italic) {
|
||||
$variations[] = "1,{$weight}";
|
||||
}
|
||||
}
|
||||
|
||||
// If only normal styles, simpler format
|
||||
if ($has_normal && !$has_italic) {
|
||||
$wght = implode(';', $weights);
|
||||
return "https://fonts.googleapis.com/css2?family={$family}:wght@{$wght}&display=swap";
|
||||
}
|
||||
|
||||
// Full format with ital axis
|
||||
$variation_string = implode(';', $variations);
|
||||
return "https://fonts.googleapis.com/css2?family={$family}:ital,wght@{$variation_string}&display=swap";
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CRITICAL: User-Agent Requirement
|
||||
|
||||
Google Fonts returns **different file formats** based on user-agent:
|
||||
|
||||
| User-Agent | Format Returned |
|
||||
|------------|-----------------|
|
||||
| Old browser | TTF or WOFF |
|
||||
| Modern browser | WOFF2 |
|
||||
| curl (no UA) | TTF |
|
||||
|
||||
**We need WOFF2** (smallest, best compression, modern browser support).
|
||||
|
||||
### Required User-Agent
|
||||
|
||||
Use a modern Chrome user-agent:
|
||||
|
||||
```php
|
||||
$user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
||||
```
|
||||
|
||||
### Fetch Function
|
||||
|
||||
```php
|
||||
/**
|
||||
* Fetch CSS from Google Fonts API.
|
||||
*
|
||||
* @param string $font_name Font family name
|
||||
* @param array $weights Weights to fetch
|
||||
* @param array $styles Styles to fetch
|
||||
* @return string|WP_Error CSS content or error
|
||||
*/
|
||||
function mlf_fetch_google_css($font_name, $weights, $styles) {
|
||||
$url = mlf_build_google_fonts_url($font_name, $weights, $styles);
|
||||
|
||||
// Validate URL
|
||||
if (!mlf_is_valid_google_fonts_url($url)) {
|
||||
return new WP_Error('invalid_url', 'Invalid Google Fonts URL');
|
||||
}
|
||||
|
||||
// CRITICAL: Must use modern browser user-agent to get WOFF2
|
||||
$response = wp_remote_get($url, [
|
||||
'timeout' => 15,
|
||||
'sslverify' => true,
|
||||
'user-agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||
]);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
return new WP_Error(
|
||||
'request_failed',
|
||||
'Could not connect to Google Fonts: ' . $response->get_error_message()
|
||||
);
|
||||
}
|
||||
|
||||
$status = wp_remote_retrieve_response_code($response);
|
||||
if ($status === 400) {
|
||||
return new WP_Error('font_not_found', 'Font not found on Google Fonts');
|
||||
}
|
||||
if ($status !== 200) {
|
||||
return new WP_Error('http_error', 'Google Fonts returned HTTP ' . $status);
|
||||
}
|
||||
|
||||
$css = wp_remote_retrieve_body($response);
|
||||
if (empty($css)) {
|
||||
return new WP_Error('empty_response', 'Empty response from Google Fonts');
|
||||
}
|
||||
|
||||
// Verify we got WOFF2 (sanity check)
|
||||
if (strpos($css, '.woff2)') === false) {
|
||||
return new WP_Error('wrong_format', 'Did not receive WOFF2 format - check user-agent');
|
||||
}
|
||||
|
||||
return $css;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Parsing the CSS Response
|
||||
|
||||
### Sample Response
|
||||
|
||||
Google returns CSS like this:
|
||||
|
||||
```css
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-stretch: 100%;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v40/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsg-1x4gaVI.woff2) format('woff2');
|
||||
unicode-range: U+0100-02AF, U+0304, U+0308, ...;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-stretch: 100%;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v40/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0B4gaVI.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, ...;
|
||||
}
|
||||
```
|
||||
|
||||
### Key Observations
|
||||
|
||||
1. **Multiple @font-face blocks per weight** — Google splits fonts by unicode subset (latin, latin-ext, cyrillic, etc.)
|
||||
2. **We want the "latin" subset** — It's the base and covers most use cases
|
||||
3. **Each block has:** font-family, font-style, font-weight, src URL, unicode-range
|
||||
|
||||
### Unicode Subset Strategy
|
||||
|
||||
**Option A: Download only latin (simpler, smaller)**
|
||||
- Parse CSS, identify latin blocks, download only those
|
||||
- Good for most sites
|
||||
|
||||
**Option B: Download all subsets (more complete)**
|
||||
- Download all WOFF2 files
|
||||
- Larger but supports more languages
|
||||
|
||||
**Recommended: Option A** — Start with latin subset. Can add option for more subsets later.
|
||||
|
||||
### Parsing Function
|
||||
|
||||
```php
|
||||
/**
|
||||
* Parse Google Fonts CSS and extract font face data.
|
||||
*
|
||||
* @param string $css CSS content from Google Fonts
|
||||
* @param string $font_name Expected font family name
|
||||
* @return array|WP_Error Array of font face data or error
|
||||
*/
|
||||
function mlf_parse_google_css($css, $font_name) {
|
||||
$font_faces = [];
|
||||
|
||||
// Match all @font-face blocks
|
||||
$pattern = '/@font-face\s*\{([^}]+)\}/s';
|
||||
if (!preg_match_all($pattern, $css, $matches)) {
|
||||
return new WP_Error('parse_failed', 'Could not parse CSS - no @font-face rules found');
|
||||
}
|
||||
|
||||
foreach ($matches[1] as $block) {
|
||||
$face_data = mlf_parse_font_face_block($block);
|
||||
|
||||
if (is_wp_error($face_data)) {
|
||||
continue; // Skip malformed blocks
|
||||
}
|
||||
|
||||
// Verify font family matches (security)
|
||||
if (strcasecmp($face_data['family'], $font_name) !== 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create unique key for weight+style combo
|
||||
$key = $face_data['weight'] . '-' . $face_data['style'];
|
||||
|
||||
// Prefer latin subset (usually comes after latin-ext)
|
||||
// Check if this is a latin block by unicode-range
|
||||
$is_latin = mlf_is_latin_subset($face_data['unicode_range']);
|
||||
|
||||
// Only store if:
|
||||
// 1. We don't have this weight/style yet, OR
|
||||
// 2. This is latin and replaces non-latin
|
||||
if (!isset($font_faces[$key]) || $is_latin) {
|
||||
$font_faces[$key] = $face_data;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($font_faces)) {
|
||||
return new WP_Error('no_fonts', 'No valid font faces found in CSS');
|
||||
}
|
||||
|
||||
return array_values($font_faces);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a single @font-face block.
|
||||
*
|
||||
* @param string $block Content inside @font-face { }
|
||||
* @return array|WP_Error Parsed data or error
|
||||
*/
|
||||
function mlf_parse_font_face_block($block) {
|
||||
$data = [];
|
||||
|
||||
// Extract font-family
|
||||
if (preg_match('/font-family:\s*[\'"]?([^;\'"]+)[\'"]?;/i', $block, $m)) {
|
||||
$data['family'] = trim($m[1]);
|
||||
} else {
|
||||
return new WP_Error('missing_family', 'Missing font-family');
|
||||
}
|
||||
|
||||
// Extract font-weight
|
||||
if (preg_match('/font-weight:\s*(\d+);/i', $block, $m)) {
|
||||
$data['weight'] = $m[1];
|
||||
} else {
|
||||
return new WP_Error('missing_weight', 'Missing font-weight');
|
||||
}
|
||||
|
||||
// Extract font-style
|
||||
if (preg_match('/font-style:\s*(\w+);/i', $block, $m)) {
|
||||
$data['style'] = $m[1];
|
||||
} else {
|
||||
$data['style'] = 'normal'; // Default
|
||||
}
|
||||
|
||||
// Extract src URL - MUST be fonts.gstatic.com
|
||||
if (preg_match('/src:\s*url\((https:\/\/fonts\.gstatic\.com\/[^)]+\.woff2)\)/i', $block, $m)) {
|
||||
$data['url'] = $m[1];
|
||||
} else {
|
||||
return new WP_Error('missing_src', 'Missing or invalid src URL');
|
||||
}
|
||||
|
||||
// Extract unicode-range (optional, for subset detection)
|
||||
if (preg_match('/unicode-range:\s*([^;]+);/i', $block, $m)) {
|
||||
$data['unicode_range'] = trim($m[1]);
|
||||
} else {
|
||||
$data['unicode_range'] = '';
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if unicode-range indicates latin subset.
|
||||
* Latin typically starts with U+0000-00FF.
|
||||
*
|
||||
* @param string $range Unicode range string
|
||||
* @return bool True if appears to be latin subset
|
||||
*/
|
||||
function mlf_is_latin_subset($range) {
|
||||
// Latin subset typically includes basic ASCII range
|
||||
// and does NOT include extended Latin (U+0100+) as primary
|
||||
|
||||
if (empty($range)) {
|
||||
return true; // Assume latin if no range specified
|
||||
}
|
||||
|
||||
// Latin subset usually starts with U+0000 and includes U+00FF
|
||||
// Latin-ext starts with U+0100
|
||||
if (preg_match('/U\+0000/', $range) && !preg_match('/^U\+0100/', $range)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Downloading WOFF2 Files
|
||||
|
||||
### Download Function
|
||||
|
||||
```php
|
||||
/**
|
||||
* Download a single WOFF2 file from Google Fonts.
|
||||
*
|
||||
* @param string $url Google Fonts static URL
|
||||
* @param string $font_slug Font slug (e.g., "open-sans")
|
||||
* @param string $weight Font weight (e.g., "400")
|
||||
* @param string $style Font style (e.g., "normal")
|
||||
* @return string|WP_Error Local file path or error
|
||||
*/
|
||||
function mlf_download_font_file($url, $font_slug, $weight, $style) {
|
||||
// Validate URL is from Google
|
||||
if (!mlf_is_valid_google_fonts_url($url)) {
|
||||
return new WP_Error('invalid_url', 'URL is not from Google Fonts');
|
||||
}
|
||||
|
||||
// Build local filename
|
||||
$filename = sprintf('%s_%s_%s.woff2', $font_slug, $style, $weight);
|
||||
$filename = sanitize_file_name($filename);
|
||||
|
||||
// Get destination path
|
||||
$font_dir = wp_get_font_dir();
|
||||
$destination = trailingslashit($font_dir['path']) . $filename;
|
||||
|
||||
// Validate destination path
|
||||
if (!mlf_validate_font_path($destination)) {
|
||||
return new WP_Error('invalid_path', 'Invalid destination path');
|
||||
}
|
||||
|
||||
// Ensure directory exists
|
||||
if (!wp_mkdir_p($font_dir['path'])) {
|
||||
return new WP_Error('mkdir_failed', 'Could not create fonts directory');
|
||||
}
|
||||
|
||||
// Download file
|
||||
$response = wp_remote_get($url, [
|
||||
'timeout' => 30,
|
||||
'sslverify' => true,
|
||||
'user-agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
||||
]);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
return new WP_Error(
|
||||
'download_failed',
|
||||
'Failed to download font file: ' . $response->get_error_message()
|
||||
);
|
||||
}
|
||||
|
||||
$status = wp_remote_retrieve_response_code($response);
|
||||
if ($status !== 200) {
|
||||
return new WP_Error('http_error', 'Font download returned HTTP ' . $status);
|
||||
}
|
||||
|
||||
$content = wp_remote_retrieve_body($response);
|
||||
if (empty($content)) {
|
||||
return new WP_Error('empty_file', 'Downloaded font file is empty');
|
||||
}
|
||||
|
||||
// Verify it looks like a WOFF2 file (magic bytes: wOF2)
|
||||
if (substr($content, 0, 4) !== 'wOF2') {
|
||||
return new WP_Error('invalid_format', 'Downloaded file is not valid WOFF2');
|
||||
}
|
||||
|
||||
// Write file using WP Filesystem
|
||||
global $wp_filesystem;
|
||||
if (empty($wp_filesystem)) {
|
||||
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||||
WP_Filesystem();
|
||||
}
|
||||
|
||||
if (!$wp_filesystem->put_contents($destination, $content, FS_CHMOD_FILE)) {
|
||||
return new WP_Error('write_failed', 'Could not write font file');
|
||||
}
|
||||
|
||||
return $destination;
|
||||
}
|
||||
```
|
||||
|
||||
### Batch Download Function
|
||||
|
||||
```php
|
||||
/**
|
||||
* Download all font files for a font family.
|
||||
*
|
||||
* @param string $font_name Font family name
|
||||
* @param array $weights Weights to download
|
||||
* @param array $styles Styles to download
|
||||
* @return array|WP_Error Array of downloaded files or error
|
||||
*/
|
||||
function mlf_download_font_family($font_name, $weights, $styles) {
|
||||
// Fetch CSS from Google
|
||||
$css = mlf_fetch_google_css($font_name, $weights, $styles);
|
||||
if (is_wp_error($css)) {
|
||||
return $css;
|
||||
}
|
||||
|
||||
// Parse CSS to get font face data
|
||||
$font_faces = mlf_parse_google_css($css, $font_name);
|
||||
if (is_wp_error($font_faces)) {
|
||||
return $font_faces;
|
||||
}
|
||||
|
||||
// Generate slug from font name
|
||||
$font_slug = sanitize_title($font_name);
|
||||
|
||||
// Download each font file
|
||||
$downloaded = [];
|
||||
$errors = [];
|
||||
|
||||
foreach ($font_faces as $face) {
|
||||
$result = mlf_download_font_file(
|
||||
$face['url'],
|
||||
$font_slug,
|
||||
$face['weight'],
|
||||
$face['style']
|
||||
);
|
||||
|
||||
if (is_wp_error($result)) {
|
||||
$errors[] = $result->get_error_message();
|
||||
continue;
|
||||
}
|
||||
|
||||
$downloaded[] = [
|
||||
'path' => $result,
|
||||
'weight' => $face['weight'],
|
||||
'style' => $face['style'],
|
||||
];
|
||||
}
|
||||
|
||||
// If no files downloaded, return error
|
||||
if (empty($downloaded)) {
|
||||
return new WP_Error(
|
||||
'download_failed',
|
||||
'Could not download any font files: ' . implode(', ', $errors)
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
'font_name' => $font_name,
|
||||
'font_slug' => $font_slug,
|
||||
'files' => $downloaded,
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Common Errors
|
||||
|
||||
| Error | Cause | User Message |
|
||||
|-------|-------|--------------|
|
||||
| Font not found | Typo in font name | "Font not found on Google Fonts. Check the spelling." |
|
||||
| Network timeout | Slow connection | "Could not connect to Google Fonts. Please try again." |
|
||||
| Invalid format | Wrong user-agent | Internal error - should not happen |
|
||||
| Write failed | Permissions | "Could not save font files. Check directory permissions." |
|
||||
|
||||
### Error Messages (User-Friendly)
|
||||
|
||||
```php
|
||||
/**
|
||||
* Convert internal error codes to user-friendly messages.
|
||||
*
|
||||
* @param WP_Error $error The error object
|
||||
* @return string User-friendly message
|
||||
*/
|
||||
function mlf_get_user_error_message($error) {
|
||||
$code = $error->get_error_code();
|
||||
|
||||
$messages = [
|
||||
'font_not_found' => 'Font not found on Google Fonts. Please check the spelling and try again.',
|
||||
'request_failed' => 'Could not connect to Google Fonts. Please check your internet connection and try again.',
|
||||
'http_error' => 'Google Fonts returned an error. Please try again later.',
|
||||
'parse_failed' => 'Could not process the font data. The font may not be available.',
|
||||
'download_failed' => 'Could not download the font files. Please try again.',
|
||||
'write_failed' => 'Could not save font files. Please check that wp-content/fonts is writable.',
|
||||
'mkdir_failed' => 'Could not create fonts directory. Please check file permissions.',
|
||||
'invalid_path' => 'Invalid file path. Please contact support.',
|
||||
'invalid_url' => 'Invalid font URL. Please contact support.',
|
||||
];
|
||||
|
||||
return $messages[$code] ?? 'An unexpected error occurred. Please try again.';
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Complete Downloader Class
|
||||
|
||||
```php
|
||||
<?php
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class MLF_Font_Downloader {
|
||||
|
||||
private $user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
||||
|
||||
/**
|
||||
* Download a font from Google Fonts.
|
||||
*
|
||||
* @param string $font_name Font family name
|
||||
* @param array $weights Weights to download
|
||||
* @param array $styles Styles to download
|
||||
* @return array|WP_Error Download result or error
|
||||
*/
|
||||
public function download($font_name, $weights, $styles) {
|
||||
// Validate inputs
|
||||
if (empty($font_name) || !preg_match('/^[a-zA-Z0-9\s\-]+$/', $font_name)) {
|
||||
return new WP_Error('invalid_name', 'Invalid font name');
|
||||
}
|
||||
|
||||
$weights = array_intersect(array_map('absint', $weights), [100,200,300,400,500,600,700,800,900]);
|
||||
if (empty($weights)) {
|
||||
return new WP_Error('invalid_weights', 'No valid weights specified');
|
||||
}
|
||||
|
||||
$styles = array_intersect($styles, ['normal', 'italic']);
|
||||
if (empty($styles)) {
|
||||
return new WP_Error('invalid_styles', 'No valid styles specified');
|
||||
}
|
||||
|
||||
// Fetch CSS
|
||||
$css = $this->fetch_css($font_name, $weights, $styles);
|
||||
if (is_wp_error($css)) {
|
||||
return $css;
|
||||
}
|
||||
|
||||
// Parse CSS
|
||||
$font_faces = $this->parse_css($css, $font_name);
|
||||
if (is_wp_error($font_faces)) {
|
||||
return $font_faces;
|
||||
}
|
||||
|
||||
// Download files
|
||||
$font_slug = sanitize_title($font_name);
|
||||
$downloaded = $this->download_files($font_faces, $font_slug);
|
||||
if (is_wp_error($downloaded)) {
|
||||
return $downloaded;
|
||||
}
|
||||
|
||||
return [
|
||||
'font_name' => $font_name,
|
||||
'font_slug' => $font_slug,
|
||||
'files' => $downloaded,
|
||||
];
|
||||
}
|
||||
|
||||
private function build_url($font_name, $weights, $styles) {
|
||||
$family = str_replace(' ', '+', $font_name);
|
||||
sort($weights);
|
||||
|
||||
$has_italic = in_array('italic', $styles, true);
|
||||
$has_normal = in_array('normal', $styles, true);
|
||||
|
||||
if ($has_normal && !$has_italic) {
|
||||
$wght = implode(';', $weights);
|
||||
return "https://fonts.googleapis.com/css2?family={$family}:wght@{$wght}&display=swap";
|
||||
}
|
||||
|
||||
$variations = [];
|
||||
foreach ($weights as $weight) {
|
||||
if ($has_normal) {
|
||||
$variations[] = "0,{$weight}";
|
||||
}
|
||||
if ($has_italic) {
|
||||
$variations[] = "1,{$weight}";
|
||||
}
|
||||
}
|
||||
|
||||
$variation_string = implode(';', $variations);
|
||||
return "https://fonts.googleapis.com/css2?family={$family}:ital,wght@{$variation_string}&display=swap";
|
||||
}
|
||||
|
||||
private function fetch_css($font_name, $weights, $styles) {
|
||||
$url = $this->build_url($font_name, $weights, $styles);
|
||||
|
||||
$response = wp_remote_get($url, [
|
||||
'timeout' => 15,
|
||||
'sslverify' => true,
|
||||
'user-agent' => $this->user_agent,
|
||||
]);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
return new WP_Error('request_failed', $response->get_error_message());
|
||||
}
|
||||
|
||||
$status = wp_remote_retrieve_response_code($response);
|
||||
if ($status === 400) {
|
||||
return new WP_Error('font_not_found', 'Font not found');
|
||||
}
|
||||
if ($status !== 200) {
|
||||
return new WP_Error('http_error', 'HTTP ' . $status);
|
||||
}
|
||||
|
||||
$css = wp_remote_retrieve_body($response);
|
||||
if (empty($css) || strpos($css, '.woff2)') === false) {
|
||||
return new WP_Error('invalid_response', 'Invalid CSS response');
|
||||
}
|
||||
|
||||
return $css;
|
||||
}
|
||||
|
||||
private function parse_css($css, $font_name) {
|
||||
// Implementation as shown above
|
||||
// Returns array of font face data
|
||||
}
|
||||
|
||||
private function download_files($font_faces, $font_slug) {
|
||||
// Implementation as shown above
|
||||
// Returns array of downloaded file info
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Valid font name (Open Sans) returns CSS with WOFF2 URLs
|
||||
- [ ] Invalid font name returns appropriate error
|
||||
- [ ] Multiple weights are all downloaded
|
||||
- [ ] Italic styles are handled correctly
|
||||
- [ ] Files are saved to correct location
|
||||
- [ ] Files have correct WOFF2 magic bytes
|
||||
- [ ] Timeout handling works (test with slow connection)
|
||||
- [ ] User-agent produces WOFF2 (not TTF/WOFF)
|
||||
621
native/wordpress/maple-fonts-wp/SECURITY.md
Normal file
621
native/wordpress/maple-fonts-wp/SECURITY.md
Normal file
|
|
@ -0,0 +1,621 @@
|
|||
# SECURITY.md — Maple Local Fonts Security Requirements
|
||||
|
||||
## Overview
|
||||
|
||||
This document covers all security requirements for the Maple Local Fonts plugin. Reference this when writing ANY PHP code.
|
||||
|
||||
---
|
||||
|
||||
## ABSPATH Check (Every PHP File)
|
||||
|
||||
Every PHP file MUST start with this check. No exceptions.
|
||||
|
||||
```php
|
||||
<?php
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Silence is Golden Files
|
||||
|
||||
Create `index.php` in EVERY directory:
|
||||
|
||||
```php
|
||||
<?php
|
||||
// Silence is golden.
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
```
|
||||
|
||||
**Required locations:**
|
||||
- `/maple-local-fonts/index.php`
|
||||
- `/maple-local-fonts/includes/index.php`
|
||||
- `/maple-local-fonts/assets/index.php`
|
||||
- `/maple-local-fonts/languages/index.php`
|
||||
|
||||
---
|
||||
|
||||
## OWASP Compliance
|
||||
|
||||
### A1 - Injection Prevention
|
||||
|
||||
**SQL Injection:**
|
||||
```php
|
||||
// NEVER do this
|
||||
$wpdb->query("SELECT * FROM table WHERE id = " . $_POST['id']);
|
||||
|
||||
// ALWAYS do this
|
||||
$wpdb->get_results($wpdb->prepare(
|
||||
"SELECT * FROM %i WHERE id = %d",
|
||||
$table_name,
|
||||
absint($_POST['id'])
|
||||
));
|
||||
```
|
||||
|
||||
**Note:** This plugin should rarely need direct SQL. Use WordPress APIs (`get_posts`, `wp_insert_post`, etc.) which handle escaping internally.
|
||||
|
||||
### A2 - Authentication
|
||||
|
||||
All admin actions require capability check:
|
||||
|
||||
```php
|
||||
if (!current_user_can('edit_theme_options')) {
|
||||
wp_die('Unauthorized', 'Error', ['response' => 403]);
|
||||
}
|
||||
```
|
||||
|
||||
### A3 - Sensitive Data
|
||||
|
||||
- No API keys (Google Fonts CSS2 API is public)
|
||||
- No user credentials stored
|
||||
- No PII collected
|
||||
|
||||
### A5 - Broken Access Control
|
||||
|
||||
**Order of checks for ALL AJAX handlers:**
|
||||
|
||||
```php
|
||||
public function handle_ajax_action() {
|
||||
// 1. Nonce verification FIRST
|
||||
if (!check_ajax_referer('mlf_action_name', 'nonce', false)) {
|
||||
wp_send_json_error(['message' => 'Security check failed'], 403);
|
||||
}
|
||||
|
||||
// 2. Capability check SECOND
|
||||
if (!current_user_can('edit_theme_options')) {
|
||||
wp_send_json_error(['message' => 'Unauthorized'], 403);
|
||||
}
|
||||
|
||||
// 3. Input validation THIRD
|
||||
// ... validate all inputs ...
|
||||
|
||||
// 4. Process request
|
||||
// ... actual logic ...
|
||||
}
|
||||
```
|
||||
|
||||
### A7 - Cross-Site Scripting (XSS)
|
||||
|
||||
**Escape ALL output:**
|
||||
|
||||
```php
|
||||
// HTML content
|
||||
echo esc_html($font_name);
|
||||
|
||||
// HTML attributes
|
||||
echo '<input value="' . esc_attr($font_name) . '">';
|
||||
|
||||
// URLs
|
||||
echo '<a href="' . esc_url($url) . '">';
|
||||
|
||||
// JavaScript data
|
||||
wp_localize_script('mlf-admin', 'mlfData', [
|
||||
'fontName' => esc_js($font_name), // Or let wp_localize_script handle it
|
||||
]);
|
||||
|
||||
// Translatable strings with variables
|
||||
printf(
|
||||
esc_html__('Installed: %s', 'maple-local-fonts'),
|
||||
esc_html($font_name)
|
||||
);
|
||||
```
|
||||
|
||||
**Never trust input for output:**
|
||||
```php
|
||||
// WRONG - XSS vulnerability
|
||||
echo '<div>' . $_POST['font_name'] . '</div>';
|
||||
|
||||
// RIGHT - sanitize input, escape output
|
||||
$font_name = sanitize_text_field($_POST['font_name']);
|
||||
echo '<div>' . esc_html($font_name) . '</div>';
|
||||
```
|
||||
|
||||
### A8 - Insecure Deserialization
|
||||
|
||||
```php
|
||||
// NEVER use unserialize() on external data
|
||||
$data = unserialize($_POST['data']); // DANGEROUS
|
||||
|
||||
// Use JSON instead
|
||||
$data = json_decode(sanitize_text_field($_POST['data']), true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
wp_send_json_error(['message' => 'Invalid data format']);
|
||||
}
|
||||
```
|
||||
|
||||
### A9 - Vulnerable Components
|
||||
|
||||
- No external PHP libraries
|
||||
- Use only WordPress core functions
|
||||
- Keep dependencies to zero
|
||||
|
||||
---
|
||||
|
||||
## Nonce Implementation
|
||||
|
||||
### Creating Nonces
|
||||
|
||||
**In admin page form:**
|
||||
```php
|
||||
wp_nonce_field('mlf_download_font', 'mlf_nonce');
|
||||
```
|
||||
|
||||
**For AJAX (via wp_localize_script):**
|
||||
```php
|
||||
wp_localize_script('mlf-admin', 'mlfData', [
|
||||
'ajaxUrl' => admin_url('admin-ajax.php'),
|
||||
'nonce' => wp_create_nonce('mlf_download_font'),
|
||||
]);
|
||||
```
|
||||
|
||||
### Verifying Nonces
|
||||
|
||||
**AJAX handler:**
|
||||
```php
|
||||
// Returns false on failure, doesn't die (we handle response ourselves)
|
||||
if (!check_ajax_referer('mlf_download_font', 'nonce', false)) {
|
||||
wp_send_json_error(['message' => 'Security check failed'], 403);
|
||||
}
|
||||
```
|
||||
|
||||
**Form submission:**
|
||||
```php
|
||||
if (!wp_verify_nonce($_POST['mlf_nonce'], 'mlf_download_font')) {
|
||||
wp_die('Security check failed');
|
||||
}
|
||||
```
|
||||
|
||||
### Nonce Names
|
||||
|
||||
Use consistent, descriptive nonce action names:
|
||||
|
||||
| Action | Nonce Name |
|
||||
|--------|------------|
|
||||
| Download font | `mlf_download_font` |
|
||||
| Delete font | `mlf_delete_font` |
|
||||
| Update settings | `mlf_update_settings` |
|
||||
|
||||
---
|
||||
|
||||
## Input Validation
|
||||
|
||||
### Font Name Validation
|
||||
|
||||
```php
|
||||
$font_name = isset($_POST['font_name']) ? sanitize_text_field($_POST['font_name']) : '';
|
||||
|
||||
// Strict allowlist pattern - alphanumeric, spaces, hyphens only
|
||||
if (!preg_match('/^[a-zA-Z0-9\s\-]+$/', $font_name)) {
|
||||
wp_send_json_error(['message' => 'Invalid font name: only letters, numbers, spaces, and hyphens allowed']);
|
||||
}
|
||||
|
||||
// Length limit
|
||||
if (strlen($font_name) > 100) {
|
||||
wp_send_json_error(['message' => 'Font name too long']);
|
||||
}
|
||||
|
||||
// Not empty
|
||||
if (empty($font_name)) {
|
||||
wp_send_json_error(['message' => 'Font name required']);
|
||||
}
|
||||
```
|
||||
|
||||
### Weight Validation
|
||||
|
||||
```php
|
||||
$weights = isset($_POST['weights']) ? (array) $_POST['weights'] : [];
|
||||
|
||||
// Convert to integers
|
||||
$weights = array_map('absint', $weights);
|
||||
|
||||
// Strict allowlist
|
||||
$allowed_weights = [100, 200, 300, 400, 500, 600, 700, 800, 900];
|
||||
$weights = array_intersect($weights, $allowed_weights);
|
||||
|
||||
// Must have at least one
|
||||
if (empty($weights)) {
|
||||
wp_send_json_error(['message' => 'At least one weight required']);
|
||||
}
|
||||
```
|
||||
|
||||
### Style Validation
|
||||
|
||||
```php
|
||||
$styles = isset($_POST['styles']) ? (array) $_POST['styles'] : [];
|
||||
|
||||
// Strict allowlist - only these two values ever
|
||||
$allowed_styles = ['normal', 'italic'];
|
||||
$styles = array_filter($styles, function($style) use ($allowed_styles) {
|
||||
return in_array($style, $allowed_styles, true);
|
||||
});
|
||||
|
||||
// Must have at least one
|
||||
if (empty($styles)) {
|
||||
wp_send_json_error(['message' => 'At least one style required']);
|
||||
}
|
||||
```
|
||||
|
||||
### Font Family ID Validation (for delete)
|
||||
|
||||
```php
|
||||
$font_id = isset($_POST['font_id']) ? absint($_POST['font_id']) : 0;
|
||||
|
||||
if ($font_id < 1) {
|
||||
wp_send_json_error(['message' => 'Invalid font ID']);
|
||||
}
|
||||
|
||||
// Verify it exists and is a font family
|
||||
$font = get_post($font_id);
|
||||
if (!$font || $font->post_type !== 'wp_font_family') {
|
||||
wp_send_json_error(['message' => 'Font not found']);
|
||||
}
|
||||
|
||||
// Verify it's one we imported (not a theme font)
|
||||
if (get_post_meta($font_id, '_mlf_imported', true) !== '1') {
|
||||
wp_send_json_error(['message' => 'Cannot delete theme fonts']);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Operation Security
|
||||
|
||||
### Path Traversal Prevention
|
||||
|
||||
```php
|
||||
/**
|
||||
* Validate that a path is within the WordPress fonts directory.
|
||||
* Prevents path traversal attacks.
|
||||
*
|
||||
* @param string $path Full path to validate
|
||||
* @return bool True if path is safe, false otherwise
|
||||
*/
|
||||
function mlf_validate_font_path($path) {
|
||||
$font_dir = wp_get_font_dir();
|
||||
$fonts_path = wp_normalize_path(trailingslashit($font_dir['path']));
|
||||
|
||||
// Resolve to real path (handles ../ etc)
|
||||
$real_path = realpath($path);
|
||||
|
||||
// If realpath fails, file doesn't exist yet - validate the directory
|
||||
if ($real_path === false) {
|
||||
$dir = dirname($path);
|
||||
$real_dir = realpath($dir);
|
||||
if ($real_dir === false) {
|
||||
return false;
|
||||
}
|
||||
$real_path = wp_normalize_path($real_dir . '/' . basename($path));
|
||||
} else {
|
||||
$real_path = wp_normalize_path($real_path);
|
||||
}
|
||||
|
||||
// Must be within fonts directory
|
||||
return strpos($real_path, $fonts_path) === 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Filename Sanitization
|
||||
|
||||
```php
|
||||
/**
|
||||
* Sanitize and validate a font filename.
|
||||
*
|
||||
* @param string $filename The filename to validate
|
||||
* @return string|false Sanitized filename or false if invalid
|
||||
*/
|
||||
function mlf_sanitize_font_filename($filename) {
|
||||
// WordPress sanitization first
|
||||
$filename = sanitize_file_name($filename);
|
||||
|
||||
// Must have .woff2 extension
|
||||
if (pathinfo($filename, PATHINFO_EXTENSION) !== 'woff2') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// No path components
|
||||
if ($filename !== basename($filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reasonable length
|
||||
if (strlen($filename) > 200) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $filename;
|
||||
}
|
||||
```
|
||||
|
||||
### Safe File Writing
|
||||
|
||||
```php
|
||||
/**
|
||||
* Safely write a font file to the fonts directory.
|
||||
*
|
||||
* @param string $filename Sanitized filename
|
||||
* @param string $content File content
|
||||
* @return string|WP_Error File path on success, WP_Error on failure
|
||||
*/
|
||||
function mlf_write_font_file($filename, $content) {
|
||||
// Validate filename
|
||||
$safe_filename = mlf_sanitize_font_filename($filename);
|
||||
if ($safe_filename === false) {
|
||||
return new WP_Error('invalid_filename', 'Invalid filename');
|
||||
}
|
||||
|
||||
// Get fonts directory
|
||||
$font_dir = wp_get_font_dir();
|
||||
$destination = trailingslashit($font_dir['path']) . $safe_filename;
|
||||
|
||||
// Validate path
|
||||
if (!mlf_validate_font_path($destination)) {
|
||||
return new WP_Error('invalid_path', 'Invalid file path');
|
||||
}
|
||||
|
||||
// Ensure directory exists
|
||||
if (!wp_mkdir_p($font_dir['path'])) {
|
||||
return new WP_Error('mkdir_failed', 'Could not create fonts directory');
|
||||
}
|
||||
|
||||
// Write file
|
||||
global $wp_filesystem;
|
||||
if (empty($wp_filesystem)) {
|
||||
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||||
WP_Filesystem();
|
||||
}
|
||||
|
||||
if (!$wp_filesystem->put_contents($destination, $content, FS_CHMOD_FILE)) {
|
||||
return new WP_Error('write_failed', 'Could not write font file');
|
||||
}
|
||||
|
||||
return $destination;
|
||||
}
|
||||
```
|
||||
|
||||
### Safe File Deletion
|
||||
|
||||
```php
|
||||
/**
|
||||
* Safely delete a font file.
|
||||
*
|
||||
* @param string $path Full path to the file
|
||||
* @return bool True on success, false on failure
|
||||
*/
|
||||
function mlf_delete_font_file($path) {
|
||||
// Validate path is within fonts directory
|
||||
if (!mlf_validate_font_path($path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Must be a .woff2 file
|
||||
if (pathinfo($path, PATHINFO_EXTENSION) !== 'woff2') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// File must exist
|
||||
if (!file_exists($path)) {
|
||||
return true; // Already gone, that's fine
|
||||
}
|
||||
|
||||
return wp_delete_file($path);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## HTTP Request Security
|
||||
|
||||
### Outbound Requests (Google Fonts)
|
||||
|
||||
```php
|
||||
$response = wp_remote_get($url, [
|
||||
'timeout' => 15,
|
||||
'sslverify' => true, // Always verify SSL
|
||||
'user-agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
||||
]);
|
||||
|
||||
// Check for errors
|
||||
if (is_wp_error($response)) {
|
||||
// Log error, return gracefully
|
||||
error_log('MLF: Google Fonts request failed - ' . $response->get_error_message());
|
||||
return new WP_Error('request_failed', 'Could not connect to Google Fonts');
|
||||
}
|
||||
|
||||
// Check HTTP status
|
||||
$status = wp_remote_retrieve_response_code($response);
|
||||
if ($status !== 200) {
|
||||
return new WP_Error('http_error', 'Google Fonts returned status ' . $status);
|
||||
}
|
||||
|
||||
// Get body
|
||||
$body = wp_remote_retrieve_body($response);
|
||||
if (empty($body)) {
|
||||
return new WP_Error('empty_response', 'Empty response from Google Fonts');
|
||||
}
|
||||
```
|
||||
|
||||
### URL Validation (Google Fonts only)
|
||||
|
||||
```php
|
||||
/**
|
||||
* Validate that a URL is a legitimate Google Fonts URL.
|
||||
*
|
||||
* @param string $url URL to validate
|
||||
* @return bool True if valid Google Fonts URL
|
||||
*/
|
||||
function mlf_is_valid_google_fonts_url($url) {
|
||||
$parsed = wp_parse_url($url);
|
||||
|
||||
if (!$parsed || !isset($parsed['host'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only allow Google Fonts domains
|
||||
$allowed_hosts = [
|
||||
'fonts.googleapis.com',
|
||||
'fonts.gstatic.com',
|
||||
];
|
||||
|
||||
return in_array($parsed['host'], $allowed_hosts, true);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## AJAX Handler Complete Template
|
||||
|
||||
```php
|
||||
<?php
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class MLF_Ajax_Handler {
|
||||
|
||||
public function __construct() {
|
||||
add_action('wp_ajax_mlf_download_font', [$this, 'handle_download']);
|
||||
add_action('wp_ajax_mlf_delete_font', [$this, 'handle_delete']);
|
||||
// NEVER add wp_ajax_nopriv_ - admin only functionality
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle font download AJAX request.
|
||||
*/
|
||||
public function handle_download() {
|
||||
// 1. NONCE CHECK
|
||||
if (!check_ajax_referer('mlf_download_font', 'nonce', false)) {
|
||||
wp_send_json_error(['message' => 'Security check failed'], 403);
|
||||
}
|
||||
|
||||
// 2. CAPABILITY CHECK
|
||||
if (!current_user_can('edit_theme_options')) {
|
||||
wp_send_json_error(['message' => 'Unauthorized'], 403);
|
||||
}
|
||||
|
||||
// 3. INPUT VALIDATION
|
||||
$font_name = isset($_POST['font_name']) ? sanitize_text_field($_POST['font_name']) : '';
|
||||
if (empty($font_name) || !preg_match('/^[a-zA-Z0-9\s\-]+$/', $font_name) || strlen($font_name) > 100) {
|
||||
wp_send_json_error(['message' => 'Invalid font name']);
|
||||
}
|
||||
|
||||
$weights = isset($_POST['weights']) ? array_map('absint', (array) $_POST['weights']) : [];
|
||||
$weights = array_intersect($weights, [100, 200, 300, 400, 500, 600, 700, 800, 900]);
|
||||
if (empty($weights)) {
|
||||
wp_send_json_error(['message' => 'At least one weight required']);
|
||||
}
|
||||
|
||||
$styles = isset($_POST['styles']) ? (array) $_POST['styles'] : [];
|
||||
$styles = array_intersect($styles, ['normal', 'italic']);
|
||||
if (empty($styles)) {
|
||||
wp_send_json_error(['message' => 'At least one style required']);
|
||||
}
|
||||
|
||||
// 4. PROCESS REQUEST
|
||||
try {
|
||||
$downloader = new MLF_Font_Downloader();
|
||||
$result = $downloader->download($font_name, $weights, $styles);
|
||||
|
||||
if (is_wp_error($result)) {
|
||||
wp_send_json_error(['message' => $result->get_error_message()]);
|
||||
}
|
||||
|
||||
wp_send_json_success([
|
||||
'message' => sprintf('Successfully installed %s', esc_html($font_name)),
|
||||
'font_id' => $result,
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
error_log('MLF Download Error: ' . $e->getMessage());
|
||||
wp_send_json_error(['message' => 'An unexpected error occurred']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle font deletion AJAX request.
|
||||
*/
|
||||
public function handle_delete() {
|
||||
// 1. NONCE CHECK
|
||||
if (!check_ajax_referer('mlf_delete_font', 'nonce', false)) {
|
||||
wp_send_json_error(['message' => 'Security check failed'], 403);
|
||||
}
|
||||
|
||||
// 2. CAPABILITY CHECK
|
||||
if (!current_user_can('edit_theme_options')) {
|
||||
wp_send_json_error(['message' => 'Unauthorized'], 403);
|
||||
}
|
||||
|
||||
// 3. INPUT VALIDATION
|
||||
$font_id = isset($_POST['font_id']) ? absint($_POST['font_id']) : 0;
|
||||
if ($font_id < 1) {
|
||||
wp_send_json_error(['message' => 'Invalid font ID']);
|
||||
}
|
||||
|
||||
// Verify font exists and is ours
|
||||
$font = get_post($font_id);
|
||||
if (!$font || $font->post_type !== 'wp_font_family') {
|
||||
wp_send_json_error(['message' => 'Font not found']);
|
||||
}
|
||||
if (get_post_meta($font_id, '_mlf_imported', true) !== '1') {
|
||||
wp_send_json_error(['message' => 'Cannot delete theme fonts']);
|
||||
}
|
||||
|
||||
// 4. PROCESS REQUEST
|
||||
try {
|
||||
$registry = new MLF_Font_Registry();
|
||||
$result = $registry->delete_font($font_id);
|
||||
|
||||
if (is_wp_error($result)) {
|
||||
wp_send_json_error(['message' => $result->get_error_message()]);
|
||||
}
|
||||
|
||||
wp_send_json_success(['message' => 'Font deleted successfully']);
|
||||
} catch (Exception $e) {
|
||||
error_log('MLF Delete Error: ' . $e->getMessage());
|
||||
wp_send_json_error(['message' => 'An unexpected error occurred']);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security Checklist
|
||||
|
||||
Before committing any code:
|
||||
|
||||
- [ ] ABSPATH check at top of every PHP file
|
||||
- [ ] index.php exists in every directory
|
||||
- [ ] All AJAX handlers verify nonce first
|
||||
- [ ] All AJAX handlers check capability second
|
||||
- [ ] All user input sanitized with appropriate function
|
||||
- [ ] All user input validated against allowlists where applicable
|
||||
- [ ] All output escaped with appropriate function
|
||||
- [ ] File paths validated to prevent traversal
|
||||
- [ ] No direct SQL queries (use WordPress APIs)
|
||||
- [ ] No `unserialize()` on user input
|
||||
- [ ] No `eval()` or similar dynamic execution
|
||||
- [ ] External URLs validated before use
|
||||
- [ ] Error messages don't expose sensitive info
|
||||
560
native/wordpress/maple-fonts-wp/WORDPRESS_COMPATIBILITY.md
Normal file
560
native/wordpress/maple-fonts-wp/WORDPRESS_COMPATIBILITY.md
Normal file
|
|
@ -0,0 +1,560 @@
|
|||
# WORDPRESS_COMPATIBILITY.md — WordPress & Plugin Compatibility
|
||||
|
||||
## Overview
|
||||
|
||||
This document covers compatibility requirements for WordPress core systems and popular plugins. Reference this when building the font registry class and integration points.
|
||||
|
||||
---
|
||||
|
||||
## WordPress Version Requirements
|
||||
|
||||
**Minimum: WordPress 6.5**
|
||||
|
||||
WordPress 6.5 introduced the Font Library API which this plugin depends on. Earlier versions will not work.
|
||||
|
||||
```php
|
||||
// Check on activation
|
||||
register_activation_hook(__FILE__, function() {
|
||||
if (version_compare(get_bloginfo('version'), '6.5', '<')) {
|
||||
deactivate_plugins(plugin_basename(__FILE__));
|
||||
wp_die(
|
||||
'Maple Local Fonts requires WordPress 6.5 or higher for Font Library support.',
|
||||
'Plugin Activation Error',
|
||||
['back_link' => true]
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Also check on admin init (in case WP was downgraded)
|
||||
add_action('admin_init', function() {
|
||||
if (version_compare(get_bloginfo('version'), '6.5', '<')) {
|
||||
deactivate_plugins(plugin_basename(__FILE__));
|
||||
add_action('admin_notices', function() {
|
||||
echo '<div class="error"><p>Maple Local Fonts has been deactivated. It requires WordPress 6.5 or higher.</p></div>';
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## WordPress Font Library API
|
||||
|
||||
### How It Works
|
||||
|
||||
WordPress 6.5+ stores fonts using custom post types:
|
||||
|
||||
| Post Type | Purpose |
|
||||
|-----------|---------|
|
||||
| `wp_font_family` | Font family (e.g., "Open Sans") |
|
||||
| `wp_font_face` | Individual weight/style variant (child of family) |
|
||||
|
||||
Fonts are stored in `wp-content/fonts/` by default.
|
||||
|
||||
### Getting the Fonts Directory
|
||||
|
||||
```php
|
||||
// ALWAYS use this function, never hardcode paths
|
||||
$font_dir = wp_get_font_dir();
|
||||
|
||||
// Returns:
|
||||
[
|
||||
'path' => '/var/www/html/wp-content/fonts',
|
||||
'url' => 'https://example.com/wp-content/fonts',
|
||||
'subdir' => '',
|
||||
'basedir' => '/var/www/html/wp-content/fonts',
|
||||
'baseurl' => 'https://example.com/wp-content/fonts',
|
||||
]
|
||||
```
|
||||
|
||||
### Registering a Font Family
|
||||
|
||||
```php
|
||||
/**
|
||||
* Register a font family with WordPress Font Library.
|
||||
*
|
||||
* @param string $font_name Display name (e.g., "Open Sans")
|
||||
* @param string $font_slug Slug (e.g., "open-sans")
|
||||
* @param array $files Array of downloaded file data
|
||||
* @return int|WP_Error Font family post ID or error
|
||||
*/
|
||||
function mlf_register_font_family($font_name, $font_slug, $files) {
|
||||
// Check if font already exists
|
||||
$existing = get_posts([
|
||||
'post_type' => 'wp_font_family',
|
||||
'name' => $font_slug,
|
||||
'posts_per_page' => 1,
|
||||
'post_status' => 'publish',
|
||||
]);
|
||||
|
||||
if (!empty($existing)) {
|
||||
return new WP_Error('font_exists', 'Font family already installed');
|
||||
}
|
||||
|
||||
// Build font family settings
|
||||
$font_family_settings = [
|
||||
'name' => $font_name,
|
||||
'slug' => $font_slug,
|
||||
'fontFamily' => sprintf('"%s", sans-serif', $font_name),
|
||||
'fontFace' => [],
|
||||
];
|
||||
|
||||
// Add each font face
|
||||
foreach ($files as $file) {
|
||||
$font_dir = wp_get_font_dir();
|
||||
$relative_path = str_replace($font_dir['path'], '', $file['path']);
|
||||
|
||||
$font_family_settings['fontFace'][] = [
|
||||
'fontFamily' => $font_name,
|
||||
'fontWeight' => $file['weight'],
|
||||
'fontStyle' => $file['style'],
|
||||
'src' => 'file:.' . $font_dir['basedir'] . $relative_path,
|
||||
];
|
||||
}
|
||||
|
||||
// Create font family post
|
||||
$family_id = wp_insert_post([
|
||||
'post_type' => 'wp_font_family',
|
||||
'post_title' => $font_name,
|
||||
'post_name' => $font_slug,
|
||||
'post_status' => 'publish',
|
||||
'post_content' => wp_json_encode($font_family_settings),
|
||||
]);
|
||||
|
||||
if (is_wp_error($family_id)) {
|
||||
return $family_id;
|
||||
}
|
||||
|
||||
// Mark as imported by our plugin (for identification)
|
||||
update_post_meta($family_id, '_mlf_imported', '1');
|
||||
update_post_meta($family_id, '_mlf_import_date', current_time('mysql'));
|
||||
|
||||
// Create font face posts (children)
|
||||
foreach ($files as $file) {
|
||||
$font_dir = wp_get_font_dir();
|
||||
|
||||
$face_settings = [
|
||||
'fontFamily' => $font_name,
|
||||
'fontWeight' => $file['weight'],
|
||||
'fontStyle' => $file['style'],
|
||||
'src' => 'file:.' . $font_dir['baseurl'] . '/' . basename($file['path']),
|
||||
];
|
||||
|
||||
wp_insert_post([
|
||||
'post_type' => 'wp_font_face',
|
||||
'post_parent' => $family_id,
|
||||
'post_status' => 'publish',
|
||||
'post_content' => wp_json_encode($face_settings),
|
||||
]);
|
||||
}
|
||||
|
||||
// Clear font caches
|
||||
delete_transient('wp_font_library_fonts');
|
||||
|
||||
return $family_id;
|
||||
}
|
||||
```
|
||||
|
||||
### Deleting a Font Family
|
||||
|
||||
```php
|
||||
/**
|
||||
* Delete a font family and its files.
|
||||
*
|
||||
* @param int $family_id Font family post ID
|
||||
* @return bool|WP_Error True on success, error on failure
|
||||
*/
|
||||
function mlf_delete_font_family($family_id) {
|
||||
$family = get_post($family_id);
|
||||
|
||||
if (!$family || $family->post_type !== 'wp_font_family') {
|
||||
return new WP_Error('not_found', 'Font family not found');
|
||||
}
|
||||
|
||||
// Verify it's one we imported
|
||||
if (get_post_meta($family_id, '_mlf_imported', true) !== '1') {
|
||||
return new WP_Error('not_ours', 'Cannot delete fonts not imported by this plugin');
|
||||
}
|
||||
|
||||
// Get font faces
|
||||
$faces = get_children([
|
||||
'post_parent' => $family_id,
|
||||
'post_type' => 'wp_font_face',
|
||||
]);
|
||||
|
||||
$font_dir = wp_get_font_dir();
|
||||
|
||||
// Delete font face files and posts
|
||||
foreach ($faces as $face) {
|
||||
$settings = json_decode($face->post_content, true);
|
||||
|
||||
if (isset($settings['src'])) {
|
||||
// Convert file:. URL to path
|
||||
$src = $settings['src'];
|
||||
$src = str_replace('file:.', '', $src);
|
||||
|
||||
// Handle both URL and path formats
|
||||
if (strpos($src, $font_dir['baseurl']) !== false) {
|
||||
$file_path = str_replace($font_dir['baseurl'], $font_dir['path'], $src);
|
||||
} else {
|
||||
$file_path = $font_dir['path'] . '/' . basename($src);
|
||||
}
|
||||
|
||||
// Validate path before deletion
|
||||
if (mlf_validate_font_path($file_path) && file_exists($file_path)) {
|
||||
wp_delete_file($file_path);
|
||||
}
|
||||
}
|
||||
|
||||
wp_delete_post($face->ID, true);
|
||||
}
|
||||
|
||||
// Delete family post
|
||||
wp_delete_post($family_id, true);
|
||||
|
||||
// Clear caches
|
||||
delete_transient('wp_font_library_fonts');
|
||||
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
### Listing Installed Fonts
|
||||
|
||||
```php
|
||||
/**
|
||||
* Get all fonts imported by this plugin.
|
||||
*
|
||||
* @return array Array of font data
|
||||
*/
|
||||
function mlf_get_imported_fonts() {
|
||||
$fonts = get_posts([
|
||||
'post_type' => 'wp_font_family',
|
||||
'posts_per_page' => 100,
|
||||
'post_status' => 'publish',
|
||||
'meta_key' => '_mlf_imported',
|
||||
'meta_value' => '1',
|
||||
]);
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($fonts as $font) {
|
||||
$settings = json_decode($font->post_content, true);
|
||||
|
||||
// Get variants
|
||||
$faces = get_children([
|
||||
'post_parent' => $font->ID,
|
||||
'post_type' => 'wp_font_face',
|
||||
]);
|
||||
|
||||
$variants = [];
|
||||
foreach ($faces as $face) {
|
||||
$face_settings = json_decode($face->post_content, true);
|
||||
$variants[] = [
|
||||
'weight' => $face_settings['fontWeight'] ?? '400',
|
||||
'style' => $face_settings['fontStyle'] ?? 'normal',
|
||||
];
|
||||
}
|
||||
|
||||
$result[] = [
|
||||
'id' => $font->ID,
|
||||
'name' => $settings['name'] ?? $font->post_title,
|
||||
'slug' => $settings['slug'] ?? $font->post_name,
|
||||
'variants' => $variants,
|
||||
'import_date' => get_post_meta($font->ID, '_mlf_import_date', true),
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Gutenberg FSE Integration
|
||||
|
||||
### How Fonts Appear in the Editor
|
||||
|
||||
Once registered via the Font Library API, fonts automatically appear in:
|
||||
|
||||
1. **Global Styles** → Typography → Font dropdown
|
||||
2. **Block settings** → Typography → Font dropdown (when per-block typography is enabled)
|
||||
|
||||
No additional integration code is needed — WordPress handles this automatically.
|
||||
|
||||
### Theme.json Compatibility
|
||||
|
||||
**DO NOT:**
|
||||
- Directly modify theme.json
|
||||
- Filter `wp_theme_json_data_theme` to inject fonts (let Font Library handle it)
|
||||
- Override global styles CSS directly
|
||||
|
||||
**DO:**
|
||||
- Use the Font Library API (post types)
|
||||
- Let WordPress generate CSS custom properties
|
||||
- Trust the system
|
||||
|
||||
### CSS Custom Properties
|
||||
|
||||
When a font is applied in Global Styles, WordPress generates:
|
||||
|
||||
```css
|
||||
body {
|
||||
--wp--preset--font-family--open-sans: "Open Sans", sans-serif;
|
||||
}
|
||||
```
|
||||
|
||||
And applies it:
|
||||
|
||||
```css
|
||||
body {
|
||||
font-family: var(--wp--preset--font-family--open-sans);
|
||||
}
|
||||
```
|
||||
|
||||
Our plugin doesn't need to touch this — it's automatic.
|
||||
|
||||
---
|
||||
|
||||
## WooCommerce Compatibility
|
||||
|
||||
### HPOS (High-Performance Order Storage)
|
||||
|
||||
WooCommerce's HPOS moves order data from post meta to custom tables. We must declare compatibility.
|
||||
|
||||
```php
|
||||
// Declare HPOS compatibility
|
||||
add_action('before_woocommerce_init', function() {
|
||||
if (class_exists(\Automattic\WooCommerce\Utilities\FeaturesUtil::class)) {
|
||||
\Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility(
|
||||
'custom_order_tables',
|
||||
__FILE__,
|
||||
true
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Why We're Compatible
|
||||
|
||||
Our plugin:
|
||||
- Does NOT interact with orders at all
|
||||
- Does NOT query wp_posts for order data
|
||||
- Does NOT use wp_postmeta for order data
|
||||
- Only uses wp_font_family and wp_font_face post types
|
||||
|
||||
We're inherently compatible because we don't touch WooCommerce data.
|
||||
|
||||
### Frontend Considerations
|
||||
|
||||
**DO NOT:**
|
||||
- Override `.woocommerce` class styles
|
||||
- Override `.wc-block-*` styles
|
||||
- Target cart/checkout elements specifically
|
||||
|
||||
**DO:**
|
||||
- Let WooCommerce elements inherit from body/heading fonts
|
||||
- Let global styles cascade naturally
|
||||
|
||||
WooCommerce product titles, descriptions, and other text will naturally inherit the fonts set via Global Styles. No special handling needed.
|
||||
|
||||
---
|
||||
|
||||
## Wordfence Compatibility
|
||||
|
||||
### Potential Concerns
|
||||
|
||||
1. **Outbound requests** to Google Fonts during import
|
||||
2. **AJAX endpoints** for admin actions
|
||||
3. **File operations** in wp-content
|
||||
|
||||
### Why We're Compatible
|
||||
|
||||
**Outbound Requests:**
|
||||
- Only occur during admin import (user-initiated action)
|
||||
- Target well-known domains (fonts.googleapis.com, fonts.gstatic.com)
|
||||
- Use standard `wp_remote_get()` which Wordfence allows
|
||||
- No runtime external requests on frontend
|
||||
|
||||
**AJAX Endpoints:**
|
||||
- Use standard `admin-ajax.php` (not custom endpoints)
|
||||
- Include proper nonces
|
||||
- Follow WordPress patterns that Wordfence expects
|
||||
|
||||
**File Operations:**
|
||||
- Write only to `wp-content/fonts/` (WordPress default directory)
|
||||
- Use WordPress Filesystem API
|
||||
- Don't create executable files
|
||||
|
||||
### Testing with Wordfence
|
||||
|
||||
Test these scenarios with Wordfence active:
|
||||
|
||||
- [ ] Learning Mode: Import should succeed
|
||||
- [ ] Enabled Mode: Import should succeed
|
||||
- [ ] Rate Limiting: Admin AJAX not blocked
|
||||
- [ ] Firewall: No false positives on font download
|
||||
|
||||
---
|
||||
|
||||
## LearnDash Compatibility
|
||||
|
||||
### Overview
|
||||
|
||||
LearnDash is a WordPress LMS that uses:
|
||||
- Custom post types (courses, lessons, topics, quizzes)
|
||||
- Custom templates
|
||||
- Focus Mode (distraction-free learning)
|
||||
|
||||
### Why We're Compatible
|
||||
|
||||
Our plugin:
|
||||
- Doesn't touch LearnDash post types
|
||||
- Doesn't modify LearnDash templates
|
||||
- Doesn't inject CSS on frontend
|
||||
- Lets Global Styles cascade to LearnDash content
|
||||
|
||||
LearnDash course content, lesson text, and quiz questions will inherit the fonts set in Global Styles automatically.
|
||||
|
||||
### Focus Mode Consideration
|
||||
|
||||
LearnDash Focus Mode uses its own template. Fonts set via Global Styles will apply because:
|
||||
- Focus Mode still loads theme.json styles
|
||||
- CSS custom properties cascade to all content
|
||||
- No special handling needed
|
||||
|
||||
**DO NOT:**
|
||||
- Target `.learndash-*` classes specifically
|
||||
- Override Focus Mode styles
|
||||
- Inject custom CSS for LearnDash
|
||||
|
||||
---
|
||||
|
||||
## WPForms Compatibility
|
||||
|
||||
### Overview
|
||||
|
||||
WPForms renders forms via shortcodes and blocks. Form styling is handled by WPForms.
|
||||
|
||||
### Why We're Compatible
|
||||
|
||||
- Form labels and text inherit from body font
|
||||
- We don't override `.wpforms-*` classes
|
||||
- No JavaScript conflicts (we have no frontend JS)
|
||||
|
||||
### Consideration
|
||||
|
||||
If a user wants form text in a different font, they should use WPForms' built-in styling options or custom CSS — not expect our plugin to handle it.
|
||||
|
||||
---
|
||||
|
||||
## General Best Practices
|
||||
|
||||
### What We Hook Into
|
||||
|
||||
```php
|
||||
// Admin menu
|
||||
add_action('admin_menu', [$this, 'register_menu']);
|
||||
|
||||
// Admin assets (only on our page)
|
||||
add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_assets']);
|
||||
|
||||
// AJAX handlers
|
||||
add_action('wp_ajax_mlf_download_font', [$this, 'handle_download']);
|
||||
add_action('wp_ajax_mlf_delete_font', [$this, 'handle_delete']);
|
||||
|
||||
// WooCommerce HPOS compatibility
|
||||
add_action('before_woocommerce_init', [$this, 'declare_hpos_compatibility']);
|
||||
```
|
||||
|
||||
### What We DON'T Hook Into
|
||||
|
||||
```php
|
||||
// NO frontend hooks
|
||||
// add_action('wp_enqueue_scripts', ...); // DON'T DO THIS
|
||||
// add_action('wp_head', ...); // DON'T DO THIS
|
||||
// add_action('wp_footer', ...); // DON'T DO THIS
|
||||
|
||||
// NO theme modification hooks
|
||||
// add_filter('wp_theme_json_data_theme', ...); // Let Font Library handle it
|
||||
|
||||
// NO WooCommerce hooks
|
||||
// add_action('woocommerce_*', ...); // DON'T DO THIS
|
||||
|
||||
// NO content filters
|
||||
// add_filter('the_content', ...); // DON'T DO THIS
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Conflict Debugging
|
||||
|
||||
If a user reports a conflict, check:
|
||||
|
||||
### 1. Plugin Load Order
|
||||
|
||||
Our plugin should load with default priority. Check if another plugin is:
|
||||
- Modifying the Font Library
|
||||
- Overriding font CSS
|
||||
- Filtering theme.json
|
||||
|
||||
### 2. CSS Specificity
|
||||
|
||||
If fonts aren't applying:
|
||||
- Check browser DevTools for CSS cascade
|
||||
- Look for more specific selectors overriding global styles
|
||||
- Check for `!important` declarations
|
||||
|
||||
### 3. Cache Issues
|
||||
|
||||
Font changes not appearing:
|
||||
- Clear browser cache
|
||||
- Clear any caching plugins (WP Rocket, W3TC, etc.)
|
||||
- Clear CDN cache if applicable
|
||||
- WordPress transients: `delete_transient('wp_font_library_fonts')`
|
||||
|
||||
### 4. JavaScript Errors
|
||||
|
||||
If admin page isn't working:
|
||||
- Check browser console for JS errors
|
||||
- Look for conflicts with other admin scripts
|
||||
- Verify jQuery isn't being dequeued
|
||||
|
||||
---
|
||||
|
||||
## Compatibility Checklist
|
||||
|
||||
Before releasing:
|
||||
|
||||
### WordPress Core
|
||||
- [ ] Works on WordPress 6.5
|
||||
- [ ] Works on WordPress 6.6+
|
||||
- [ ] Font Library API integration works
|
||||
- [ ] Fonts appear in Global Styles
|
||||
- [ ] Fonts apply correctly on frontend
|
||||
|
||||
### WooCommerce
|
||||
- [ ] HPOS compatibility declared
|
||||
- [ ] No errors in WooCommerce status page
|
||||
- [ ] Product pages render correctly with custom fonts
|
||||
- [ ] Cart/Checkout not affected
|
||||
|
||||
### Wordfence
|
||||
- [ ] Import works with firewall enabled
|
||||
- [ ] No blocked requests
|
||||
- [ ] No false positive security alerts
|
||||
|
||||
### LearnDash
|
||||
- [ ] Course content inherits fonts
|
||||
- [ ] Focus Mode renders correctly
|
||||
- [ ] No JavaScript conflicts
|
||||
|
||||
### WPForms
|
||||
- [ ] Forms render correctly
|
||||
- [ ] No styling conflicts
|
||||
|
||||
### Other
|
||||
- [ ] No PHP errors in debug.log
|
||||
- [ ] No JavaScript errors in console
|
||||
- [ ] Admin page loads correctly
|
||||
- [ ] No memory issues during import
|
||||
354
native/wordpress/maple-fonts-wp/assets/admin.css
Normal file
354
native/wordpress/maple-fonts-wp/assets/admin.css
Normal file
|
|
@ -0,0 +1,354 @@
|
|||
/**
|
||||
* Maple Local Fonts - Admin Styles
|
||||
*
|
||||
* @package Maple_Local_Fonts
|
||||
*/
|
||||
|
||||
/* Container */
|
||||
.mlf-wrap {
|
||||
max-width: 900px;
|
||||
}
|
||||
|
||||
.mlf-container {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
/* Sections */
|
||||
.mlf-section {
|
||||
background: #fff;
|
||||
border: 1px solid #c3c4c7;
|
||||
border-radius: 4px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.mlf-section h2 {
|
||||
margin-top: 0;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #c3c4c7;
|
||||
}
|
||||
|
||||
/* Form */
|
||||
.mlf-form-row {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.mlf-form-row label {
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.mlf-form-row input[type="text"] {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.mlf-form-row .description {
|
||||
margin-top: 8px;
|
||||
color: #646970;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Checkbox Grid */
|
||||
.mlf-checkbox-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.mlf-checkbox-grid-small {
|
||||
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.mlf-checkbox-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
background: #f6f7f7;
|
||||
border: 1px solid #dcdcde;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s ease;
|
||||
}
|
||||
|
||||
.mlf-checkbox-label:hover {
|
||||
background: #f0f0f1;
|
||||
}
|
||||
|
||||
.mlf-checkbox-label input[type="checkbox"] {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.mlf-checkbox-label input[type="checkbox"]:checked + span {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* File Count */
|
||||
.mlf-form-row-info {
|
||||
padding: 12px 16px;
|
||||
background: #f0f6fc;
|
||||
border: 1px solid #c5d9ed;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.mlf-file-count {
|
||||
color: #2271b1;
|
||||
}
|
||||
|
||||
.mlf-file-count strong {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
/* Font Preview */
|
||||
.mlf-preview-section {
|
||||
margin-top: 20px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #dcdcde;
|
||||
}
|
||||
|
||||
.mlf-preview-box {
|
||||
background: #fff;
|
||||
border: 1px solid #dcdcde;
|
||||
border-radius: 4px;
|
||||
padding: 24px;
|
||||
min-height: 100px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mlf-preview-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.mlf-preview-sample {
|
||||
display: block;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.mlf-preview-heading {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.mlf-preview-paragraph {
|
||||
font-size: 16px;
|
||||
color: #50575e;
|
||||
}
|
||||
|
||||
.mlf-preview-loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
color: #646970;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.mlf-preview-loading .spinner {
|
||||
float: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.mlf-preview-error {
|
||||
color: #b32d2e;
|
||||
padding: 20px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Submit Row */
|
||||
.mlf-form-row-submit {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.mlf-form-row-submit .spinner {
|
||||
float: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Messages */
|
||||
.mlf-message {
|
||||
padding: 12px 16px;
|
||||
border-radius: 4px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.mlf-message-success {
|
||||
background: #d4edda;
|
||||
border: 1px solid #c3e6cb;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.mlf-message-error {
|
||||
background: #f8d7da;
|
||||
border: 1px solid #f5c6cb;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
/* Font List */
|
||||
.mlf-font-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.mlf-font-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
background: #f6f7f7;
|
||||
border: 1px solid #dcdcde;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.mlf-font-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.mlf-font-name {
|
||||
margin: 0 0 4px 0;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.mlf-font-variants {
|
||||
margin: 0;
|
||||
color: #646970;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.mlf-font-actions {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.mlf-delete-btn {
|
||||
color: #b32d2e;
|
||||
border-color: #b32d2e;
|
||||
}
|
||||
|
||||
.mlf-delete-btn:hover {
|
||||
background: #b32d2e;
|
||||
color: #fff;
|
||||
border-color: #b32d2e;
|
||||
}
|
||||
|
||||
.mlf-delete-btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* No Fonts Message */
|
||||
.mlf-no-fonts {
|
||||
color: #646970;
|
||||
font-style: italic;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
background: #f6f7f7;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Info Box */
|
||||
.mlf-info-box {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
background: #f0f6fc;
|
||||
border: 1px solid #c5d9ed;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.mlf-info-box .dashicons {
|
||||
color: #2271b1;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.mlf-info-box p {
|
||||
margin: 0;
|
||||
color: #1d2327;
|
||||
}
|
||||
|
||||
.mlf-info-box a {
|
||||
color: #2271b1;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.mlf-info-box a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Classic Theme Info Box */
|
||||
.mlf-info-box-classic {
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
background: #fcf9e8;
|
||||
border-color: #d4b106;
|
||||
}
|
||||
|
||||
.mlf-info-box-classic .dashicons {
|
||||
color: #9d7e05;
|
||||
}
|
||||
|
||||
.mlf-classic-theme-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.mlf-classic-theme-info p {
|
||||
margin: 0 0 12px 0;
|
||||
}
|
||||
|
||||
.mlf-classic-theme-info p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.mlf-classic-theme-info .description {
|
||||
color: #646970;
|
||||
font-style: italic;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.mlf-code-example {
|
||||
background: #1d2327;
|
||||
color: #f0f0f1;
|
||||
padding: 12px 16px;
|
||||
border-radius: 4px;
|
||||
font-family: Consolas, Monaco, monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
overflow-x: auto;
|
||||
white-space: pre;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
/* Loading State */
|
||||
.mlf-loading {
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media screen and (max-width: 782px) {
|
||||
.mlf-checkbox-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.mlf-font-item {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.mlf-font-actions {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 480px) {
|
||||
.mlf-checkbox-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
354
native/wordpress/maple-fonts-wp/assets/admin.js
Normal file
354
native/wordpress/maple-fonts-wp/assets/admin.js
Normal file
|
|
@ -0,0 +1,354 @@
|
|||
/**
|
||||
* Maple Local Fonts - Admin JavaScript
|
||||
*
|
||||
* @package Maple_Local_Fonts
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
var MLF = {
|
||||
/**
|
||||
* Preview debounce timer.
|
||||
*/
|
||||
previewTimer: null,
|
||||
|
||||
/**
|
||||
* Currently loaded preview font.
|
||||
*/
|
||||
currentPreviewFont: null,
|
||||
|
||||
/**
|
||||
* Initialize the admin functionality.
|
||||
*/
|
||||
init: function() {
|
||||
this.bindEvents();
|
||||
this.updateFileCount();
|
||||
},
|
||||
|
||||
/**
|
||||
* Bind event handlers.
|
||||
*/
|
||||
bindEvents: function() {
|
||||
// Form submission
|
||||
$('#mlf-import-form').on('submit', this.handleDownload.bind(this));
|
||||
|
||||
// Delete button clicks
|
||||
$(document).on('click', '.mlf-delete-btn', this.handleDelete.bind(this));
|
||||
|
||||
// Update file count on checkbox change
|
||||
$('#mlf-import-form').on('change', 'input[type="checkbox"]', this.updateFileCount.bind(this));
|
||||
|
||||
// Font name input for preview (debounced)
|
||||
$('#mlf-font-name').on('input', this.handleFontNameInput.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle font name input for preview (debounced).
|
||||
*
|
||||
* @param {Event} e Input event.
|
||||
*/
|
||||
handleFontNameInput: function(e) {
|
||||
var fontName = $(e.target).val().trim();
|
||||
|
||||
// Clear previous timer
|
||||
if (this.previewTimer) {
|
||||
clearTimeout(this.previewTimer);
|
||||
}
|
||||
|
||||
// Hide preview if empty
|
||||
if (!fontName) {
|
||||
this.hidePreview();
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate font name (only allowed characters)
|
||||
if (!/^[a-zA-Z0-9\s\-]+$/.test(fontName)) {
|
||||
this.hidePreview();
|
||||
return;
|
||||
}
|
||||
|
||||
// Debounce: wait 500ms before loading preview
|
||||
this.previewTimer = setTimeout(function() {
|
||||
MLF.loadFontPreview(fontName);
|
||||
}, 500);
|
||||
},
|
||||
|
||||
/**
|
||||
* Load font preview from Google Fonts.
|
||||
*
|
||||
* @param {string} fontName Font family name.
|
||||
*/
|
||||
loadFontPreview: function(fontName) {
|
||||
var $section = $('#mlf-preview-section');
|
||||
var $text = $('#mlf-preview-text');
|
||||
var $loading = $('#mlf-preview-loading');
|
||||
var $error = $('#mlf-preview-error');
|
||||
|
||||
// Skip if same font already loaded
|
||||
if (this.currentPreviewFont === fontName) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Show section with loading state
|
||||
$section.show();
|
||||
$text.hide();
|
||||
$loading.show();
|
||||
$error.hide();
|
||||
|
||||
// Remove previous preview font link
|
||||
$('#mlf-preview-font-link').remove();
|
||||
|
||||
// Build Google Fonts URL for preview (just 400 weight for preview)
|
||||
var fontFamily = fontName.replace(/\s+/g, '+');
|
||||
var previewUrl = 'https://fonts.googleapis.com/css2?family=' + encodeURIComponent(fontFamily) + ':wght@400&display=swap';
|
||||
|
||||
// Create link element
|
||||
var $link = $('<link>', {
|
||||
id: 'mlf-preview-font-link',
|
||||
rel: 'stylesheet',
|
||||
href: previewUrl
|
||||
});
|
||||
|
||||
// Handle load success
|
||||
$link.on('load', function() {
|
||||
MLF.currentPreviewFont = fontName;
|
||||
$text.css('font-family', '"' + fontName + '", sans-serif');
|
||||
$loading.hide();
|
||||
$text.show();
|
||||
});
|
||||
|
||||
// Handle load error
|
||||
$link.on('error', function() {
|
||||
MLF.currentPreviewFont = null;
|
||||
$loading.hide();
|
||||
$error.show();
|
||||
});
|
||||
|
||||
// Append to head
|
||||
$('head').append($link);
|
||||
|
||||
// Fallback timeout (5 seconds)
|
||||
setTimeout(function() {
|
||||
if ($loading.is(':visible')) {
|
||||
// Check if font actually loaded by measuring text width
|
||||
var testSpan = $('<span>').text('test').css({
|
||||
'font-family': '"' + fontName + '", monospace',
|
||||
'position': 'absolute',
|
||||
'visibility': 'hidden'
|
||||
}).appendTo('body');
|
||||
|
||||
var testWidth = testSpan.width();
|
||||
|
||||
var fallbackSpan = $('<span>').text('test').css({
|
||||
'font-family': 'monospace',
|
||||
'position': 'absolute',
|
||||
'visibility': 'hidden'
|
||||
}).appendTo('body');
|
||||
|
||||
var fallbackWidth = fallbackSpan.width();
|
||||
|
||||
testSpan.remove();
|
||||
fallbackSpan.remove();
|
||||
|
||||
if (testWidth !== fallbackWidth) {
|
||||
// Font loaded successfully
|
||||
MLF.currentPreviewFont = fontName;
|
||||
$text.css('font-family', '"' + fontName + '", sans-serif');
|
||||
$loading.hide();
|
||||
$text.show();
|
||||
} else {
|
||||
// Font failed to load
|
||||
MLF.currentPreviewFont = null;
|
||||
$loading.hide();
|
||||
$error.show();
|
||||
}
|
||||
}
|
||||
}, 5000);
|
||||
},
|
||||
|
||||
/**
|
||||
* Hide the font preview section.
|
||||
*/
|
||||
hidePreview: function() {
|
||||
this.currentPreviewFont = null;
|
||||
$('#mlf-preview-section').hide();
|
||||
$('#mlf-preview-font-link').remove();
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the file count display.
|
||||
*/
|
||||
updateFileCount: function() {
|
||||
var weights = $('input[name="weights[]"]:checked').length;
|
||||
var styles = $('input[name="styles[]"]:checked').length;
|
||||
var count = weights * styles;
|
||||
|
||||
$('#mlf-file-count').text(count);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle font download form submission.
|
||||
*
|
||||
* @param {Event} e Form submit event.
|
||||
*/
|
||||
handleDownload: function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var $form = $('#mlf-import-form');
|
||||
var $button = $('#mlf-download-btn');
|
||||
var $spinner = $('#mlf-spinner');
|
||||
var $message = $('#mlf-message');
|
||||
|
||||
// Get form values
|
||||
var fontName = $('#mlf-font-name').val().trim();
|
||||
var weights = [];
|
||||
var styles = [];
|
||||
|
||||
$('input[name="weights[]"]:checked').each(function() {
|
||||
weights.push($(this).val());
|
||||
});
|
||||
|
||||
$('input[name="styles[]"]:checked').each(function() {
|
||||
styles.push($(this).val());
|
||||
});
|
||||
|
||||
// Validate
|
||||
if (!fontName) {
|
||||
this.showMessage($message, mapleLocalFontsData.strings.enterFontName, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (weights.length === 0) {
|
||||
this.showMessage($message, mapleLocalFontsData.strings.selectWeight, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (styles.length === 0) {
|
||||
this.showMessage($message, mapleLocalFontsData.strings.selectStyle, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable form
|
||||
$form.addClass('mlf-loading');
|
||||
$button.prop('disabled', true).text(mapleLocalFontsData.strings.downloading);
|
||||
$spinner.addClass('is-active');
|
||||
$message.hide();
|
||||
|
||||
// Send AJAX request
|
||||
$.ajax({
|
||||
url: mapleLocalFontsData.ajaxUrl,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'mlf_download_font',
|
||||
nonce: mapleLocalFontsData.downloadNonce,
|
||||
font_name: fontName,
|
||||
weights: weights,
|
||||
styles: styles
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
MLF.showMessage($message, response.data.message, 'success');
|
||||
// Reload page to show new font
|
||||
setTimeout(function() {
|
||||
window.location.reload();
|
||||
}, 1500);
|
||||
} else {
|
||||
MLF.showMessage($message, response.data.message || mapleLocalFontsData.strings.error, 'error');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
MLF.showMessage($message, mapleLocalFontsData.strings.error, 'error');
|
||||
},
|
||||
complete: function() {
|
||||
$form.removeClass('mlf-loading');
|
||||
$button.prop('disabled', false).text($button.data('original-text') || 'Download & Install');
|
||||
$spinner.removeClass('is-active');
|
||||
}
|
||||
});
|
||||
|
||||
// Store original button text
|
||||
if (!$button.data('original-text')) {
|
||||
$button.data('original-text', $button.text());
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle font deletion.
|
||||
*
|
||||
* @param {Event} e Click event.
|
||||
*/
|
||||
handleDelete: function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var $button = $(e.currentTarget);
|
||||
var fontId = $button.data('font-id');
|
||||
var $fontItem = $button.closest('.mlf-font-item');
|
||||
|
||||
// Confirm deletion
|
||||
if (!confirm(mapleLocalFontsData.strings.confirmDelete)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable button
|
||||
$button.prop('disabled', true).text(mapleLocalFontsData.strings.deleting);
|
||||
$fontItem.addClass('mlf-loading');
|
||||
|
||||
// Send AJAX request
|
||||
$.ajax({
|
||||
url: mapleLocalFontsData.ajaxUrl,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'mlf_delete_font',
|
||||
nonce: mapleLocalFontsData.deleteNonce,
|
||||
font_id: fontId
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
// Remove font item with animation
|
||||
$fontItem.slideUp(300, function() {
|
||||
$(this).remove();
|
||||
|
||||
// Check if any fonts remain
|
||||
if ($('.mlf-font-item').length === 0) {
|
||||
$('#mlf-font-list').html(
|
||||
'<p class="mlf-no-fonts">No fonts installed yet.</p>'
|
||||
);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
alert(response.data.message || mapleLocalFontsData.strings.error);
|
||||
$button.prop('disabled', false).text('Delete');
|
||||
$fontItem.removeClass('mlf-loading');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
alert(mapleLocalFontsData.strings.error);
|
||||
$button.prop('disabled', false).text('Delete');
|
||||
$fontItem.removeClass('mlf-loading');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Show a message to the user.
|
||||
*
|
||||
* @param {jQuery} $element Message element.
|
||||
* @param {string} message Message text.
|
||||
* @param {string} type Message type (success or error).
|
||||
*/
|
||||
showMessage: function($element, message, type) {
|
||||
$element
|
||||
.removeClass('mlf-message-success mlf-message-error')
|
||||
.addClass('mlf-message-' + type)
|
||||
.text(message)
|
||||
.show();
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize on document ready
|
||||
$(document).ready(function() {
|
||||
MLF.init();
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
5
native/wordpress/maple-fonts-wp/assets/index.php
Normal file
5
native/wordpress/maple-fonts-wp/assets/index.php
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?php
|
||||
// Silence is golden.
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
39
native/wordpress/maple-fonts-wp/composer.json
Normal file
39
native/wordpress/maple-fonts-wp/composer.json
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"name": "mapleopentech/maple-local-fonts",
|
||||
"description": "Import Google Fonts to local storage for GDPR-compliant, privacy-friendly typography in WordPress.",
|
||||
"type": "wordpress-plugin",
|
||||
"license": "GPL-2.0-or-later",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Maple Open Technologies",
|
||||
"homepage": "https://mapleopentech.org"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.6",
|
||||
"wp-coding-standards/wpcs": "^3.0",
|
||||
"phpcompatibility/phpcompatibility-wp": "^2.1",
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "^1.0",
|
||||
"yoast/phpunit-polyfills": "^2.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "phpunit",
|
||||
"phpcs": "phpcs",
|
||||
"phpcbf": "phpcbf",
|
||||
"compat": "phpcs -p --standard=PHPCompatibilityWP --runtime-set testVersion 7.4- --extensions=php --ignore=vendor,tests ."
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": true
|
||||
},
|
||||
"sort-packages": true
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"MapleLocalFonts\\Tests\\": "tests/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,210 @@
|
|||
<?php
|
||||
/**
|
||||
* Admin Page for Maple Local Fonts.
|
||||
*
|
||||
* @package Maple_Local_Fonts
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class MLF_Admin_Page
|
||||
*
|
||||
* Handles the admin settings page rendering.
|
||||
*/
|
||||
class MLF_Admin_Page {
|
||||
|
||||
/**
|
||||
* Available font weights.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $weights = [
|
||||
100 => 'Thin',
|
||||
200 => 'Extra Light',
|
||||
300 => 'Light',
|
||||
400 => 'Regular',
|
||||
500 => 'Medium',
|
||||
600 => 'Semi Bold',
|
||||
700 => 'Bold',
|
||||
800 => 'Extra Bold',
|
||||
900 => 'Black',
|
||||
];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
// Empty constructor - class is instantiated for rendering
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the admin page.
|
||||
*/
|
||||
public function render() {
|
||||
$capability = function_exists('mlf_get_capability') ? mlf_get_capability() : 'edit_theme_options';
|
||||
if (!current_user_can($capability)) {
|
||||
wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'maple-local-fonts'));
|
||||
}
|
||||
|
||||
$registry = new MLF_Font_Registry();
|
||||
$installed_fonts = $registry->get_imported_fonts();
|
||||
?>
|
||||
<div class="wrap mlf-wrap">
|
||||
<h1><?php esc_html_e('Maple Local Fonts', 'maple-local-fonts'); ?></h1>
|
||||
|
||||
<div class="mlf-container">
|
||||
<!-- Import Section -->
|
||||
<div class="mlf-section mlf-import-section">
|
||||
<h2><?php esc_html_e('Import from Google Fonts', 'maple-local-fonts'); ?></h2>
|
||||
|
||||
<form id="mlf-import-form" class="mlf-form">
|
||||
<div class="mlf-form-row">
|
||||
<label for="mlf-font-name"><?php esc_html_e('Font Name', 'maple-local-fonts'); ?></label>
|
||||
<input type="text" id="mlf-font-name" name="font_name" placeholder="<?php esc_attr_e('e.g., Open Sans', 'maple-local-fonts'); ?>" required />
|
||||
<p class="description"><?php esc_html_e('Enter the exact font name as it appears on Google Fonts.', 'maple-local-fonts'); ?></p>
|
||||
</div>
|
||||
|
||||
<div class="mlf-form-row">
|
||||
<label><?php esc_html_e('Weights', 'maple-local-fonts'); ?></label>
|
||||
<div class="mlf-checkbox-grid">
|
||||
<?php foreach ($this->weights as $weight => $label) : ?>
|
||||
<label class="mlf-checkbox-label">
|
||||
<input type="checkbox" name="weights[]" value="<?php echo esc_attr($weight); ?>" <?php checked(in_array($weight, [400, 700], true)); ?> />
|
||||
<span><?php echo esc_html($weight); ?> (<?php echo esc_html($label); ?>)</span>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mlf-form-row">
|
||||
<label><?php esc_html_e('Styles', 'maple-local-fonts'); ?></label>
|
||||
<div class="mlf-checkbox-grid mlf-checkbox-grid-small">
|
||||
<label class="mlf-checkbox-label">
|
||||
<input type="checkbox" name="styles[]" value="normal" checked />
|
||||
<span><?php esc_html_e('Normal', 'maple-local-fonts'); ?></span>
|
||||
</label>
|
||||
<label class="mlf-checkbox-label">
|
||||
<input type="checkbox" name="styles[]" value="italic" />
|
||||
<span><?php esc_html_e('Italic', 'maple-local-fonts'); ?></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mlf-form-row mlf-form-row-info">
|
||||
<span class="mlf-file-count">
|
||||
<?php esc_html_e('Files to download:', 'maple-local-fonts'); ?>
|
||||
<strong id="mlf-file-count">2</strong>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Font Preview Section -->
|
||||
<div class="mlf-form-row mlf-preview-section" id="mlf-preview-section" style="display: none;">
|
||||
<label><?php esc_html_e('Preview', 'maple-local-fonts'); ?></label>
|
||||
<div class="mlf-preview-box" id="mlf-preview-box">
|
||||
<div class="mlf-preview-text" id="mlf-preview-text">
|
||||
<span class="mlf-preview-sample mlf-preview-heading"><?php esc_html_e('The quick brown fox jumps over the lazy dog', 'maple-local-fonts'); ?></span>
|
||||
<span class="mlf-preview-sample mlf-preview-paragraph"><?php esc_html_e('ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz 0123456789', 'maple-local-fonts'); ?></span>
|
||||
</div>
|
||||
<div class="mlf-preview-loading" id="mlf-preview-loading" style="display: none;">
|
||||
<span class="spinner is-active"></span>
|
||||
<span><?php esc_html_e('Loading preview...', 'maple-local-fonts'); ?></span>
|
||||
</div>
|
||||
<div class="mlf-preview-error" id="mlf-preview-error" style="display: none;">
|
||||
<?php esc_html_e('Could not load font preview. The font may not exist on Google Fonts.', 'maple-local-fonts'); ?>
|
||||
</div>
|
||||
</div>
|
||||
<p class="description"><?php esc_html_e('Preview is loaded directly from Google Fonts. After installation, the font will be served locally.', 'maple-local-fonts'); ?></p>
|
||||
</div>
|
||||
|
||||
<div class="mlf-form-row mlf-form-row-submit">
|
||||
<button type="submit" class="button button-primary" id="mlf-download-btn">
|
||||
<?php esc_html_e('Download & Install', 'maple-local-fonts'); ?>
|
||||
</button>
|
||||
<span class="spinner" id="mlf-spinner"></span>
|
||||
</div>
|
||||
|
||||
<div id="mlf-message" class="mlf-message" style="display: none;"></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Installed Fonts Section -->
|
||||
<div class="mlf-section mlf-installed-section">
|
||||
<h2><?php esc_html_e('Installed Fonts', 'maple-local-fonts'); ?></h2>
|
||||
|
||||
<?php if (empty($installed_fonts)) : ?>
|
||||
<p class="mlf-no-fonts"><?php esc_html_e('No fonts installed yet.', 'maple-local-fonts'); ?></p>
|
||||
<?php else : ?>
|
||||
<div id="mlf-font-list" class="mlf-font-list">
|
||||
<?php foreach ($installed_fonts as $font) : ?>
|
||||
<div class="mlf-font-item" data-font-id="<?php echo esc_attr($font['id']); ?>">
|
||||
<div class="mlf-font-info">
|
||||
<h3 class="mlf-font-name"><?php echo esc_html($font['name']); ?></h3>
|
||||
<p class="mlf-font-variants">
|
||||
<?php
|
||||
$variant_strings = [];
|
||||
foreach ($font['variants'] as $variant) {
|
||||
$variant_strings[] = sprintf('%s %s', $variant['weight'], $variant['style']);
|
||||
}
|
||||
echo esc_html(implode(', ', $variant_strings));
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
<div class="mlf-font-actions">
|
||||
<button type="button" class="button mlf-delete-btn" data-font-id="<?php echo esc_attr($font['id']); ?>">
|
||||
<?php esc_html_e('Delete', 'maple-local-fonts'); ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Info Section -->
|
||||
<div class="mlf-section mlf-info-section">
|
||||
<?php if (wp_is_block_theme()) : ?>
|
||||
<div class="mlf-info-box">
|
||||
<span class="dashicons dashicons-info"></span>
|
||||
<p>
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %s: link to WordPress Editor */
|
||||
esc_html__('Use %s to apply fonts to your site.', 'maple-local-fonts'),
|
||||
'<a href="' . esc_url(admin_url('site-editor.php?path=%2Fwp_global_styles')) . '">' . esc_html__('Appearance → Editor → Styles → Typography', 'maple-local-fonts') . '</a>'
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<div class="mlf-info-box mlf-info-box-classic">
|
||||
<span class="dashicons dashicons-info"></span>
|
||||
<div class="mlf-classic-theme-info">
|
||||
<p><strong><?php esc_html_e('Classic Theme Detected', 'maple-local-fonts'); ?></strong></p>
|
||||
<p><?php esc_html_e('Your theme does not support the Full Site Editor. To use imported fonts, add custom CSS to your theme:', 'maple-local-fonts'); ?></p>
|
||||
<pre class="mlf-code-example">body {
|
||||
font-family: "Open Sans", sans-serif;
|
||||
}
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: "Open Sans", sans-serif;
|
||||
}</pre>
|
||||
<p class="description">
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %s: link to Customizer */
|
||||
esc_html__('Add this CSS in %s or your theme\'s style.css file.', 'maple-local-fonts'),
|
||||
'<a href="' . esc_url(admin_url('customize.php')) . '">' . esc_html__('Appearance → Customize → Additional CSS', 'maple-local-fonts') . '</a>'
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,227 @@
|
|||
<?php
|
||||
/**
|
||||
* AJAX Handler for Maple Local Fonts.
|
||||
*
|
||||
* @package Maple_Local_Fonts
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class MLF_Ajax_Handler
|
||||
*
|
||||
* Handles all AJAX requests for font download and deletion.
|
||||
*/
|
||||
class MLF_Ajax_Handler {
|
||||
|
||||
/**
|
||||
* Rate limiter instance.
|
||||
*
|
||||
* @var MLF_Rate_Limiter
|
||||
*/
|
||||
private $rate_limiter;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action('wp_ajax_mlf_download_font', [$this, 'handle_download']);
|
||||
add_action('wp_ajax_mlf_delete_font', [$this, 'handle_delete']);
|
||||
// NEVER add wp_ajax_nopriv_ - admin only functionality
|
||||
|
||||
// Initialize rate limiter: 10 requests per minute
|
||||
$this->rate_limiter = new MLF_Rate_Limiter(10, 60);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle font download AJAX request.
|
||||
*/
|
||||
public function handle_download() {
|
||||
// 1. NONCE CHECK
|
||||
if (!check_ajax_referer('mlf_download_font', 'nonce', false)) {
|
||||
wp_send_json_error(['message' => __('Security check failed.', 'maple-local-fonts')], 403);
|
||||
}
|
||||
|
||||
// 2. CAPABILITY CHECK
|
||||
$capability = function_exists('mlf_get_capability') ? mlf_get_capability() : 'edit_theme_options';
|
||||
if (!current_user_can($capability)) {
|
||||
wp_send_json_error(['message' => __('Unauthorized.', 'maple-local-fonts')], 403);
|
||||
}
|
||||
|
||||
// 3. RATE LIMIT CHECK
|
||||
if (!$this->rate_limiter->check_and_record('download')) {
|
||||
wp_send_json_error([
|
||||
'message' => __('Too many requests. Please wait a moment and try again.', 'maple-local-fonts'),
|
||||
], 429);
|
||||
}
|
||||
|
||||
// 4. INPUT VALIDATION
|
||||
// Validate font name
|
||||
$font_name = isset($_POST['font_name']) ? sanitize_text_field(wp_unslash($_POST['font_name'])) : '';
|
||||
|
||||
if (empty($font_name)) {
|
||||
wp_send_json_error(['message' => __('Font name is required.', 'maple-local-fonts')]);
|
||||
}
|
||||
|
||||
// Strict allowlist pattern - alphanumeric, spaces, hyphens only
|
||||
if (!preg_match('/^[a-zA-Z0-9\s\-]+$/', $font_name)) {
|
||||
wp_send_json_error(['message' => __('Invalid font name: only letters, numbers, spaces, and hyphens allowed.', 'maple-local-fonts')]);
|
||||
}
|
||||
|
||||
if (strlen($font_name) > 100) {
|
||||
wp_send_json_error(['message' => __('Font name is too long.', 'maple-local-fonts')]);
|
||||
}
|
||||
|
||||
// Validate weights
|
||||
$weights = isset($_POST['weights']) ? array_map('absint', (array) $_POST['weights']) : [];
|
||||
$allowed_weights = [100, 200, 300, 400, 500, 600, 700, 800, 900];
|
||||
$weights = array_intersect($weights, $allowed_weights);
|
||||
|
||||
if (empty($weights)) {
|
||||
wp_send_json_error(['message' => __('At least one weight is required.', 'maple-local-fonts')]);
|
||||
}
|
||||
|
||||
if (count($weights) > MLF_MAX_WEIGHTS_PER_FONT) {
|
||||
wp_send_json_error(['message' => __('Too many weights selected.', 'maple-local-fonts')]);
|
||||
}
|
||||
|
||||
// Validate styles
|
||||
$styles = isset($_POST['styles']) ? (array) $_POST['styles'] : [];
|
||||
$allowed_styles = ['normal', 'italic'];
|
||||
// Sanitize each style value before filtering
|
||||
$styles = array_map('sanitize_text_field', $styles);
|
||||
$styles = array_filter($styles, function($style) use ($allowed_styles) {
|
||||
return in_array($style, $allowed_styles, true);
|
||||
});
|
||||
|
||||
if (empty($styles)) {
|
||||
wp_send_json_error(['message' => __('At least one style is required.', 'maple-local-fonts')]);
|
||||
}
|
||||
|
||||
// 5. PROCESS REQUEST
|
||||
try {
|
||||
$downloader = new MLF_Font_Downloader();
|
||||
$download_result = $downloader->download($font_name, $weights, $styles);
|
||||
|
||||
if (is_wp_error($download_result)) {
|
||||
wp_send_json_error(['message' => $this->get_user_error_message($download_result)]);
|
||||
}
|
||||
|
||||
// Register font with WordPress
|
||||
$registry = new MLF_Font_Registry();
|
||||
$result = $registry->register_font(
|
||||
$download_result['font_name'],
|
||||
$download_result['font_slug'],
|
||||
$download_result['files']
|
||||
);
|
||||
|
||||
if (is_wp_error($result)) {
|
||||
wp_send_json_error(['message' => $this->get_user_error_message($result)]);
|
||||
}
|
||||
|
||||
wp_send_json_success([
|
||||
'message' => sprintf(
|
||||
/* translators: %s: font name */
|
||||
__('Successfully installed %s.', 'maple-local-fonts'),
|
||||
esc_html($font_name)
|
||||
),
|
||||
'font_id' => $result,
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
// Sanitize exception message before logging (defense in depth)
|
||||
error_log('MLF Download Error: ' . sanitize_text_field($e->getMessage()));
|
||||
wp_send_json_error(['message' => __('An unexpected error occurred.', 'maple-local-fonts')]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle font deletion AJAX request.
|
||||
*/
|
||||
public function handle_delete() {
|
||||
// 1. NONCE CHECK
|
||||
if (!check_ajax_referer('mlf_delete_font', 'nonce', false)) {
|
||||
wp_send_json_error(['message' => __('Security check failed.', 'maple-local-fonts')], 403);
|
||||
}
|
||||
|
||||
// 2. CAPABILITY CHECK
|
||||
$capability = function_exists('mlf_get_capability') ? mlf_get_capability() : 'edit_theme_options';
|
||||
if (!current_user_can($capability)) {
|
||||
wp_send_json_error(['message' => __('Unauthorized.', 'maple-local-fonts')], 403);
|
||||
}
|
||||
|
||||
// 3. RATE LIMIT CHECK
|
||||
if (!$this->rate_limiter->check_and_record('delete')) {
|
||||
wp_send_json_error([
|
||||
'message' => __('Too many requests. Please wait a moment and try again.', 'maple-local-fonts'),
|
||||
], 429);
|
||||
}
|
||||
|
||||
// 4. INPUT VALIDATION
|
||||
$font_id = isset($_POST['font_id']) ? absint($_POST['font_id']) : 0;
|
||||
|
||||
if ($font_id < 1) {
|
||||
wp_send_json_error(['message' => __('Invalid font ID.', 'maple-local-fonts')]);
|
||||
}
|
||||
|
||||
// Verify font exists and is a font family
|
||||
$font = get_post($font_id);
|
||||
if (!$font || $font->post_type !== 'wp_font_family') {
|
||||
wp_send_json_error(['message' => __('Font not found.', 'maple-local-fonts')]);
|
||||
}
|
||||
|
||||
// Verify it's one we imported (not a theme font)
|
||||
if (get_post_meta($font_id, '_mlf_imported', true) !== '1') {
|
||||
wp_send_json_error(['message' => __('Cannot delete fonts not imported by this plugin.', 'maple-local-fonts')]);
|
||||
}
|
||||
|
||||
// 5. PROCESS REQUEST
|
||||
try {
|
||||
$registry = new MLF_Font_Registry();
|
||||
$result = $registry->delete_font($font_id);
|
||||
|
||||
if (is_wp_error($result)) {
|
||||
wp_send_json_error(['message' => $this->get_user_error_message($result)]);
|
||||
}
|
||||
|
||||
wp_send_json_success(['message' => __('Font deleted successfully.', 'maple-local-fonts')]);
|
||||
} catch (Exception $e) {
|
||||
// Sanitize exception message before logging (defense in depth)
|
||||
error_log('MLF Delete Error: ' . sanitize_text_field($e->getMessage()));
|
||||
wp_send_json_error(['message' => __('An unexpected error occurred.', 'maple-local-fonts')]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert internal error codes to user-friendly messages.
|
||||
*
|
||||
* @param WP_Error $error The error object.
|
||||
* @return string User-friendly message.
|
||||
*/
|
||||
private function get_user_error_message($error) {
|
||||
$code = $error->get_error_code();
|
||||
|
||||
$messages = [
|
||||
'font_not_found' => __('Font not found on Google Fonts. Please check the spelling and try again.', 'maple-local-fonts'),
|
||||
'font_exists' => __('This font is already installed.', 'maple-local-fonts'),
|
||||
'request_failed' => __('Could not connect to Google Fonts. Please check your internet connection and try again.', 'maple-local-fonts'),
|
||||
'http_error' => __('Google Fonts returned an error. Please try again later.', 'maple-local-fonts'),
|
||||
'parse_failed' => __('Could not process the font data. The font may not be available.', 'maple-local-fonts'),
|
||||
'download_failed' => __('Could not download the font files. Please try again.', 'maple-local-fonts'),
|
||||
'write_failed' => __('Could not save font files. Please check that wp-content/fonts is writable.', 'maple-local-fonts'),
|
||||
'mkdir_failed' => __('Could not create fonts directory. Please check file permissions.', 'maple-local-fonts'),
|
||||
'invalid_path' => __('Invalid file path.', 'maple-local-fonts'),
|
||||
'invalid_url' => __('Invalid font URL.', 'maple-local-fonts'),
|
||||
'invalid_name' => __('Invalid font name.', 'maple-local-fonts'),
|
||||
'invalid_weights' => __('No valid weights specified.', 'maple-local-fonts'),
|
||||
'invalid_styles' => __('No valid styles specified.', 'maple-local-fonts'),
|
||||
'not_found' => __('Font not found.', 'maple-local-fonts'),
|
||||
'not_ours' => __('Cannot delete fonts not imported by this plugin.', 'maple-local-fonts'),
|
||||
'response_too_large' => __('The font data is too large to process. Please try selecting fewer weights.', 'maple-local-fonts'),
|
||||
'file_too_large' => __('The font file is too large to download.', 'maple-local-fonts'),
|
||||
];
|
||||
|
||||
return $messages[$code] ?? __('An unexpected error occurred. Please try again.', 'maple-local-fonts');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,504 @@
|
|||
<?php
|
||||
/**
|
||||
* Font Downloader for Maple Local Fonts.
|
||||
*
|
||||
* @package Maple_Local_Fonts
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class MLF_Font_Downloader
|
||||
*
|
||||
* Handles downloading fonts from Google Fonts CSS2 API.
|
||||
*/
|
||||
class MLF_Font_Downloader {
|
||||
|
||||
/**
|
||||
* User agent to send with requests (needed to get WOFF2 format).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
||||
|
||||
/**
|
||||
* Download a font from Google Fonts.
|
||||
*
|
||||
* @param string $font_name Font family name.
|
||||
* @param array $weights Weights to download.
|
||||
* @param array $styles Styles to download.
|
||||
* @return array|WP_Error Download result or error.
|
||||
*/
|
||||
public function download($font_name, $weights, $styles) {
|
||||
// Validate inputs
|
||||
if (empty($font_name) || !preg_match('/^[a-zA-Z0-9\s\-]+$/', $font_name)) {
|
||||
return new WP_Error('invalid_name', 'Invalid font name');
|
||||
}
|
||||
|
||||
$weights = array_intersect(array_map('absint', $weights), [100, 200, 300, 400, 500, 600, 700, 800, 900]);
|
||||
if (empty($weights)) {
|
||||
return new WP_Error('invalid_weights', 'No valid weights specified');
|
||||
}
|
||||
|
||||
$styles = array_intersect($styles, ['normal', 'italic']);
|
||||
if (empty($styles)) {
|
||||
return new WP_Error('invalid_styles', 'No valid styles specified');
|
||||
}
|
||||
|
||||
// Fetch CSS from Google
|
||||
$css = $this->fetch_css($font_name, $weights, $styles);
|
||||
if (is_wp_error($css)) {
|
||||
return $css;
|
||||
}
|
||||
|
||||
// Parse CSS to get font face data
|
||||
$font_faces = $this->parse_css($css, $font_name);
|
||||
if (is_wp_error($font_faces)) {
|
||||
return $font_faces;
|
||||
}
|
||||
|
||||
// Download each font file
|
||||
$font_slug = sanitize_title($font_name);
|
||||
$downloaded = $this->download_files($font_faces, $font_slug);
|
||||
if (is_wp_error($downloaded)) {
|
||||
return $downloaded;
|
||||
}
|
||||
|
||||
return [
|
||||
'font_name' => $font_name,
|
||||
'font_slug' => $font_slug,
|
||||
'files' => $downloaded,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build Google Fonts CSS2 API URL.
|
||||
*
|
||||
* @param string $font_name Font family name.
|
||||
* @param array $weights Array of weights.
|
||||
* @param array $styles Array of styles.
|
||||
* @return string Google Fonts CSS2 URL.
|
||||
*/
|
||||
private function build_url($font_name, $weights, $styles) {
|
||||
// URL-encode font name (spaces become +)
|
||||
$family = str_replace(' ', '+', $font_name);
|
||||
|
||||
// Sort for consistent URLs
|
||||
sort($weights);
|
||||
|
||||
$has_italic = in_array('italic', $styles, true);
|
||||
$has_normal = in_array('normal', $styles, true);
|
||||
|
||||
// If only normal styles, simpler format
|
||||
if ($has_normal && !$has_italic) {
|
||||
$wght = implode(';', $weights);
|
||||
return "https://fonts.googleapis.com/css2?family={$family}:wght@{$wght}&display=swap";
|
||||
}
|
||||
|
||||
// Full format with ital axis
|
||||
$variations = [];
|
||||
foreach ($weights as $weight) {
|
||||
if ($has_normal) {
|
||||
$variations[] = "0,{$weight}";
|
||||
}
|
||||
if ($has_italic) {
|
||||
$variations[] = "1,{$weight}";
|
||||
}
|
||||
}
|
||||
|
||||
$variation_string = implode(';', $variations);
|
||||
return "https://fonts.googleapis.com/css2?family={$family}:ital,wght@{$variation_string}&display=swap";
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch CSS from Google Fonts API.
|
||||
*
|
||||
* @param string $font_name Font family name.
|
||||
* @param array $weights Weights to fetch.
|
||||
* @param array $styles Styles to fetch.
|
||||
* @return string|WP_Error CSS content or error.
|
||||
*/
|
||||
private function fetch_css($font_name, $weights, $styles) {
|
||||
$url = $this->build_url($font_name, $weights, $styles);
|
||||
|
||||
// Validate URL
|
||||
if (!$this->is_valid_google_fonts_url($url)) {
|
||||
return new WP_Error('invalid_url', 'Invalid Google Fonts URL');
|
||||
}
|
||||
|
||||
// CRITICAL: Must use modern browser user-agent to get WOFF2
|
||||
$response = wp_remote_get($url, [
|
||||
'timeout' => MLF_REQUEST_TIMEOUT,
|
||||
'sslverify' => true,
|
||||
'user-agent' => $this->user_agent,
|
||||
]);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
return new WP_Error('request_failed', $response->get_error_message());
|
||||
}
|
||||
|
||||
$status = wp_remote_retrieve_response_code($response);
|
||||
if ($status === 400) {
|
||||
return new WP_Error('font_not_found', 'Font not found');
|
||||
}
|
||||
if ($status !== 200) {
|
||||
return new WP_Error('http_error', 'HTTP ' . $status);
|
||||
}
|
||||
|
||||
$css = wp_remote_retrieve_body($response);
|
||||
if (empty($css)) {
|
||||
return new WP_Error('empty_response', 'Empty response from Google Fonts');
|
||||
}
|
||||
|
||||
// Check CSS response size to prevent memory issues
|
||||
$max_size = defined('MLF_MAX_CSS_SIZE') ? MLF_MAX_CSS_SIZE : 512 * 1024;
|
||||
if (strlen($css) > $max_size) {
|
||||
return new WP_Error('response_too_large', 'CSS response exceeds maximum size limit');
|
||||
}
|
||||
|
||||
// Verify we got WOFF2 (sanity check)
|
||||
if (strpos($css, '.woff2)') === false) {
|
||||
return new WP_Error('wrong_format', 'Did not receive WOFF2 format');
|
||||
}
|
||||
|
||||
return $css;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse Google Fonts CSS and extract font face data.
|
||||
*
|
||||
* @param string $css CSS content from Google Fonts.
|
||||
* @param string $font_name Expected font family name.
|
||||
* @return array|WP_Error Array of font face data or error.
|
||||
*/
|
||||
private function parse_css($css, $font_name) {
|
||||
$font_faces = [];
|
||||
|
||||
// Match all @font-face blocks
|
||||
$pattern = '/@font-face\s*\{([^}]+)\}/s';
|
||||
if (!preg_match_all($pattern, $css, $matches)) {
|
||||
return new WP_Error('parse_failed', 'Could not parse CSS - no @font-face rules found');
|
||||
}
|
||||
|
||||
foreach ($matches[1] as $block) {
|
||||
$face_data = $this->parse_font_face_block($block);
|
||||
|
||||
if (is_wp_error($face_data)) {
|
||||
continue; // Skip malformed blocks
|
||||
}
|
||||
|
||||
// Verify font family matches (security)
|
||||
if (strcasecmp($face_data['family'], $font_name) !== 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create unique key for weight+style combo
|
||||
$key = $face_data['weight'] . '-' . $face_data['style'];
|
||||
|
||||
// Prefer latin subset (usually comes after latin-ext)
|
||||
$is_latin = $this->is_latin_subset($face_data['unicode_range']);
|
||||
|
||||
// Only store if:
|
||||
// 1. We don't have this weight/style yet, OR
|
||||
// 2. This is latin and replaces non-latin
|
||||
if (!isset($font_faces[$key]) || $is_latin) {
|
||||
$font_faces[$key] = $face_data;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($font_faces)) {
|
||||
return new WP_Error('no_fonts', 'No valid font faces found in CSS');
|
||||
}
|
||||
|
||||
// Limit number of font faces to prevent excessive downloads
|
||||
$max_faces = defined('MLF_MAX_FONT_FACES') ? MLF_MAX_FONT_FACES : 20;
|
||||
$font_faces_array = array_values($font_faces);
|
||||
if (count($font_faces_array) > $max_faces) {
|
||||
$font_faces_array = array_slice($font_faces_array, 0, $max_faces);
|
||||
}
|
||||
|
||||
return $font_faces_array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a single @font-face block.
|
||||
*
|
||||
* @param string $block Content inside @font-face { }.
|
||||
* @return array|WP_Error Parsed data or error.
|
||||
*/
|
||||
private function parse_font_face_block($block) {
|
||||
$data = [];
|
||||
|
||||
// Extract font-family
|
||||
if (preg_match('/font-family:\s*[\'"]?([^;\'"]+)[\'"]?;/i', $block, $m)) {
|
||||
$data['family'] = trim($m[1]);
|
||||
} else {
|
||||
return new WP_Error('missing_family', 'Missing font-family');
|
||||
}
|
||||
|
||||
// Extract font-weight
|
||||
if (preg_match('/font-weight:\s*(\d+);/i', $block, $m)) {
|
||||
$data['weight'] = $m[1];
|
||||
} else {
|
||||
return new WP_Error('missing_weight', 'Missing font-weight');
|
||||
}
|
||||
|
||||
// Extract font-style
|
||||
if (preg_match('/font-style:\s*(\w+);/i', $block, $m)) {
|
||||
$data['style'] = $m[1];
|
||||
} else {
|
||||
$data['style'] = 'normal'; // Default
|
||||
}
|
||||
|
||||
// Extract src URL - MUST be fonts.gstatic.com
|
||||
if (preg_match('/src:\s*url\((https:\/\/fonts\.gstatic\.com\/[^)]+\.woff2)\)/i', $block, $m)) {
|
||||
$data['url'] = $m[1];
|
||||
} else {
|
||||
return new WP_Error('missing_src', 'Missing or invalid src URL');
|
||||
}
|
||||
|
||||
// Extract unicode-range (optional, for subset detection)
|
||||
if (preg_match('/unicode-range:\s*([^;]+);/i', $block, $m)) {
|
||||
$data['unicode_range'] = trim($m[1]);
|
||||
} else {
|
||||
$data['unicode_range'] = '';
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if unicode-range indicates latin subset.
|
||||
*
|
||||
* @param string $range Unicode range string.
|
||||
* @return bool True if appears to be latin subset.
|
||||
*/
|
||||
private function is_latin_subset($range) {
|
||||
if (empty($range)) {
|
||||
return true; // Assume latin if no range specified
|
||||
}
|
||||
|
||||
// Latin subset typically includes basic ASCII range
|
||||
// and does NOT include extended Latin (U+0100+) as primary
|
||||
if (preg_match('/U\+0000/', $range) && !preg_match('/^U\+0100/', $range)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download all font files.
|
||||
*
|
||||
* @param array $font_faces Array of font face data.
|
||||
* @param string $font_slug Font slug for filename.
|
||||
* @return array|WP_Error Array of downloaded file info or error.
|
||||
*/
|
||||
private function download_files($font_faces, $font_slug) {
|
||||
$downloaded = [];
|
||||
$errors = [];
|
||||
|
||||
foreach ($font_faces as $face) {
|
||||
$result = $this->download_single_file(
|
||||
$face['url'],
|
||||
$font_slug,
|
||||
$face['weight'],
|
||||
$face['style']
|
||||
);
|
||||
|
||||
if (is_wp_error($result)) {
|
||||
$errors[] = $result->get_error_message();
|
||||
continue;
|
||||
}
|
||||
|
||||
$downloaded[] = [
|
||||
'path' => $result,
|
||||
'weight' => $face['weight'],
|
||||
'style' => $face['style'],
|
||||
];
|
||||
}
|
||||
|
||||
// If no files downloaded, return error
|
||||
if (empty($downloaded)) {
|
||||
return new WP_Error(
|
||||
'download_failed',
|
||||
'Could not download any font files: ' . implode(', ', $errors)
|
||||
);
|
||||
}
|
||||
|
||||
return $downloaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a single WOFF2 file from Google Fonts.
|
||||
*
|
||||
* @param string $url Google Fonts static URL.
|
||||
* @param string $font_slug Font slug for filename.
|
||||
* @param string $weight Font weight.
|
||||
* @param string $style Font style.
|
||||
* @return string|WP_Error Local file path or error.
|
||||
*/
|
||||
private function download_single_file($url, $font_slug, $weight, $style) {
|
||||
// Validate URL is from Google
|
||||
if (!$this->is_valid_google_fonts_url($url)) {
|
||||
return new WP_Error('invalid_url', 'URL is not from Google Fonts');
|
||||
}
|
||||
|
||||
// Build local filename
|
||||
$filename = sprintf('%s_%s_%s.woff2', $font_slug, $style, $weight);
|
||||
$filename = sanitize_file_name($filename);
|
||||
|
||||
// Validate filename
|
||||
$filename = $this->sanitize_font_filename($filename);
|
||||
if ($filename === false) {
|
||||
return new WP_Error('invalid_filename', 'Invalid filename');
|
||||
}
|
||||
|
||||
// Get destination path
|
||||
$font_dir = wp_get_font_dir();
|
||||
$destination = trailingslashit($font_dir['path']) . $filename;
|
||||
|
||||
// Validate destination path before any file operations
|
||||
if (!$this->validate_font_path($destination)) {
|
||||
return new WP_Error('invalid_path', 'Invalid destination path');
|
||||
}
|
||||
|
||||
// Ensure directory exists
|
||||
if (!wp_mkdir_p($font_dir['path'])) {
|
||||
return new WP_Error('mkdir_failed', 'Could not create fonts directory');
|
||||
}
|
||||
|
||||
// Download file
|
||||
$response = wp_remote_get($url, [
|
||||
'timeout' => MLF_REQUEST_TIMEOUT,
|
||||
'sslverify' => true,
|
||||
'user-agent' => $this->user_agent,
|
||||
]);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
return new WP_Error('download_failed', 'Failed to download font file: ' . $response->get_error_message());
|
||||
}
|
||||
|
||||
$status = wp_remote_retrieve_response_code($response);
|
||||
if ($status !== 200) {
|
||||
return new WP_Error('http_error', 'Font download returned HTTP ' . $status);
|
||||
}
|
||||
|
||||
$content = wp_remote_retrieve_body($response);
|
||||
if (empty($content)) {
|
||||
return new WP_Error('empty_file', 'Downloaded font file is empty');
|
||||
}
|
||||
|
||||
// Check font file size to prevent memory issues
|
||||
$max_size = defined('MLF_MAX_FONT_FILE_SIZE') ? MLF_MAX_FONT_FILE_SIZE : 5 * 1024 * 1024;
|
||||
if (strlen($content) > $max_size) {
|
||||
return new WP_Error('file_too_large', 'Font file exceeds maximum size limit');
|
||||
}
|
||||
|
||||
// Verify it looks like a WOFF2 file (magic bytes: wOF2)
|
||||
if (substr($content, 0, 4) !== 'wOF2') {
|
||||
return new WP_Error('invalid_format', 'Downloaded file is not valid WOFF2');
|
||||
}
|
||||
|
||||
// Write file using WP Filesystem
|
||||
global $wp_filesystem;
|
||||
if (empty($wp_filesystem)) {
|
||||
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||||
WP_Filesystem();
|
||||
}
|
||||
|
||||
if (!$wp_filesystem->put_contents($destination, $content, FS_CHMOD_FILE)) {
|
||||
return new WP_Error('write_failed', 'Could not write font file');
|
||||
}
|
||||
|
||||
return $destination;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that a URL is a legitimate Google Fonts URL.
|
||||
*
|
||||
* @param string $url URL to validate.
|
||||
* @return bool True if valid Google Fonts URL.
|
||||
*/
|
||||
private function is_valid_google_fonts_url($url) {
|
||||
$parsed = wp_parse_url($url);
|
||||
|
||||
if (!$parsed || !isset($parsed['host'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only allow Google Fonts domains
|
||||
$allowed_hosts = [
|
||||
'fonts.googleapis.com',
|
||||
'fonts.gstatic.com',
|
||||
];
|
||||
|
||||
return in_array($parsed['host'], $allowed_hosts, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize and validate a font filename.
|
||||
*
|
||||
* @param string $filename The filename to validate.
|
||||
* @return string|false Sanitized filename or false if invalid.
|
||||
*/
|
||||
private function sanitize_font_filename($filename) {
|
||||
// WordPress sanitization first
|
||||
$filename = sanitize_file_name($filename);
|
||||
|
||||
// Must have .woff2 extension
|
||||
if (pathinfo($filename, PATHINFO_EXTENSION) !== 'woff2') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// No path components
|
||||
if ($filename !== basename($filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reasonable length
|
||||
if (strlen($filename) > 200) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that a path is within the WordPress fonts directory.
|
||||
*
|
||||
* @param string $path Full path to validate.
|
||||
* @return bool True if path is safe, false otherwise.
|
||||
*/
|
||||
private function validate_font_path($path) {
|
||||
$font_dir = wp_get_font_dir();
|
||||
$fonts_path = wp_normalize_path(trailingslashit($font_dir['path']));
|
||||
|
||||
// Resolve to real path (handles ../ etc)
|
||||
$real_path = realpath($path);
|
||||
|
||||
// If realpath fails, file doesn't exist yet - validate the directory
|
||||
if ($real_path === false) {
|
||||
$dir = dirname($path);
|
||||
$real_dir = realpath($dir);
|
||||
if ($real_dir === false) {
|
||||
// Directory doesn't exist yet, check parent
|
||||
$parent_dir = dirname($dir);
|
||||
$real_parent = realpath($parent_dir);
|
||||
if ($real_parent === false) {
|
||||
return false;
|
||||
}
|
||||
$real_path = wp_normalize_path($real_parent . '/' . basename($dir) . '/' . basename($path));
|
||||
} else {
|
||||
$real_path = wp_normalize_path($real_dir . '/' . basename($path));
|
||||
}
|
||||
} else {
|
||||
$real_path = wp_normalize_path($real_path);
|
||||
}
|
||||
|
||||
// Must be within fonts directory
|
||||
return strpos($real_path, $fonts_path) === 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,287 @@
|
|||
<?php
|
||||
/**
|
||||
* Font Registry for Maple Local Fonts.
|
||||
*
|
||||
* @package Maple_Local_Fonts
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class MLF_Font_Registry
|
||||
*
|
||||
* Handles registering fonts with WordPress Font Library API.
|
||||
*/
|
||||
class MLF_Font_Registry {
|
||||
|
||||
/**
|
||||
* Register a font family with WordPress Font Library.
|
||||
*
|
||||
* @param string $font_name Display name (e.g., "Open Sans").
|
||||
* @param string $font_slug Slug (e.g., "open-sans").
|
||||
* @param array $files Array of downloaded file data.
|
||||
* @return int|WP_Error Font family post ID or error.
|
||||
*/
|
||||
public function register_font($font_name, $font_slug, $files) {
|
||||
// Check if font already exists
|
||||
$existing = get_posts([
|
||||
'post_type' => 'wp_font_family',
|
||||
'name' => $font_slug,
|
||||
'posts_per_page' => 1,
|
||||
'post_status' => 'publish',
|
||||
]);
|
||||
|
||||
if (!empty($existing)) {
|
||||
return new WP_Error('font_exists', 'Font family already installed');
|
||||
}
|
||||
|
||||
// Get font directory
|
||||
$font_dir = wp_get_font_dir();
|
||||
|
||||
// Build font face array for WordPress
|
||||
$font_faces = [];
|
||||
foreach ($files as $file) {
|
||||
$filename = basename($file['path']);
|
||||
$font_faces[] = [
|
||||
'fontFamily' => $font_name,
|
||||
'fontWeight' => $file['weight'],
|
||||
'fontStyle' => $file['style'],
|
||||
'src' => 'file:./' . $filename,
|
||||
];
|
||||
}
|
||||
|
||||
// Build font family settings
|
||||
$font_family_settings = [
|
||||
'name' => $font_name,
|
||||
'slug' => $font_slug,
|
||||
'fontFamily' => sprintf('"%s", sans-serif', $font_name),
|
||||
'fontFace' => $font_faces,
|
||||
];
|
||||
|
||||
// Create font family post
|
||||
$family_id = wp_insert_post([
|
||||
'post_type' => 'wp_font_family',
|
||||
'post_title' => $font_name,
|
||||
'post_name' => $font_slug,
|
||||
'post_status' => 'publish',
|
||||
'post_content' => wp_json_encode($font_family_settings),
|
||||
]);
|
||||
|
||||
if (is_wp_error($family_id)) {
|
||||
return $family_id;
|
||||
}
|
||||
|
||||
// Mark as imported by our plugin (for identification)
|
||||
update_post_meta($family_id, '_mlf_imported', '1');
|
||||
update_post_meta($family_id, '_mlf_import_date', current_time('mysql'));
|
||||
|
||||
// Create font face posts (children)
|
||||
foreach ($files as $file) {
|
||||
$filename = basename($file['path']);
|
||||
|
||||
$face_settings = [
|
||||
'fontFamily' => $font_name,
|
||||
'fontWeight' => $file['weight'],
|
||||
'fontStyle' => $file['style'],
|
||||
'src' => 'file:./' . $filename,
|
||||
];
|
||||
|
||||
wp_insert_post([
|
||||
'post_type' => 'wp_font_face',
|
||||
'post_parent' => $family_id,
|
||||
'post_title' => sprintf('%s %s %s', $font_name, $file['weight'], $file['style']),
|
||||
'post_status' => 'publish',
|
||||
'post_content' => wp_json_encode($face_settings),
|
||||
]);
|
||||
}
|
||||
|
||||
// Clear font caches
|
||||
delete_transient('wp_font_library_fonts');
|
||||
delete_transient('mlf_imported_fonts_list');
|
||||
|
||||
return $family_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a font family and its files.
|
||||
*
|
||||
* @param int $family_id Font family post ID.
|
||||
* @return bool|WP_Error True on success, error on failure.
|
||||
*/
|
||||
public function delete_font($family_id) {
|
||||
$family = get_post($family_id);
|
||||
|
||||
if (!$family || $family->post_type !== 'wp_font_family') {
|
||||
return new WP_Error('not_found', 'Font family not found');
|
||||
}
|
||||
|
||||
// Verify it's one we imported
|
||||
if (get_post_meta($family_id, '_mlf_imported', true) !== '1') {
|
||||
return new WP_Error('not_ours', 'Cannot delete fonts not imported by this plugin');
|
||||
}
|
||||
|
||||
// Get font faces (children)
|
||||
$faces = get_children([
|
||||
'post_parent' => $family_id,
|
||||
'post_type' => 'wp_font_face',
|
||||
]);
|
||||
|
||||
$font_dir = wp_get_font_dir();
|
||||
|
||||
// Delete font face files and posts
|
||||
foreach ($faces as $face) {
|
||||
$settings = json_decode($face->post_content, true);
|
||||
|
||||
if (isset($settings['src'])) {
|
||||
// Convert file:. URL to path
|
||||
$src = $settings['src'];
|
||||
$src = str_replace('file:./', '', $src);
|
||||
$file_path = trailingslashit($font_dir['path']) . basename($src);
|
||||
|
||||
// Validate path and extension before deletion
|
||||
if ($this->validate_font_path($file_path)
|
||||
&& pathinfo($file_path, PATHINFO_EXTENSION) === 'woff2'
|
||||
&& file_exists($file_path)) {
|
||||
wp_delete_file($file_path);
|
||||
}
|
||||
}
|
||||
|
||||
wp_delete_post($face->ID, true);
|
||||
}
|
||||
|
||||
// Delete family post
|
||||
wp_delete_post($family_id, true);
|
||||
|
||||
// Clear caches
|
||||
delete_transient('wp_font_library_fonts');
|
||||
delete_transient('mlf_imported_fonts_list');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all fonts imported by this plugin.
|
||||
*
|
||||
* Uses optimized queries to avoid N+1 pattern.
|
||||
*
|
||||
* @return array Array of font data.
|
||||
*/
|
||||
public function get_imported_fonts() {
|
||||
// Check transient cache first
|
||||
$cached = get_transient('mlf_imported_fonts_list');
|
||||
if ($cached !== false) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$fonts = get_posts([
|
||||
'post_type' => 'wp_font_family',
|
||||
'posts_per_page' => 100,
|
||||
'post_status' => 'publish',
|
||||
'meta_key' => '_mlf_imported',
|
||||
'meta_value' => '1',
|
||||
]);
|
||||
|
||||
if (empty($fonts)) {
|
||||
set_transient('mlf_imported_fonts_list', [], 5 * MINUTE_IN_SECONDS);
|
||||
return [];
|
||||
}
|
||||
|
||||
// Collect all font IDs for batch query
|
||||
$font_ids = wp_list_pluck($fonts, 'ID');
|
||||
|
||||
// Single query to get ALL font faces for ALL fonts (fixes N+1)
|
||||
$all_faces = get_posts([
|
||||
'post_type' => 'wp_font_face',
|
||||
'posts_per_page' => 1000, // Max 100 fonts × 9 weights × 2 styles = 1800, but limit reasonably
|
||||
'post_status' => 'publish',
|
||||
'post_parent__in' => $font_ids,
|
||||
]);
|
||||
|
||||
// Group faces by parent font ID
|
||||
$faces_by_font = [];
|
||||
foreach ($all_faces as $face) {
|
||||
$parent_id = $face->post_parent;
|
||||
if (!isset($faces_by_font[$parent_id])) {
|
||||
$faces_by_font[$parent_id] = [];
|
||||
}
|
||||
$faces_by_font[$parent_id][] = $face;
|
||||
}
|
||||
|
||||
// Batch get all import dates in single query
|
||||
$import_dates = [];
|
||||
foreach ($font_ids as $font_id) {
|
||||
$import_dates[$font_id] = get_post_meta($font_id, '_mlf_import_date', true);
|
||||
}
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($fonts as $font) {
|
||||
$settings = json_decode($font->post_content, true);
|
||||
|
||||
// Get variants from pre-fetched data
|
||||
$faces = $faces_by_font[$font->ID] ?? [];
|
||||
|
||||
$variants = [];
|
||||
foreach ($faces as $face) {
|
||||
$face_settings = json_decode($face->post_content, true);
|
||||
$variants[] = [
|
||||
'weight' => $face_settings['fontWeight'] ?? '400',
|
||||
'style' => $face_settings['fontStyle'] ?? 'normal',
|
||||
];
|
||||
}
|
||||
|
||||
// Sort variants by weight then style
|
||||
usort($variants, function($a, $b) {
|
||||
$weight_cmp = intval($a['weight']) - intval($b['weight']);
|
||||
if ($weight_cmp !== 0) {
|
||||
return $weight_cmp;
|
||||
}
|
||||
return strcmp($a['style'], $b['style']);
|
||||
});
|
||||
|
||||
$result[] = [
|
||||
'id' => $font->ID,
|
||||
'name' => $settings['name'] ?? $font->post_title,
|
||||
'slug' => $settings['slug'] ?? $font->post_name,
|
||||
'variants' => $variants,
|
||||
'import_date' => $import_dates[$font->ID] ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
// Cache for 5 minutes
|
||||
set_transient('mlf_imported_fonts_list', $result, 5 * MINUTE_IN_SECONDS);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that a path is within the WordPress fonts directory.
|
||||
*
|
||||
* @param string $path Full path to validate.
|
||||
* @return bool True if path is safe, false otherwise.
|
||||
*/
|
||||
private function validate_font_path($path) {
|
||||
$font_dir = wp_get_font_dir();
|
||||
$fonts_path = wp_normalize_path(trailingslashit($font_dir['path']));
|
||||
|
||||
// Resolve to real path (handles ../ etc)
|
||||
$real_path = realpath($path);
|
||||
|
||||
// If realpath fails, file doesn't exist yet - validate the directory
|
||||
if ($real_path === false) {
|
||||
$dir = dirname($path);
|
||||
$real_dir = realpath($dir);
|
||||
if ($real_dir === false) {
|
||||
return false;
|
||||
}
|
||||
$real_path = wp_normalize_path($real_dir . '/' . basename($path));
|
||||
} else {
|
||||
$real_path = wp_normalize_path($real_path);
|
||||
}
|
||||
|
||||
// Must be within fonts directory
|
||||
return strpos($real_path, $fonts_path) === 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
<?php
|
||||
/**
|
||||
* Rate Limiter for Maple Local Fonts.
|
||||
*
|
||||
* @package Maple_Local_Fonts
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class MLF_Rate_Limiter
|
||||
*
|
||||
* Provides rate limiting functionality to prevent abuse of AJAX endpoints.
|
||||
*/
|
||||
class MLF_Rate_Limiter {
|
||||
|
||||
/**
|
||||
* Default rate limit (requests per window).
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $limit = 10;
|
||||
|
||||
/**
|
||||
* Default time window in seconds.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $window = 60;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param int $limit Maximum requests allowed per window.
|
||||
* @param int $window Time window in seconds.
|
||||
*/
|
||||
public function __construct($limit = 10, $window = 60) {
|
||||
$this->limit = absint($limit);
|
||||
$this->window = absint($window);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current user/IP is rate limited.
|
||||
*
|
||||
* @param string $action The action being rate limited.
|
||||
* @return bool True if rate limited (should block), false if allowed.
|
||||
*/
|
||||
public function is_limited($action) {
|
||||
$key = $this->get_rate_limit_key($action);
|
||||
$data = get_transient($key);
|
||||
|
||||
if ($data === false) {
|
||||
// First request, not limited
|
||||
return false;
|
||||
}
|
||||
|
||||
return $data['count'] >= $this->limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a request for rate limiting purposes.
|
||||
*
|
||||
* @param string $action The action being recorded.
|
||||
* @return void
|
||||
*/
|
||||
public function record_request($action) {
|
||||
$key = $this->get_rate_limit_key($action);
|
||||
$data = get_transient($key);
|
||||
|
||||
if ($data === false) {
|
||||
// First request in this window
|
||||
$data = [
|
||||
'count' => 1,
|
||||
'start' => time(),
|
||||
];
|
||||
} else {
|
||||
$data['count']++;
|
||||
}
|
||||
|
||||
// Set/update transient with remaining window time
|
||||
$elapsed = time() - $data['start'];
|
||||
$remaining = max(1, $this->window - $elapsed);
|
||||
|
||||
set_transient($key, $data, $remaining);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check rate limit and record request in one call.
|
||||
*
|
||||
* @param string $action The action being checked.
|
||||
* @return bool True if request is allowed, false if rate limited.
|
||||
*/
|
||||
public function check_and_record($action) {
|
||||
if ($this->is_limited($action)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->record_request($action);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of remaining requests in the current window.
|
||||
*
|
||||
* @param string $action The action to check.
|
||||
* @return int Number of remaining requests.
|
||||
*/
|
||||
public function get_remaining($action) {
|
||||
$key = $this->get_rate_limit_key($action);
|
||||
$data = get_transient($key);
|
||||
|
||||
if ($data === false) {
|
||||
return $this->limit;
|
||||
}
|
||||
|
||||
return max(0, $this->limit - $data['count']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the rate limit key for the current user/IP.
|
||||
*
|
||||
* @param string $action The action being rate limited.
|
||||
* @return string Transient key.
|
||||
*/
|
||||
private function get_rate_limit_key($action) {
|
||||
// Use user ID if logged in, otherwise IP
|
||||
$user_id = get_current_user_id();
|
||||
|
||||
if ($user_id > 0) {
|
||||
$identifier = 'user_' . $user_id;
|
||||
} else {
|
||||
// Sanitize and hash IP for privacy
|
||||
$ip = $this->get_client_ip();
|
||||
$identifier = 'ip_' . md5($ip);
|
||||
}
|
||||
|
||||
return 'mlf_rate_' . sanitize_key($action) . '_' . $identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the client IP address.
|
||||
*
|
||||
* @return string Client IP address.
|
||||
*/
|
||||
private function get_client_ip() {
|
||||
$ip = '';
|
||||
|
||||
// Check for various headers (in order of reliability)
|
||||
$headers = [
|
||||
'HTTP_CLIENT_IP',
|
||||
'HTTP_X_FORWARDED_FOR',
|
||||
'HTTP_X_FORWARDED',
|
||||
'HTTP_X_CLUSTER_CLIENT_IP',
|
||||
'HTTP_FORWARDED_FOR',
|
||||
'HTTP_FORWARDED',
|
||||
'REMOTE_ADDR',
|
||||
];
|
||||
|
||||
foreach ($headers as $header) {
|
||||
if (!empty($_SERVER[$header])) {
|
||||
// X-Forwarded-For can contain multiple IPs, get the first one
|
||||
$ips = explode(',', sanitize_text_field(wp_unslash($_SERVER[$header])));
|
||||
$ip = trim($ips[0]);
|
||||
|
||||
// Validate IP
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to localhost if no valid IP found
|
||||
return $ip ?: '127.0.0.1';
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear rate limit for a specific action and user/IP.
|
||||
*
|
||||
* @param string $action The action to clear.
|
||||
* @return void
|
||||
*/
|
||||
public function clear($action) {
|
||||
$key = $this->get_rate_limit_key($action);
|
||||
delete_transient($key);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,525 @@
|
|||
<?php
|
||||
/**
|
||||
* REST API Controller for Maple Local Fonts.
|
||||
*
|
||||
* @package Maple_Local_Fonts
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class MLF_Rest_Controller
|
||||
*
|
||||
* Provides REST API endpoints for font management.
|
||||
*/
|
||||
class MLF_Rest_Controller extends WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* Namespace for the API.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'mlf/v1';
|
||||
|
||||
/**
|
||||
* Resource name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'fonts';
|
||||
|
||||
/**
|
||||
* Rate limiter instance.
|
||||
*
|
||||
* @var MLF_Rate_Limiter
|
||||
*/
|
||||
private $rate_limiter;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->rate_limiter = new MLF_Rate_Limiter(10, 60);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the routes for the controller.
|
||||
*/
|
||||
public function register_routes() {
|
||||
// GET /wp-json/mlf/v1/fonts - List all fonts
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
[
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [$this, 'get_items'],
|
||||
'permission_callback' => [$this, 'get_items_permissions_check'],
|
||||
],
|
||||
[
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => [$this, 'create_item'],
|
||||
'permission_callback' => [$this, 'create_item_permissions_check'],
|
||||
'args' => $this->get_create_item_args(),
|
||||
],
|
||||
'schema' => [$this, 'get_public_item_schema'],
|
||||
]
|
||||
);
|
||||
|
||||
// DELETE /wp-json/mlf/v1/fonts/{id} - Delete a font
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/(?P<id>[\d]+)',
|
||||
[
|
||||
[
|
||||
'methods' => WP_REST_Server::DELETABLE,
|
||||
'callback' => [$this, 'delete_item'],
|
||||
'permission_callback' => [$this, 'delete_item_permissions_check'],
|
||||
'args' => [
|
||||
'id' => [
|
||||
'description' => __('Unique identifier for the font.', 'maple-local-fonts'),
|
||||
'type' => 'integer',
|
||||
'required' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
// GET /wp-json/mlf/v1/fonts/{id} - Get a single font
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/(?P<id>[\d]+)',
|
||||
[
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [$this, 'get_item'],
|
||||
'permission_callback' => [$this, 'get_items_permissions_check'],
|
||||
'args' => [
|
||||
'id' => [
|
||||
'description' => __('Unique identifier for the font.', 'maple-local-fonts'),
|
||||
'type' => 'integer',
|
||||
'required' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given request has access to get items.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
* @return bool|WP_Error True if the request has read access, WP_Error object otherwise.
|
||||
*/
|
||||
public function get_items_permissions_check($request) {
|
||||
$capability = function_exists('mlf_get_capability') ? mlf_get_capability() : 'edit_theme_options';
|
||||
if (!current_user_can($capability)) {
|
||||
return new WP_Error(
|
||||
'rest_forbidden',
|
||||
__('Sorry, you are not allowed to view fonts.', 'maple-local-fonts'),
|
||||
['status' => rest_authorization_required_code()]
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given request has access to create items.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
* @return bool|WP_Error True if the request has create access, WP_Error object otherwise.
|
||||
*/
|
||||
public function create_item_permissions_check($request) {
|
||||
$capability = function_exists('mlf_get_capability') ? mlf_get_capability() : 'edit_theme_options';
|
||||
if (!current_user_can($capability)) {
|
||||
return new WP_Error(
|
||||
'rest_forbidden',
|
||||
__('Sorry, you are not allowed to create fonts.', 'maple-local-fonts'),
|
||||
['status' => rest_authorization_required_code()]
|
||||
);
|
||||
}
|
||||
|
||||
// Rate limit check
|
||||
if (!$this->rate_limiter->check_and_record('rest_create')) {
|
||||
return new WP_Error(
|
||||
'rest_rate_limited',
|
||||
__('Too many requests. Please wait a moment and try again.', 'maple-local-fonts'),
|
||||
['status' => 429]
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given request has access to delete a specific item.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
* @return bool|WP_Error True if the request has delete access, WP_Error object otherwise.
|
||||
*/
|
||||
public function delete_item_permissions_check($request) {
|
||||
$capability = function_exists('mlf_get_capability') ? mlf_get_capability() : 'edit_theme_options';
|
||||
if (!current_user_can($capability)) {
|
||||
return new WP_Error(
|
||||
'rest_forbidden',
|
||||
__('Sorry, you are not allowed to delete fonts.', 'maple-local-fonts'),
|
||||
['status' => rest_authorization_required_code()]
|
||||
);
|
||||
}
|
||||
|
||||
// Rate limit check
|
||||
if (!$this->rate_limiter->check_and_record('rest_delete')) {
|
||||
return new WP_Error(
|
||||
'rest_rate_limited',
|
||||
__('Too many requests. Please wait a moment and try again.', 'maple-local-fonts'),
|
||||
['status' => 429]
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a collection of fonts.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function get_items($request) {
|
||||
$registry = new MLF_Font_Registry();
|
||||
$fonts = $registry->get_imported_fonts();
|
||||
|
||||
$data = [];
|
||||
foreach ($fonts as $font) {
|
||||
$data[] = $this->prepare_item_for_response($font, $request);
|
||||
}
|
||||
|
||||
return rest_ensure_response($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single font.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function get_item($request) {
|
||||
$font_id = absint($request->get_param('id'));
|
||||
$font = get_post($font_id);
|
||||
|
||||
if (!$font || $font->post_type !== 'wp_font_family') {
|
||||
return new WP_Error(
|
||||
'rest_font_not_found',
|
||||
__('Font not found.', 'maple-local-fonts'),
|
||||
['status' => 404]
|
||||
);
|
||||
}
|
||||
|
||||
// Verify it's one we imported
|
||||
if (get_post_meta($font_id, '_mlf_imported', true) !== '1') {
|
||||
return new WP_Error(
|
||||
'rest_font_not_found',
|
||||
__('Font not found.', 'maple-local-fonts'),
|
||||
['status' => 404]
|
||||
);
|
||||
}
|
||||
|
||||
$registry = new MLF_Font_Registry();
|
||||
$fonts = $registry->get_imported_fonts();
|
||||
$font_data = null;
|
||||
|
||||
foreach ($fonts as $f) {
|
||||
if ($f['id'] === $font_id) {
|
||||
$font_data = $f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$font_data) {
|
||||
return new WP_Error(
|
||||
'rest_font_not_found',
|
||||
__('Font not found.', 'maple-local-fonts'),
|
||||
['status' => 404]
|
||||
);
|
||||
}
|
||||
|
||||
return rest_ensure_response($this->prepare_item_for_response($font_data, $request));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a font.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function create_item($request) {
|
||||
$font_name = sanitize_text_field($request->get_param('font_name'));
|
||||
$weights = array_map('absint', (array) $request->get_param('weights'));
|
||||
$styles = array_map('sanitize_text_field', (array) $request->get_param('styles'));
|
||||
|
||||
// Validate font name
|
||||
if (!preg_match('/^[a-zA-Z0-9\s\-]+$/', $font_name)) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_param',
|
||||
__('Invalid font name: only letters, numbers, spaces, and hyphens allowed.', 'maple-local-fonts'),
|
||||
['status' => 400]
|
||||
);
|
||||
}
|
||||
|
||||
if (strlen($font_name) > 100) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_param',
|
||||
__('Font name is too long.', 'maple-local-fonts'),
|
||||
['status' => 400]
|
||||
);
|
||||
}
|
||||
|
||||
// Validate weights
|
||||
$allowed_weights = [100, 200, 300, 400, 500, 600, 700, 800, 900];
|
||||
$weights = array_intersect($weights, $allowed_weights);
|
||||
|
||||
if (empty($weights)) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_param',
|
||||
__('At least one valid weight is required.', 'maple-local-fonts'),
|
||||
['status' => 400]
|
||||
);
|
||||
}
|
||||
|
||||
if (count($weights) > MLF_MAX_WEIGHTS_PER_FONT) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_param',
|
||||
__('Too many weights selected.', 'maple-local-fonts'),
|
||||
['status' => 400]
|
||||
);
|
||||
}
|
||||
|
||||
// Validate styles
|
||||
$allowed_styles = ['normal', 'italic'];
|
||||
$styles = array_filter($styles, function($style) use ($allowed_styles) {
|
||||
return in_array($style, $allowed_styles, true);
|
||||
});
|
||||
|
||||
if (empty($styles)) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_param',
|
||||
__('At least one valid style is required.', 'maple-local-fonts'),
|
||||
['status' => 400]
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$downloader = new MLF_Font_Downloader();
|
||||
$download_result = $downloader->download($font_name, $weights, $styles);
|
||||
|
||||
if (is_wp_error($download_result)) {
|
||||
return new WP_Error(
|
||||
'rest_download_failed',
|
||||
$download_result->get_error_message(),
|
||||
['status' => 400]
|
||||
);
|
||||
}
|
||||
|
||||
// Register font with WordPress
|
||||
$registry = new MLF_Font_Registry();
|
||||
$result = $registry->register_font(
|
||||
$download_result['font_name'],
|
||||
$download_result['font_slug'],
|
||||
$download_result['files']
|
||||
);
|
||||
|
||||
if (is_wp_error($result)) {
|
||||
return new WP_Error(
|
||||
'rest_register_failed',
|
||||
$result->get_error_message(),
|
||||
['status' => 400]
|
||||
);
|
||||
}
|
||||
|
||||
// Return the created font
|
||||
$fonts = $registry->get_imported_fonts();
|
||||
$created_font = null;
|
||||
foreach ($fonts as $font) {
|
||||
if ($font['id'] === $result) {
|
||||
$created_font = $font;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$response = rest_ensure_response($this->prepare_item_for_response($created_font, $request));
|
||||
$response->set_status(201);
|
||||
$response->header('Location', rest_url(sprintf('%s/%s/%d', $this->namespace, $this->rest_base, $result)));
|
||||
|
||||
return $response;
|
||||
} catch (Exception $e) {
|
||||
error_log('MLF REST Create Error: ' . sanitize_text_field($e->getMessage()));
|
||||
return new WP_Error(
|
||||
'rest_internal_error',
|
||||
__('An unexpected error occurred.', 'maple-local-fonts'),
|
||||
['status' => 500]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a font.
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function delete_item($request) {
|
||||
$font_id = absint($request->get_param('id'));
|
||||
$font = get_post($font_id);
|
||||
|
||||
if (!$font || $font->post_type !== 'wp_font_family') {
|
||||
return new WP_Error(
|
||||
'rest_font_not_found',
|
||||
__('Font not found.', 'maple-local-fonts'),
|
||||
['status' => 404]
|
||||
);
|
||||
}
|
||||
|
||||
// Verify it's one we imported
|
||||
if (get_post_meta($font_id, '_mlf_imported', true) !== '1') {
|
||||
return new WP_Error(
|
||||
'rest_cannot_delete',
|
||||
__('Cannot delete fonts not imported by this plugin.', 'maple-local-fonts'),
|
||||
['status' => 403]
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$registry = new MLF_Font_Registry();
|
||||
$result = $registry->delete_font($font_id);
|
||||
|
||||
if (is_wp_error($result)) {
|
||||
return new WP_Error(
|
||||
'rest_delete_failed',
|
||||
$result->get_error_message(),
|
||||
['status' => 400]
|
||||
);
|
||||
}
|
||||
|
||||
return rest_ensure_response([
|
||||
'deleted' => true,
|
||||
'message' => __('Font deleted successfully.', 'maple-local-fonts'),
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
error_log('MLF REST Delete Error: ' . sanitize_text_field($e->getMessage()));
|
||||
return new WP_Error(
|
||||
'rest_internal_error',
|
||||
__('An unexpected error occurred.', 'maple-local-fonts'),
|
||||
['status' => 500]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a font for the REST response.
|
||||
*
|
||||
* @param array $font Font data.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return array Prepared font data.
|
||||
*/
|
||||
public function prepare_item_for_response($font, $request) {
|
||||
return [
|
||||
'id' => $font['id'],
|
||||
'name' => $font['name'],
|
||||
'slug' => $font['slug'],
|
||||
'variants' => $font['variants'],
|
||||
'_links' => [
|
||||
'self' => [
|
||||
['href' => rest_url(sprintf('%s/%s/%d', $this->namespace, $this->rest_base, $font['id']))],
|
||||
],
|
||||
'collection' => [
|
||||
['href' => rest_url(sprintf('%s/%s', $this->namespace, $this->rest_base))],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the argument schema for creating items.
|
||||
*
|
||||
* @return array Arguments schema.
|
||||
*/
|
||||
protected function get_create_item_args() {
|
||||
return [
|
||||
'font_name' => [
|
||||
'description' => __('The font family name from Google Fonts.', 'maple-local-fonts'),
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
],
|
||||
'weights' => [
|
||||
'description' => __('Array of font weights to download.', 'maple-local-fonts'),
|
||||
'type' => 'array',
|
||||
'required' => true,
|
||||
'items' => [
|
||||
'type' => 'integer',
|
||||
'enum' => [100, 200, 300, 400, 500, 600, 700, 800, 900],
|
||||
],
|
||||
],
|
||||
'styles' => [
|
||||
'description' => __('Array of font styles to download.', 'maple-local-fonts'),
|
||||
'type' => 'array',
|
||||
'required' => true,
|
||||
'items' => [
|
||||
'type' => 'string',
|
||||
'enum' => ['normal', 'italic'],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the font schema.
|
||||
*
|
||||
* @return array Schema definition.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
return [
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'font',
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'id' => [
|
||||
'description' => __('Unique identifier for the font.', 'maple-local-fonts'),
|
||||
'type' => 'integer',
|
||||
'context' => ['view'],
|
||||
'readonly' => true,
|
||||
],
|
||||
'name' => [
|
||||
'description' => __('The font family name.', 'maple-local-fonts'),
|
||||
'type' => 'string',
|
||||
'context' => ['view'],
|
||||
],
|
||||
'slug' => [
|
||||
'description' => __('The font slug.', 'maple-local-fonts'),
|
||||
'type' => 'string',
|
||||
'context' => ['view'],
|
||||
],
|
||||
'variants' => [
|
||||
'description' => __('Array of font variants.', 'maple-local-fonts'),
|
||||
'type' => 'array',
|
||||
'context' => ['view'],
|
||||
'items' => [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'weight' => [
|
||||
'type' => 'string',
|
||||
],
|
||||
'style' => [
|
||||
'type' => 'string',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
5
native/wordpress/maple-fonts-wp/includes/index.php
Normal file
5
native/wordpress/maple-fonts-wp/includes/index.php
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?php
|
||||
// Silence is golden.
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
5
native/wordpress/maple-fonts-wp/index.php
Normal file
5
native/wordpress/maple-fonts-wp/index.php
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?php
|
||||
// Silence is golden.
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
5
native/wordpress/maple-fonts-wp/languages/index.php
Normal file
5
native/wordpress/maple-fonts-wp/languages/index.php
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?php
|
||||
// Silence is golden.
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
# Translation of Maple Local Fonts in Arabic
|
||||
# Copyright (C) 2024 Maple Open Technologies
|
||||
# This file is distributed under the GPL-2.0-or-later.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Maple Local Fonts 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: https://mapleopentech.org/\n"
|
||||
"Last-Translator: Maple Open Technologies\n"
|
||||
"Language-Team: Arabic\n"
|
||||
"Language: ar\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"POT-Creation-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"PO-Revision-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"X-Generator: Manual\n"
|
||||
"X-Domain: maple-local-fonts\n"
|
||||
"Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5);\n"
|
||||
|
||||
#. Plugin Name of the plugin
|
||||
msgid "Maple Local Fonts"
|
||||
msgstr "Maple Local Fonts"
|
||||
|
||||
#. Description of the plugin
|
||||
msgid "Import Google Fonts to local storage and register them with WordPress Font Library for GDPR-compliant, privacy-friendly typography."
|
||||
msgstr "استورد خطوط Google Fonts إلى التخزين المحلي وسجلها في مكتبة خطوط WordPress للحصول على طباعة متوافقة مع GDPR وصديقة للخصوصية."
|
||||
|
||||
#. Author of the plugin
|
||||
msgid "Maple Open Technologies"
|
||||
msgstr "Maple Open Technologies"
|
||||
|
||||
msgid "Maple Local Fonts requires WordPress 6.5 or higher for Font Library support."
|
||||
msgstr "يتطلب Maple Local Fonts إصدار WordPress 6.5 أو أعلى لدعم مكتبة الخطوط."
|
||||
|
||||
msgid "Plugin Activation Error"
|
||||
msgstr "خطأ في تفعيل الإضافة"
|
||||
|
||||
msgid "Maple Local Fonts has been deactivated. It requires WordPress 6.5 or higher."
|
||||
msgstr "تم إلغاء تفعيل Maple Local Fonts. يتطلب WordPress 6.5 أو أعلى."
|
||||
|
||||
msgid "Local Fonts"
|
||||
msgstr "الخطوط المحلية"
|
||||
|
||||
msgid "Downloading..."
|
||||
msgstr "جارٍ التنزيل..."
|
||||
|
||||
msgid "Deleting..."
|
||||
msgstr "جارٍ الحذف..."
|
||||
|
||||
msgid "Are you sure you want to delete this font?"
|
||||
msgstr "هل أنت متأكد أنك تريد حذف هذا الخط؟"
|
||||
|
||||
msgid "An error occurred. Please try again."
|
||||
msgstr "حدث خطأ. يرجى المحاولة مرة أخرى."
|
||||
|
||||
msgid "Please select at least one weight."
|
||||
msgstr "يرجى اختيار وزن واحد على الأقل."
|
||||
|
||||
msgid "Please select at least one style."
|
||||
msgstr "يرجى اختيار نمط واحد على الأقل."
|
||||
|
||||
msgid "Please enter a font name."
|
||||
msgstr "يرجى إدخال اسم الخط."
|
||||
|
||||
msgid "Security check failed."
|
||||
msgstr "فشل التحقق الأمني."
|
||||
|
||||
msgid "Unauthorized."
|
||||
msgstr "غير مصرح."
|
||||
|
||||
msgid "Font name is required."
|
||||
msgstr "اسم الخط مطلوب."
|
||||
|
||||
msgid "Invalid font name: only letters, numbers, spaces, and hyphens allowed."
|
||||
msgstr "اسم خط غير صالح: يُسمح فقط بالأحرف والأرقام والمسافات والشرطات."
|
||||
|
||||
msgid "Font name is too long."
|
||||
msgstr "اسم الخط طويل جداً."
|
||||
|
||||
msgid "At least one weight is required."
|
||||
msgstr "مطلوب وزن واحد على الأقل."
|
||||
|
||||
msgid "Too many weights selected."
|
||||
msgstr "تم اختيار أوزان كثيرة جداً."
|
||||
|
||||
msgid "At least one style is required."
|
||||
msgstr "مطلوب نمط واحد على الأقل."
|
||||
|
||||
msgid "Successfully installed %s."
|
||||
msgstr "تم تثبيت %s بنجاح."
|
||||
|
||||
msgid "An unexpected error occurred."
|
||||
msgstr "حدث خطأ غير متوقع."
|
||||
|
||||
msgid "Invalid font ID."
|
||||
msgstr "معرف خط غير صالح."
|
||||
|
||||
msgid "Font not found."
|
||||
msgstr "الخط غير موجود."
|
||||
|
||||
msgid "Cannot delete fonts not imported by this plugin."
|
||||
msgstr "لا يمكن حذف الخطوط التي لم يتم استيرادها بواسطة هذه الإضافة."
|
||||
|
||||
msgid "Font deleted successfully."
|
||||
msgstr "تم حذف الخط بنجاح."
|
||||
|
||||
msgid "Font not found on Google Fonts. Please check the spelling and try again."
|
||||
msgstr "الخط غير موجود في Google Fonts. يرجى التحقق من الإملاء والمحاولة مرة أخرى."
|
||||
|
||||
msgid "This font is already installed."
|
||||
msgstr "هذا الخط مثبت بالفعل."
|
||||
|
||||
msgid "Could not connect to Google Fonts. Please check your internet connection and try again."
|
||||
msgstr "تعذر الاتصال بـ Google Fonts. يرجى التحقق من اتصالك بالإنترنت والمحاولة مرة أخرى."
|
||||
|
||||
msgid "Google Fonts returned an error. Please try again later."
|
||||
msgstr "أرجع Google Fonts خطأ. يرجى المحاولة لاحقاً."
|
||||
|
||||
msgid "Could not process the font data. The font may not be available."
|
||||
msgstr "تعذرت معالجة بيانات الخط. قد لا يكون الخط متاحاً."
|
||||
|
||||
msgid "Could not download the font files. Please try again."
|
||||
msgstr "تعذر تنزيل ملفات الخط. يرجى المحاولة مرة أخرى."
|
||||
|
||||
msgid "Could not save font files. Please check that wp-content/fonts is writable."
|
||||
msgstr "تعذر حفظ ملفات الخط. يرجى التحقق من أن wp-content/fonts قابل للكتابة."
|
||||
|
||||
msgid "Could not create fonts directory. Please check file permissions."
|
||||
msgstr "تعذر إنشاء دليل الخطوط. يرجى التحقق من أذونات الملفات."
|
||||
|
||||
msgid "Invalid file path."
|
||||
msgstr "مسار ملف غير صالح."
|
||||
|
||||
msgid "Invalid font URL."
|
||||
msgstr "رابط خط غير صالح."
|
||||
|
||||
msgid "Invalid font name."
|
||||
msgstr "اسم خط غير صالح."
|
||||
|
||||
msgid "No valid weights specified."
|
||||
msgstr "لم يتم تحديد أوزان صالحة."
|
||||
|
||||
msgid "No valid styles specified."
|
||||
msgstr "لم يتم تحديد أنماط صالحة."
|
||||
|
||||
msgid "An unexpected error occurred. Please try again."
|
||||
msgstr "حدث خطأ غير متوقع. يرجى المحاولة مرة أخرى."
|
||||
|
||||
msgid "Thin"
|
||||
msgstr "رفيع"
|
||||
|
||||
msgid "Extra Light"
|
||||
msgstr "خفيف جداً"
|
||||
|
||||
msgid "Light"
|
||||
msgstr "خفيف"
|
||||
|
||||
msgid "Regular"
|
||||
msgstr "عادي"
|
||||
|
||||
msgid "Medium"
|
||||
msgstr "متوسط"
|
||||
|
||||
msgid "Semi Bold"
|
||||
msgstr "شبه عريض"
|
||||
|
||||
msgid "Bold"
|
||||
msgstr "عريض"
|
||||
|
||||
msgid "Extra Bold"
|
||||
msgstr "عريض جداً"
|
||||
|
||||
msgid "Black"
|
||||
msgstr "أسود"
|
||||
|
||||
msgid "You do not have sufficient permissions to access this page."
|
||||
msgstr "ليس لديك الصلاحيات الكافية للوصول إلى هذه الصفحة."
|
||||
|
||||
msgid "Import from Google Fonts"
|
||||
msgstr "استيراد من Google Fonts"
|
||||
|
||||
msgid "Font Name"
|
||||
msgstr "اسم الخط"
|
||||
|
||||
msgid "e.g., Open Sans"
|
||||
msgstr "مثال: Open Sans"
|
||||
|
||||
msgid "Enter the exact font name as it appears on Google Fonts."
|
||||
msgstr "أدخل اسم الخط الدقيق كما يظهر في Google Fonts."
|
||||
|
||||
msgid "Weights"
|
||||
msgstr "الأوزان"
|
||||
|
||||
msgid "Styles"
|
||||
msgstr "الأنماط"
|
||||
|
||||
msgid "Normal"
|
||||
msgstr "عادي"
|
||||
|
||||
msgid "Italic"
|
||||
msgstr "مائل"
|
||||
|
||||
msgid "Files to download:"
|
||||
msgstr "الملفات للتنزيل:"
|
||||
|
||||
msgid "Download & Install"
|
||||
msgstr "تنزيل وتثبيت"
|
||||
|
||||
msgid "Installed Fonts"
|
||||
msgstr "الخطوط المثبتة"
|
||||
|
||||
msgid "No fonts installed yet."
|
||||
msgstr "لم يتم تثبيت أي خطوط بعد."
|
||||
|
||||
msgid "Delete"
|
||||
msgstr "حذف"
|
||||
|
||||
msgid "Use %s to apply fonts to your site."
|
||||
msgstr "استخدم %s لتطبيق الخطوط على موقعك."
|
||||
|
||||
msgid "Appearance → Editor → Styles → Typography"
|
||||
msgstr "المظهر ← المحرر ← الأنماط ← الطباعة"
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
# Translation of Maple Local Fonts in German
|
||||
# Copyright (C) 2024 Maple Open Technologies
|
||||
# This file is distributed under the GPL-2.0-or-later.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Maple Local Fonts 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: https://mapleopentech.org/\n"
|
||||
"Last-Translator: Maple Open Technologies\n"
|
||||
"Language-Team: German\n"
|
||||
"Language: de_DE\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"POT-Creation-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"PO-Revision-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"X-Generator: Manual\n"
|
||||
"X-Domain: maple-local-fonts\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. Plugin Name of the plugin
|
||||
msgid "Maple Local Fonts"
|
||||
msgstr "Maple Local Fonts"
|
||||
|
||||
#. Description of the plugin
|
||||
msgid "Import Google Fonts to local storage and register them with WordPress Font Library for GDPR-compliant, privacy-friendly typography."
|
||||
msgstr "Importieren Sie Google Fonts in den lokalen Speicher und registrieren Sie sie in der WordPress-Schriftbibliothek für DSGVO-konforme, datenschutzfreundliche Typografie."
|
||||
|
||||
#. Author of the plugin
|
||||
msgid "Maple Open Technologies"
|
||||
msgstr "Maple Open Technologies"
|
||||
|
||||
msgid "Maple Local Fonts requires WordPress 6.5 or higher for Font Library support."
|
||||
msgstr "Maple Local Fonts erfordert WordPress 6.5 oder höher für die Unterstützung der Schriftbibliothek."
|
||||
|
||||
msgid "Plugin Activation Error"
|
||||
msgstr "Plugin-Aktivierungsfehler"
|
||||
|
||||
msgid "Maple Local Fonts has been deactivated. It requires WordPress 6.5 or higher."
|
||||
msgstr "Maple Local Fonts wurde deaktiviert. Es erfordert WordPress 6.5 oder höher."
|
||||
|
||||
msgid "Local Fonts"
|
||||
msgstr "Lokale Schriften"
|
||||
|
||||
msgid "Downloading..."
|
||||
msgstr "Wird heruntergeladen..."
|
||||
|
||||
msgid "Deleting..."
|
||||
msgstr "Wird gelöscht..."
|
||||
|
||||
msgid "Are you sure you want to delete this font?"
|
||||
msgstr "Sind Sie sicher, dass Sie diese Schrift löschen möchten?"
|
||||
|
||||
msgid "An error occurred. Please try again."
|
||||
msgstr "Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut."
|
||||
|
||||
msgid "Please select at least one weight."
|
||||
msgstr "Bitte wählen Sie mindestens eine Schriftstärke aus."
|
||||
|
||||
msgid "Please select at least one style."
|
||||
msgstr "Bitte wählen Sie mindestens einen Stil aus."
|
||||
|
||||
msgid "Please enter a font name."
|
||||
msgstr "Bitte geben Sie einen Schriftnamen ein."
|
||||
|
||||
msgid "Security check failed."
|
||||
msgstr "Sicherheitsprüfung fehlgeschlagen."
|
||||
|
||||
msgid "Unauthorized."
|
||||
msgstr "Nicht autorisiert."
|
||||
|
||||
msgid "Font name is required."
|
||||
msgstr "Schriftname ist erforderlich."
|
||||
|
||||
msgid "Invalid font name: only letters, numbers, spaces, and hyphens allowed."
|
||||
msgstr "Ungültiger Schriftname: Nur Buchstaben, Zahlen, Leerzeichen und Bindestriche erlaubt."
|
||||
|
||||
msgid "Font name is too long."
|
||||
msgstr "Schriftname ist zu lang."
|
||||
|
||||
msgid "At least one weight is required."
|
||||
msgstr "Mindestens eine Schriftstärke ist erforderlich."
|
||||
|
||||
msgid "Too many weights selected."
|
||||
msgstr "Zu viele Schriftstärken ausgewählt."
|
||||
|
||||
msgid "At least one style is required."
|
||||
msgstr "Mindestens ein Stil ist erforderlich."
|
||||
|
||||
msgid "Successfully installed %s."
|
||||
msgstr "%s wurde erfolgreich installiert."
|
||||
|
||||
msgid "An unexpected error occurred."
|
||||
msgstr "Ein unerwarteter Fehler ist aufgetreten."
|
||||
|
||||
msgid "Invalid font ID."
|
||||
msgstr "Ungültige Schrift-ID."
|
||||
|
||||
msgid "Font not found."
|
||||
msgstr "Schrift nicht gefunden."
|
||||
|
||||
msgid "Cannot delete fonts not imported by this plugin."
|
||||
msgstr "Schriften, die nicht von diesem Plugin importiert wurden, können nicht gelöscht werden."
|
||||
|
||||
msgid "Font deleted successfully."
|
||||
msgstr "Schrift erfolgreich gelöscht."
|
||||
|
||||
msgid "Font not found on Google Fonts. Please check the spelling and try again."
|
||||
msgstr "Schrift bei Google Fonts nicht gefunden. Bitte überprüfen Sie die Schreibweise und versuchen Sie es erneut."
|
||||
|
||||
msgid "This font is already installed."
|
||||
msgstr "Diese Schrift ist bereits installiert."
|
||||
|
||||
msgid "Could not connect to Google Fonts. Please check your internet connection and try again."
|
||||
msgstr "Verbindung zu Google Fonts konnte nicht hergestellt werden. Bitte überprüfen Sie Ihre Internetverbindung und versuchen Sie es erneut."
|
||||
|
||||
msgid "Google Fonts returned an error. Please try again later."
|
||||
msgstr "Google Fonts hat einen Fehler zurückgegeben. Bitte versuchen Sie es später erneut."
|
||||
|
||||
msgid "Could not process the font data. The font may not be available."
|
||||
msgstr "Schriftdaten konnten nicht verarbeitet werden. Die Schrift ist möglicherweise nicht verfügbar."
|
||||
|
||||
msgid "Could not download the font files. Please try again."
|
||||
msgstr "Schriftdateien konnten nicht heruntergeladen werden. Bitte versuchen Sie es erneut."
|
||||
|
||||
msgid "Could not save font files. Please check that wp-content/fonts is writable."
|
||||
msgstr "Schriftdateien konnten nicht gespeichert werden. Bitte überprüfen Sie, ob wp-content/fonts beschreibbar ist."
|
||||
|
||||
msgid "Could not create fonts directory. Please check file permissions."
|
||||
msgstr "Schriftverzeichnis konnte nicht erstellt werden. Bitte überprüfen Sie die Dateiberechtigungen."
|
||||
|
||||
msgid "Invalid file path."
|
||||
msgstr "Ungültiger Dateipfad."
|
||||
|
||||
msgid "Invalid font URL."
|
||||
msgstr "Ungültige Schrift-URL."
|
||||
|
||||
msgid "Invalid font name."
|
||||
msgstr "Ungültiger Schriftname."
|
||||
|
||||
msgid "No valid weights specified."
|
||||
msgstr "Keine gültigen Schriftstärken angegeben."
|
||||
|
||||
msgid "No valid styles specified."
|
||||
msgstr "Keine gültigen Stile angegeben."
|
||||
|
||||
msgid "An unexpected error occurred. Please try again."
|
||||
msgstr "Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es erneut."
|
||||
|
||||
msgid "Thin"
|
||||
msgstr "Dünn"
|
||||
|
||||
msgid "Extra Light"
|
||||
msgstr "Extraleicht"
|
||||
|
||||
msgid "Light"
|
||||
msgstr "Leicht"
|
||||
|
||||
msgid "Regular"
|
||||
msgstr "Normal"
|
||||
|
||||
msgid "Medium"
|
||||
msgstr "Mittel"
|
||||
|
||||
msgid "Semi Bold"
|
||||
msgstr "Halbfett"
|
||||
|
||||
msgid "Bold"
|
||||
msgstr "Fett"
|
||||
|
||||
msgid "Extra Bold"
|
||||
msgstr "Extrafett"
|
||||
|
||||
msgid "Black"
|
||||
msgstr "Schwarz"
|
||||
|
||||
msgid "You do not have sufficient permissions to access this page."
|
||||
msgstr "Sie haben nicht die erforderlichen Berechtigungen, um auf diese Seite zuzugreifen."
|
||||
|
||||
msgid "Import from Google Fonts"
|
||||
msgstr "Von Google Fonts importieren"
|
||||
|
||||
msgid "Font Name"
|
||||
msgstr "Schriftname"
|
||||
|
||||
msgid "e.g., Open Sans"
|
||||
msgstr "z.B. Open Sans"
|
||||
|
||||
msgid "Enter the exact font name as it appears on Google Fonts."
|
||||
msgstr "Geben Sie den genauen Schriftnamen ein, wie er bei Google Fonts erscheint."
|
||||
|
||||
msgid "Weights"
|
||||
msgstr "Schriftstärken"
|
||||
|
||||
msgid "Styles"
|
||||
msgstr "Stile"
|
||||
|
||||
msgid "Normal"
|
||||
msgstr "Normal"
|
||||
|
||||
msgid "Italic"
|
||||
msgstr "Kursiv"
|
||||
|
||||
msgid "Files to download:"
|
||||
msgstr "Herunterzuladende Dateien:"
|
||||
|
||||
msgid "Download & Install"
|
||||
msgstr "Herunterladen & Installieren"
|
||||
|
||||
msgid "Installed Fonts"
|
||||
msgstr "Installierte Schriften"
|
||||
|
||||
msgid "No fonts installed yet."
|
||||
msgstr "Noch keine Schriften installiert."
|
||||
|
||||
msgid "Delete"
|
||||
msgstr "Löschen"
|
||||
|
||||
msgid "Use %s to apply fonts to your site."
|
||||
msgstr "Verwenden Sie %s, um Schriften auf Ihre Website anzuwenden."
|
||||
|
||||
msgid "Appearance → Editor → Styles → Typography"
|
||||
msgstr "Design → Editor → Stile → Typografie"
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
# Translation of Maple Local Fonts in English (Canada)
|
||||
# Copyright (C) 2024 Maple Open Technologies
|
||||
# This file is distributed under the GPL-2.0-or-later.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Maple Local Fonts 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: https://mapleopentech.org/\n"
|
||||
"Last-Translator: Maple Open Technologies\n"
|
||||
"Language-Team: English (Canada)\n"
|
||||
"Language: en_CA\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"POT-Creation-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"PO-Revision-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"X-Generator: Manual\n"
|
||||
"X-Domain: maple-local-fonts\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. Plugin Name of the plugin
|
||||
msgid "Maple Local Fonts"
|
||||
msgstr "Maple Local Fonts"
|
||||
|
||||
#. Description of the plugin
|
||||
msgid "Import Google Fonts to local storage and register them with WordPress Font Library for GDPR-compliant, privacy-friendly typography."
|
||||
msgstr "Import Google Fonts to local storage and register them with WordPress Font Library for GDPR-compliant, privacy-friendly typography."
|
||||
|
||||
#. Author of the plugin
|
||||
msgid "Maple Open Technologies"
|
||||
msgstr "Maple Open Technologies"
|
||||
|
||||
msgid "Maple Local Fonts requires WordPress 6.5 or higher for Font Library support."
|
||||
msgstr "Maple Local Fonts requires WordPress 6.5 or higher for Font Library support."
|
||||
|
||||
msgid "Plugin Activation Error"
|
||||
msgstr "Plugin Activation Error"
|
||||
|
||||
msgid "Maple Local Fonts has been deactivated. It requires WordPress 6.5 or higher."
|
||||
msgstr "Maple Local Fonts has been deactivated. It requires WordPress 6.5 or higher."
|
||||
|
||||
msgid "Local Fonts"
|
||||
msgstr "Local Fonts"
|
||||
|
||||
msgid "Downloading..."
|
||||
msgstr "Downloading..."
|
||||
|
||||
msgid "Deleting..."
|
||||
msgstr "Deleting..."
|
||||
|
||||
msgid "Are you sure you want to delete this font?"
|
||||
msgstr "Are you sure you want to delete this font?"
|
||||
|
||||
msgid "An error occurred. Please try again."
|
||||
msgstr "An error occurred. Please try again."
|
||||
|
||||
msgid "Please select at least one weight."
|
||||
msgstr "Please select at least one weight."
|
||||
|
||||
msgid "Please select at least one style."
|
||||
msgstr "Please select at least one style."
|
||||
|
||||
msgid "Please enter a font name."
|
||||
msgstr "Please enter a font name."
|
||||
|
||||
msgid "Security check failed."
|
||||
msgstr "Security check failed."
|
||||
|
||||
msgid "Unauthorized."
|
||||
msgstr "Unauthorized."
|
||||
|
||||
msgid "Font name is required."
|
||||
msgstr "Font name is required."
|
||||
|
||||
msgid "Invalid font name: only letters, numbers, spaces, and hyphens allowed."
|
||||
msgstr "Invalid font name: only letters, numbers, spaces, and hyphens allowed."
|
||||
|
||||
msgid "Font name is too long."
|
||||
msgstr "Font name is too long."
|
||||
|
||||
msgid "At least one weight is required."
|
||||
msgstr "At least one weight is required."
|
||||
|
||||
msgid "Too many weights selected."
|
||||
msgstr "Too many weights selected."
|
||||
|
||||
msgid "At least one style is required."
|
||||
msgstr "At least one style is required."
|
||||
|
||||
msgid "Successfully installed %s."
|
||||
msgstr "Successfully installed %s."
|
||||
|
||||
msgid "An unexpected error occurred."
|
||||
msgstr "An unexpected error occurred."
|
||||
|
||||
msgid "Invalid font ID."
|
||||
msgstr "Invalid font ID."
|
||||
|
||||
msgid "Font not found."
|
||||
msgstr "Font not found."
|
||||
|
||||
msgid "Cannot delete fonts not imported by this plugin."
|
||||
msgstr "Cannot delete fonts not imported by this plugin."
|
||||
|
||||
msgid "Font deleted successfully."
|
||||
msgstr "Font deleted successfully."
|
||||
|
||||
msgid "Font not found on Google Fonts. Please check the spelling and try again."
|
||||
msgstr "Font not found on Google Fonts. Please check the spelling and try again."
|
||||
|
||||
msgid "This font is already installed."
|
||||
msgstr "This font is already installed."
|
||||
|
||||
msgid "Could not connect to Google Fonts. Please check your internet connection and try again."
|
||||
msgstr "Could not connect to Google Fonts. Please check your internet connection and try again."
|
||||
|
||||
msgid "Google Fonts returned an error. Please try again later."
|
||||
msgstr "Google Fonts returned an error. Please try again later."
|
||||
|
||||
msgid "Could not process the font data. The font may not be available."
|
||||
msgstr "Could not process the font data. The font may not be available."
|
||||
|
||||
msgid "Could not download the font files. Please try again."
|
||||
msgstr "Could not download the font files. Please try again."
|
||||
|
||||
msgid "Could not save font files. Please check that wp-content/fonts is writable."
|
||||
msgstr "Could not save font files. Please check that wp-content/fonts is writable."
|
||||
|
||||
msgid "Could not create fonts directory. Please check file permissions."
|
||||
msgstr "Could not create fonts directory. Please check file permissions."
|
||||
|
||||
msgid "Invalid file path."
|
||||
msgstr "Invalid file path."
|
||||
|
||||
msgid "Invalid font URL."
|
||||
msgstr "Invalid font URL."
|
||||
|
||||
msgid "Invalid font name."
|
||||
msgstr "Invalid font name."
|
||||
|
||||
msgid "No valid weights specified."
|
||||
msgstr "No valid weights specified."
|
||||
|
||||
msgid "No valid styles specified."
|
||||
msgstr "No valid styles specified."
|
||||
|
||||
msgid "An unexpected error occurred. Please try again."
|
||||
msgstr "An unexpected error occurred. Please try again."
|
||||
|
||||
msgid "Thin"
|
||||
msgstr "Thin"
|
||||
|
||||
msgid "Extra Light"
|
||||
msgstr "Extra Light"
|
||||
|
||||
msgid "Light"
|
||||
msgstr "Light"
|
||||
|
||||
msgid "Regular"
|
||||
msgstr "Regular"
|
||||
|
||||
msgid "Medium"
|
||||
msgstr "Medium"
|
||||
|
||||
msgid "Semi Bold"
|
||||
msgstr "Semi Bold"
|
||||
|
||||
msgid "Bold"
|
||||
msgstr "Bold"
|
||||
|
||||
msgid "Extra Bold"
|
||||
msgstr "Extra Bold"
|
||||
|
||||
msgid "Black"
|
||||
msgstr "Black"
|
||||
|
||||
msgid "You do not have sufficient permissions to access this page."
|
||||
msgstr "You do not have sufficient permissions to access this page."
|
||||
|
||||
msgid "Import from Google Fonts"
|
||||
msgstr "Import from Google Fonts"
|
||||
|
||||
msgid "Font Name"
|
||||
msgstr "Font Name"
|
||||
|
||||
msgid "e.g., Open Sans"
|
||||
msgstr "e.g., Open Sans"
|
||||
|
||||
msgid "Enter the exact font name as it appears on Google Fonts."
|
||||
msgstr "Enter the exact font name as it appears on Google Fonts."
|
||||
|
||||
msgid "Weights"
|
||||
msgstr "Weights"
|
||||
|
||||
msgid "Styles"
|
||||
msgstr "Styles"
|
||||
|
||||
msgid "Normal"
|
||||
msgstr "Normal"
|
||||
|
||||
msgid "Italic"
|
||||
msgstr "Italic"
|
||||
|
||||
msgid "Files to download:"
|
||||
msgstr "Files to download:"
|
||||
|
||||
msgid "Download & Install"
|
||||
msgstr "Download & Install"
|
||||
|
||||
msgid "Installed Fonts"
|
||||
msgstr "Installed Fonts"
|
||||
|
||||
msgid "No fonts installed yet."
|
||||
msgstr "No fonts installed yet."
|
||||
|
||||
msgid "Delete"
|
||||
msgstr "Delete"
|
||||
|
||||
msgid "Use %s to apply fonts to your site."
|
||||
msgstr "Use %s to apply fonts to your site."
|
||||
|
||||
msgid "Appearance → Editor → Styles → Typography"
|
||||
msgstr "Appearance → Editor → Styles → Typography"
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
# Translation of Maple Local Fonts in English (United Kingdom)
|
||||
# Copyright (C) 2024 Maple Open Technologies
|
||||
# This file is distributed under the GPL-2.0-or-later.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Maple Local Fonts 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: https://mapleopentech.org/\n"
|
||||
"Last-Translator: Maple Open Technologies\n"
|
||||
"Language-Team: English (United Kingdom)\n"
|
||||
"Language: en_GB\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"POT-Creation-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"PO-Revision-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"X-Generator: Manual\n"
|
||||
"X-Domain: maple-local-fonts\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. Plugin Name of the plugin
|
||||
msgid "Maple Local Fonts"
|
||||
msgstr "Maple Local Fonts"
|
||||
|
||||
#. Description of the plugin
|
||||
msgid "Import Google Fonts to local storage and register them with WordPress Font Library for GDPR-compliant, privacy-friendly typography."
|
||||
msgstr "Import Google Fonts to local storage and register them with WordPress Font Library for GDPR-compliant, privacy-friendly typography."
|
||||
|
||||
#. Author of the plugin
|
||||
msgid "Maple Open Technologies"
|
||||
msgstr "Maple Open Technologies"
|
||||
|
||||
msgid "Maple Local Fonts requires WordPress 6.5 or higher for Font Library support."
|
||||
msgstr "Maple Local Fonts requires WordPress 6.5 or higher for Font Library support."
|
||||
|
||||
msgid "Plugin Activation Error"
|
||||
msgstr "Plugin Activation Error"
|
||||
|
||||
msgid "Maple Local Fonts has been deactivated. It requires WordPress 6.5 or higher."
|
||||
msgstr "Maple Local Fonts has been deactivated. It requires WordPress 6.5 or higher."
|
||||
|
||||
msgid "Local Fonts"
|
||||
msgstr "Local Fonts"
|
||||
|
||||
msgid "Downloading..."
|
||||
msgstr "Downloading..."
|
||||
|
||||
msgid "Deleting..."
|
||||
msgstr "Deleting..."
|
||||
|
||||
msgid "Are you sure you want to delete this font?"
|
||||
msgstr "Are you sure you want to delete this font?"
|
||||
|
||||
msgid "An error occurred. Please try again."
|
||||
msgstr "An error occurred. Please try again."
|
||||
|
||||
msgid "Please select at least one weight."
|
||||
msgstr "Please select at least one weight."
|
||||
|
||||
msgid "Please select at least one style."
|
||||
msgstr "Please select at least one style."
|
||||
|
||||
msgid "Please enter a font name."
|
||||
msgstr "Please enter a font name."
|
||||
|
||||
msgid "Security check failed."
|
||||
msgstr "Security check failed."
|
||||
|
||||
msgid "Unauthorised."
|
||||
msgstr "Unauthorised."
|
||||
|
||||
msgid "Font name is required."
|
||||
msgstr "Font name is required."
|
||||
|
||||
msgid "Invalid font name: only letters, numbers, spaces, and hyphens allowed."
|
||||
msgstr "Invalid font name: only letters, numbers, spaces, and hyphens allowed."
|
||||
|
||||
msgid "Font name is too long."
|
||||
msgstr "Font name is too long."
|
||||
|
||||
msgid "At least one weight is required."
|
||||
msgstr "At least one weight is required."
|
||||
|
||||
msgid "Too many weights selected."
|
||||
msgstr "Too many weights selected."
|
||||
|
||||
msgid "At least one style is required."
|
||||
msgstr "At least one style is required."
|
||||
|
||||
msgid "Successfully installed %s."
|
||||
msgstr "Successfully installed %s."
|
||||
|
||||
msgid "An unexpected error occurred."
|
||||
msgstr "An unexpected error occurred."
|
||||
|
||||
msgid "Invalid font ID."
|
||||
msgstr "Invalid font ID."
|
||||
|
||||
msgid "Font not found."
|
||||
msgstr "Font not found."
|
||||
|
||||
msgid "Cannot delete fonts not imported by this plugin."
|
||||
msgstr "Cannot delete fonts not imported by this plugin."
|
||||
|
||||
msgid "Font deleted successfully."
|
||||
msgstr "Font deleted successfully."
|
||||
|
||||
msgid "Font not found on Google Fonts. Please check the spelling and try again."
|
||||
msgstr "Font not found on Google Fonts. Please check the spelling and try again."
|
||||
|
||||
msgid "This font is already installed."
|
||||
msgstr "This font is already installed."
|
||||
|
||||
msgid "Could not connect to Google Fonts. Please check your internet connection and try again."
|
||||
msgstr "Could not connect to Google Fonts. Please check your internet connection and try again."
|
||||
|
||||
msgid "Google Fonts returned an error. Please try again later."
|
||||
msgstr "Google Fonts returned an error. Please try again later."
|
||||
|
||||
msgid "Could not process the font data. The font may not be available."
|
||||
msgstr "Could not process the font data. The font may not be available."
|
||||
|
||||
msgid "Could not download the font files. Please try again."
|
||||
msgstr "Could not download the font files. Please try again."
|
||||
|
||||
msgid "Could not save font files. Please check that wp-content/fonts is writable."
|
||||
msgstr "Could not save font files. Please check that wp-content/fonts is writable."
|
||||
|
||||
msgid "Could not create fonts directory. Please check file permissions."
|
||||
msgstr "Could not create fonts directory. Please check file permissions."
|
||||
|
||||
msgid "Invalid file path."
|
||||
msgstr "Invalid file path."
|
||||
|
||||
msgid "Invalid font URL."
|
||||
msgstr "Invalid font URL."
|
||||
|
||||
msgid "Invalid font name."
|
||||
msgstr "Invalid font name."
|
||||
|
||||
msgid "No valid weights specified."
|
||||
msgstr "No valid weights specified."
|
||||
|
||||
msgid "No valid styles specified."
|
||||
msgstr "No valid styles specified."
|
||||
|
||||
msgid "An unexpected error occurred. Please try again."
|
||||
msgstr "An unexpected error occurred. Please try again."
|
||||
|
||||
msgid "Thin"
|
||||
msgstr "Thin"
|
||||
|
||||
msgid "Extra Light"
|
||||
msgstr "Extra Light"
|
||||
|
||||
msgid "Light"
|
||||
msgstr "Light"
|
||||
|
||||
msgid "Regular"
|
||||
msgstr "Regular"
|
||||
|
||||
msgid "Medium"
|
||||
msgstr "Medium"
|
||||
|
||||
msgid "Semi Bold"
|
||||
msgstr "Semi Bold"
|
||||
|
||||
msgid "Bold"
|
||||
msgstr "Bold"
|
||||
|
||||
msgid "Extra Bold"
|
||||
msgstr "Extra Bold"
|
||||
|
||||
msgid "Black"
|
||||
msgstr "Black"
|
||||
|
||||
msgid "You do not have sufficient permissions to access this page."
|
||||
msgstr "You do not have sufficient permissions to access this page."
|
||||
|
||||
msgid "Import from Google Fonts"
|
||||
msgstr "Import from Google Fonts"
|
||||
|
||||
msgid "Font Name"
|
||||
msgstr "Font Name"
|
||||
|
||||
msgid "e.g., Open Sans"
|
||||
msgstr "e.g., Open Sans"
|
||||
|
||||
msgid "Enter the exact font name as it appears on Google Fonts."
|
||||
msgstr "Enter the exact font name as it appears on Google Fonts."
|
||||
|
||||
msgid "Weights"
|
||||
msgstr "Weights"
|
||||
|
||||
msgid "Styles"
|
||||
msgstr "Styles"
|
||||
|
||||
msgid "Normal"
|
||||
msgstr "Normal"
|
||||
|
||||
msgid "Italic"
|
||||
msgstr "Italic"
|
||||
|
||||
msgid "Files to download:"
|
||||
msgstr "Files to download:"
|
||||
|
||||
msgid "Download & Install"
|
||||
msgstr "Download & Install"
|
||||
|
||||
msgid "Installed Fonts"
|
||||
msgstr "Installed Fonts"
|
||||
|
||||
msgid "No fonts installed yet."
|
||||
msgstr "No fonts installed yet."
|
||||
|
||||
msgid "Delete"
|
||||
msgstr "Delete"
|
||||
|
||||
msgid "Use %s to apply fonts to your site."
|
||||
msgstr "Use %s to apply fonts to your site."
|
||||
|
||||
msgid "Appearance → Editor → Styles → Typography"
|
||||
msgstr "Appearance → Editor → Styles → Typography"
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
# Translation of Maple Local Fonts in English (United States)
|
||||
# Copyright (C) 2024 Maple Open Technologies
|
||||
# This file is distributed under the GPL-2.0-or-later.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Maple Local Fonts 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: https://mapleopentech.org/\n"
|
||||
"Last-Translator: Maple Open Technologies\n"
|
||||
"Language-Team: English (United States)\n"
|
||||
"Language: en_US\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"POT-Creation-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"PO-Revision-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"X-Generator: Manual\n"
|
||||
"X-Domain: maple-local-fonts\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. Plugin Name of the plugin
|
||||
msgid "Maple Local Fonts"
|
||||
msgstr "Maple Local Fonts"
|
||||
|
||||
#. Description of the plugin
|
||||
msgid "Import Google Fonts to local storage and register them with WordPress Font Library for GDPR-compliant, privacy-friendly typography."
|
||||
msgstr "Import Google Fonts to local storage and register them with WordPress Font Library for GDPR-compliant, privacy-friendly typography."
|
||||
|
||||
#. Author of the plugin
|
||||
msgid "Maple Open Technologies"
|
||||
msgstr "Maple Open Technologies"
|
||||
|
||||
msgid "Maple Local Fonts requires WordPress 6.5 or higher for Font Library support."
|
||||
msgstr "Maple Local Fonts requires WordPress 6.5 or higher for Font Library support."
|
||||
|
||||
msgid "Plugin Activation Error"
|
||||
msgstr "Plugin Activation Error"
|
||||
|
||||
msgid "Maple Local Fonts has been deactivated. It requires WordPress 6.5 or higher."
|
||||
msgstr "Maple Local Fonts has been deactivated. It requires WordPress 6.5 or higher."
|
||||
|
||||
msgid "Local Fonts"
|
||||
msgstr "Local Fonts"
|
||||
|
||||
msgid "Downloading..."
|
||||
msgstr "Downloading..."
|
||||
|
||||
msgid "Deleting..."
|
||||
msgstr "Deleting..."
|
||||
|
||||
msgid "Are you sure you want to delete this font?"
|
||||
msgstr "Are you sure you want to delete this font?"
|
||||
|
||||
msgid "An error occurred. Please try again."
|
||||
msgstr "An error occurred. Please try again."
|
||||
|
||||
msgid "Please select at least one weight."
|
||||
msgstr "Please select at least one weight."
|
||||
|
||||
msgid "Please select at least one style."
|
||||
msgstr "Please select at least one style."
|
||||
|
||||
msgid "Please enter a font name."
|
||||
msgstr "Please enter a font name."
|
||||
|
||||
msgid "Security check failed."
|
||||
msgstr "Security check failed."
|
||||
|
||||
msgid "Unauthorized."
|
||||
msgstr "Unauthorized."
|
||||
|
||||
msgid "Font name is required."
|
||||
msgstr "Font name is required."
|
||||
|
||||
msgid "Invalid font name: only letters, numbers, spaces, and hyphens allowed."
|
||||
msgstr "Invalid font name: only letters, numbers, spaces, and hyphens allowed."
|
||||
|
||||
msgid "Font name is too long."
|
||||
msgstr "Font name is too long."
|
||||
|
||||
msgid "At least one weight is required."
|
||||
msgstr "At least one weight is required."
|
||||
|
||||
msgid "Too many weights selected."
|
||||
msgstr "Too many weights selected."
|
||||
|
||||
msgid "At least one style is required."
|
||||
msgstr "At least one style is required."
|
||||
|
||||
msgid "Successfully installed %s."
|
||||
msgstr "Successfully installed %s."
|
||||
|
||||
msgid "An unexpected error occurred."
|
||||
msgstr "An unexpected error occurred."
|
||||
|
||||
msgid "Invalid font ID."
|
||||
msgstr "Invalid font ID."
|
||||
|
||||
msgid "Font not found."
|
||||
msgstr "Font not found."
|
||||
|
||||
msgid "Cannot delete fonts not imported by this plugin."
|
||||
msgstr "Cannot delete fonts not imported by this plugin."
|
||||
|
||||
msgid "Font deleted successfully."
|
||||
msgstr "Font deleted successfully."
|
||||
|
||||
msgid "Font not found on Google Fonts. Please check the spelling and try again."
|
||||
msgstr "Font not found on Google Fonts. Please check the spelling and try again."
|
||||
|
||||
msgid "This font is already installed."
|
||||
msgstr "This font is already installed."
|
||||
|
||||
msgid "Could not connect to Google Fonts. Please check your internet connection and try again."
|
||||
msgstr "Could not connect to Google Fonts. Please check your internet connection and try again."
|
||||
|
||||
msgid "Google Fonts returned an error. Please try again later."
|
||||
msgstr "Google Fonts returned an error. Please try again later."
|
||||
|
||||
msgid "Could not process the font data. The font may not be available."
|
||||
msgstr "Could not process the font data. The font may not be available."
|
||||
|
||||
msgid "Could not download the font files. Please try again."
|
||||
msgstr "Could not download the font files. Please try again."
|
||||
|
||||
msgid "Could not save font files. Please check that wp-content/fonts is writable."
|
||||
msgstr "Could not save font files. Please check that wp-content/fonts is writable."
|
||||
|
||||
msgid "Could not create fonts directory. Please check file permissions."
|
||||
msgstr "Could not create fonts directory. Please check file permissions."
|
||||
|
||||
msgid "Invalid file path."
|
||||
msgstr "Invalid file path."
|
||||
|
||||
msgid "Invalid font URL."
|
||||
msgstr "Invalid font URL."
|
||||
|
||||
msgid "Invalid font name."
|
||||
msgstr "Invalid font name."
|
||||
|
||||
msgid "No valid weights specified."
|
||||
msgstr "No valid weights specified."
|
||||
|
||||
msgid "No valid styles specified."
|
||||
msgstr "No valid styles specified."
|
||||
|
||||
msgid "An unexpected error occurred. Please try again."
|
||||
msgstr "An unexpected error occurred. Please try again."
|
||||
|
||||
msgid "Thin"
|
||||
msgstr "Thin"
|
||||
|
||||
msgid "Extra Light"
|
||||
msgstr "Extra Light"
|
||||
|
||||
msgid "Light"
|
||||
msgstr "Light"
|
||||
|
||||
msgid "Regular"
|
||||
msgstr "Regular"
|
||||
|
||||
msgid "Medium"
|
||||
msgstr "Medium"
|
||||
|
||||
msgid "Semi Bold"
|
||||
msgstr "Semi Bold"
|
||||
|
||||
msgid "Bold"
|
||||
msgstr "Bold"
|
||||
|
||||
msgid "Extra Bold"
|
||||
msgstr "Extra Bold"
|
||||
|
||||
msgid "Black"
|
||||
msgstr "Black"
|
||||
|
||||
msgid "You do not have sufficient permissions to access this page."
|
||||
msgstr "You do not have sufficient permissions to access this page."
|
||||
|
||||
msgid "Import from Google Fonts"
|
||||
msgstr "Import from Google Fonts"
|
||||
|
||||
msgid "Font Name"
|
||||
msgstr "Font Name"
|
||||
|
||||
msgid "e.g., Open Sans"
|
||||
msgstr "e.g., Open Sans"
|
||||
|
||||
msgid "Enter the exact font name as it appears on Google Fonts."
|
||||
msgstr "Enter the exact font name as it appears on Google Fonts."
|
||||
|
||||
msgid "Weights"
|
||||
msgstr "Weights"
|
||||
|
||||
msgid "Styles"
|
||||
msgstr "Styles"
|
||||
|
||||
msgid "Normal"
|
||||
msgstr "Normal"
|
||||
|
||||
msgid "Italic"
|
||||
msgstr "Italic"
|
||||
|
||||
msgid "Files to download:"
|
||||
msgstr "Files to download:"
|
||||
|
||||
msgid "Download & Install"
|
||||
msgstr "Download & Install"
|
||||
|
||||
msgid "Installed Fonts"
|
||||
msgstr "Installed Fonts"
|
||||
|
||||
msgid "No fonts installed yet."
|
||||
msgstr "No fonts installed yet."
|
||||
|
||||
msgid "Delete"
|
||||
msgstr "Delete"
|
||||
|
||||
msgid "Use %s to apply fonts to your site."
|
||||
msgstr "Use %s to apply fonts to your site."
|
||||
|
||||
msgid "Appearance → Editor → Styles → Typography"
|
||||
msgstr "Appearance → Editor → Styles → Typography"
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
# Translation of Maple Local Fonts in Spanish (Latin America)
|
||||
# Copyright (C) 2024 Maple Open Technologies
|
||||
# This file is distributed under the GPL-2.0-or-later.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Maple Local Fonts 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: https://mapleopentech.org/\n"
|
||||
"Last-Translator: Maple Open Technologies\n"
|
||||
"Language-Team: Spanish (Latin America)\n"
|
||||
"Language: es_419\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"POT-Creation-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"PO-Revision-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"X-Generator: Manual\n"
|
||||
"X-Domain: maple-local-fonts\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. Plugin Name of the plugin
|
||||
msgid "Maple Local Fonts"
|
||||
msgstr "Maple Local Fonts"
|
||||
|
||||
#. Description of the plugin
|
||||
msgid "Import Google Fonts to local storage and register them with WordPress Font Library for GDPR-compliant, privacy-friendly typography."
|
||||
msgstr "Importa fuentes de Google Fonts al almacenamiento local y regístralas con la biblioteca de fuentes de WordPress para una tipografía compatible con GDPR y respetuosa con la privacidad."
|
||||
|
||||
#. Author of the plugin
|
||||
msgid "Maple Open Technologies"
|
||||
msgstr "Maple Open Technologies"
|
||||
|
||||
msgid "Maple Local Fonts requires WordPress 6.5 or higher for Font Library support."
|
||||
msgstr "Maple Local Fonts requiere WordPress 6.5 o superior para el soporte de la biblioteca de fuentes."
|
||||
|
||||
msgid "Plugin Activation Error"
|
||||
msgstr "Error de activación del plugin"
|
||||
|
||||
msgid "Maple Local Fonts has been deactivated. It requires WordPress 6.5 or higher."
|
||||
msgstr "Maple Local Fonts ha sido desactivado. Requiere WordPress 6.5 o superior."
|
||||
|
||||
msgid "Local Fonts"
|
||||
msgstr "Fuentes locales"
|
||||
|
||||
msgid "Downloading..."
|
||||
msgstr "Descargando..."
|
||||
|
||||
msgid "Deleting..."
|
||||
msgstr "Eliminando..."
|
||||
|
||||
msgid "Are you sure you want to delete this font?"
|
||||
msgstr "¿Estás seguro de que quieres eliminar esta fuente?"
|
||||
|
||||
msgid "An error occurred. Please try again."
|
||||
msgstr "Ocurrió un error. Por favor, intenta de nuevo."
|
||||
|
||||
msgid "Please select at least one weight."
|
||||
msgstr "Por favor, selecciona al menos un peso."
|
||||
|
||||
msgid "Please select at least one style."
|
||||
msgstr "Por favor, selecciona al menos un estilo."
|
||||
|
||||
msgid "Please enter a font name."
|
||||
msgstr "Por favor, ingresa un nombre de fuente."
|
||||
|
||||
msgid "Security check failed."
|
||||
msgstr "Falló la verificación de seguridad."
|
||||
|
||||
msgid "Unauthorized."
|
||||
msgstr "No autorizado."
|
||||
|
||||
msgid "Font name is required."
|
||||
msgstr "El nombre de la fuente es requerido."
|
||||
|
||||
msgid "Invalid font name: only letters, numbers, spaces, and hyphens allowed."
|
||||
msgstr "Nombre de fuente inválido: solo se permiten letras, números, espacios y guiones."
|
||||
|
||||
msgid "Font name is too long."
|
||||
msgstr "El nombre de la fuente es demasiado largo."
|
||||
|
||||
msgid "At least one weight is required."
|
||||
msgstr "Se requiere al menos un peso."
|
||||
|
||||
msgid "Too many weights selected."
|
||||
msgstr "Demasiados pesos seleccionados."
|
||||
|
||||
msgid "At least one style is required."
|
||||
msgstr "Se requiere al menos un estilo."
|
||||
|
||||
msgid "Successfully installed %s."
|
||||
msgstr "%s se instaló correctamente."
|
||||
|
||||
msgid "An unexpected error occurred."
|
||||
msgstr "Ocurrió un error inesperado."
|
||||
|
||||
msgid "Invalid font ID."
|
||||
msgstr "ID de fuente inválido."
|
||||
|
||||
msgid "Font not found."
|
||||
msgstr "Fuente no encontrada."
|
||||
|
||||
msgid "Cannot delete fonts not imported by this plugin."
|
||||
msgstr "No se pueden eliminar fuentes que no fueron importadas por este plugin."
|
||||
|
||||
msgid "Font deleted successfully."
|
||||
msgstr "Fuente eliminada correctamente."
|
||||
|
||||
msgid "Font not found on Google Fonts. Please check the spelling and try again."
|
||||
msgstr "Fuente no encontrada en Google Fonts. Por favor, verifica la ortografía e intenta de nuevo."
|
||||
|
||||
msgid "This font is already installed."
|
||||
msgstr "Esta fuente ya está instalada."
|
||||
|
||||
msgid "Could not connect to Google Fonts. Please check your internet connection and try again."
|
||||
msgstr "No se pudo conectar a Google Fonts. Por favor, verifica tu conexión a internet e intenta de nuevo."
|
||||
|
||||
msgid "Google Fonts returned an error. Please try again later."
|
||||
msgstr "Google Fonts devolvió un error. Por favor, intenta más tarde."
|
||||
|
||||
msgid "Could not process the font data. The font may not be available."
|
||||
msgstr "No se pudieron procesar los datos de la fuente. La fuente puede no estar disponible."
|
||||
|
||||
msgid "Could not download the font files. Please try again."
|
||||
msgstr "No se pudieron descargar los archivos de la fuente. Por favor, intenta de nuevo."
|
||||
|
||||
msgid "Could not save font files. Please check that wp-content/fonts is writable."
|
||||
msgstr "No se pudieron guardar los archivos de la fuente. Por favor, verifica que wp-content/fonts tenga permisos de escritura."
|
||||
|
||||
msgid "Could not create fonts directory. Please check file permissions."
|
||||
msgstr "No se pudo crear el directorio de fuentes. Por favor, verifica los permisos de archivos."
|
||||
|
||||
msgid "Invalid file path."
|
||||
msgstr "Ruta de archivo inválida."
|
||||
|
||||
msgid "Invalid font URL."
|
||||
msgstr "URL de fuente inválida."
|
||||
|
||||
msgid "Invalid font name."
|
||||
msgstr "Nombre de fuente inválido."
|
||||
|
||||
msgid "No valid weights specified."
|
||||
msgstr "No se especificaron pesos válidos."
|
||||
|
||||
msgid "No valid styles specified."
|
||||
msgstr "No se especificaron estilos válidos."
|
||||
|
||||
msgid "An unexpected error occurred. Please try again."
|
||||
msgstr "Ocurrió un error inesperado. Por favor, intenta de nuevo."
|
||||
|
||||
msgid "Thin"
|
||||
msgstr "Delgada"
|
||||
|
||||
msgid "Extra Light"
|
||||
msgstr "Extra ligera"
|
||||
|
||||
msgid "Light"
|
||||
msgstr "Ligera"
|
||||
|
||||
msgid "Regular"
|
||||
msgstr "Normal"
|
||||
|
||||
msgid "Medium"
|
||||
msgstr "Media"
|
||||
|
||||
msgid "Semi Bold"
|
||||
msgstr "Semi negrita"
|
||||
|
||||
msgid "Bold"
|
||||
msgstr "Negrita"
|
||||
|
||||
msgid "Extra Bold"
|
||||
msgstr "Extra negrita"
|
||||
|
||||
msgid "Black"
|
||||
msgstr "Negra"
|
||||
|
||||
msgid "You do not have sufficient permissions to access this page."
|
||||
msgstr "No tienes permisos suficientes para acceder a esta página."
|
||||
|
||||
msgid "Import from Google Fonts"
|
||||
msgstr "Importar desde Google Fonts"
|
||||
|
||||
msgid "Font Name"
|
||||
msgstr "Nombre de la fuente"
|
||||
|
||||
msgid "e.g., Open Sans"
|
||||
msgstr "ej., Open Sans"
|
||||
|
||||
msgid "Enter the exact font name as it appears on Google Fonts."
|
||||
msgstr "Ingresa el nombre exacto de la fuente como aparece en Google Fonts."
|
||||
|
||||
msgid "Weights"
|
||||
msgstr "Pesos"
|
||||
|
||||
msgid "Styles"
|
||||
msgstr "Estilos"
|
||||
|
||||
msgid "Normal"
|
||||
msgstr "Normal"
|
||||
|
||||
msgid "Italic"
|
||||
msgstr "Cursiva"
|
||||
|
||||
msgid "Files to download:"
|
||||
msgstr "Archivos a descargar:"
|
||||
|
||||
msgid "Download & Install"
|
||||
msgstr "Descargar e instalar"
|
||||
|
||||
msgid "Installed Fonts"
|
||||
msgstr "Fuentes instaladas"
|
||||
|
||||
msgid "No fonts installed yet."
|
||||
msgstr "Aún no hay fuentes instaladas."
|
||||
|
||||
msgid "Delete"
|
||||
msgstr "Eliminar"
|
||||
|
||||
msgid "Use %s to apply fonts to your site."
|
||||
msgstr "Usa %s para aplicar fuentes a tu sitio."
|
||||
|
||||
msgid "Appearance → Editor → Styles → Typography"
|
||||
msgstr "Apariencia → Editor → Estilos → Tipografía"
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
# Translation of Maple Local Fonts in Spanish (Spain)
|
||||
# Copyright (C) 2024 Maple Open Technologies
|
||||
# This file is distributed under the GPL-2.0-or-later.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Maple Local Fonts 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: https://mapleopentech.org/\n"
|
||||
"Last-Translator: Maple Open Technologies\n"
|
||||
"Language-Team: Spanish (Spain)\n"
|
||||
"Language: es_ES\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"POT-Creation-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"PO-Revision-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"X-Generator: Manual\n"
|
||||
"X-Domain: maple-local-fonts\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. Plugin Name of the plugin
|
||||
msgid "Maple Local Fonts"
|
||||
msgstr "Maple Local Fonts"
|
||||
|
||||
#. Description of the plugin
|
||||
msgid "Import Google Fonts to local storage and register them with WordPress Font Library for GDPR-compliant, privacy-friendly typography."
|
||||
msgstr "Importa fuentes de Google Fonts al almacenamiento local y regístralas con la biblioteca de fuentes de WordPress para una tipografía compatible con el RGPD y respetuosa con la privacidad."
|
||||
|
||||
#. Author of the plugin
|
||||
msgid "Maple Open Technologies"
|
||||
msgstr "Maple Open Technologies"
|
||||
|
||||
msgid "Maple Local Fonts requires WordPress 6.5 or higher for Font Library support."
|
||||
msgstr "Maple Local Fonts requiere WordPress 6.5 o superior para el soporte de la biblioteca de fuentes."
|
||||
|
||||
msgid "Plugin Activation Error"
|
||||
msgstr "Error de activación del plugin"
|
||||
|
||||
msgid "Maple Local Fonts has been deactivated. It requires WordPress 6.5 or higher."
|
||||
msgstr "Maple Local Fonts ha sido desactivado. Requiere WordPress 6.5 o superior."
|
||||
|
||||
msgid "Local Fonts"
|
||||
msgstr "Fuentes locales"
|
||||
|
||||
msgid "Downloading..."
|
||||
msgstr "Descargando..."
|
||||
|
||||
msgid "Deleting..."
|
||||
msgstr "Eliminando..."
|
||||
|
||||
msgid "Are you sure you want to delete this font?"
|
||||
msgstr "¿Estás seguro de que quieres eliminar esta fuente?"
|
||||
|
||||
msgid "An error occurred. Please try again."
|
||||
msgstr "Se ha producido un error. Por favor, inténtalo de nuevo."
|
||||
|
||||
msgid "Please select at least one weight."
|
||||
msgstr "Por favor, selecciona al menos un peso."
|
||||
|
||||
msgid "Please select at least one style."
|
||||
msgstr "Por favor, selecciona al menos un estilo."
|
||||
|
||||
msgid "Please enter a font name."
|
||||
msgstr "Por favor, introduce un nombre de fuente."
|
||||
|
||||
msgid "Security check failed."
|
||||
msgstr "Ha fallado la verificación de seguridad."
|
||||
|
||||
msgid "Unauthorized."
|
||||
msgstr "No autorizado."
|
||||
|
||||
msgid "Font name is required."
|
||||
msgstr "El nombre de la fuente es obligatorio."
|
||||
|
||||
msgid "Invalid font name: only letters, numbers, spaces, and hyphens allowed."
|
||||
msgstr "Nombre de fuente no válido: solo se permiten letras, números, espacios y guiones."
|
||||
|
||||
msgid "Font name is too long."
|
||||
msgstr "El nombre de la fuente es demasiado largo."
|
||||
|
||||
msgid "At least one weight is required."
|
||||
msgstr "Se requiere al menos un peso."
|
||||
|
||||
msgid "Too many weights selected."
|
||||
msgstr "Demasiados pesos seleccionados."
|
||||
|
||||
msgid "At least one style is required."
|
||||
msgstr "Se requiere al menos un estilo."
|
||||
|
||||
msgid "Successfully installed %s."
|
||||
msgstr "%s se ha instalado correctamente."
|
||||
|
||||
msgid "An unexpected error occurred."
|
||||
msgstr "Se ha producido un error inesperado."
|
||||
|
||||
msgid "Invalid font ID."
|
||||
msgstr "ID de fuente no válido."
|
||||
|
||||
msgid "Font not found."
|
||||
msgstr "Fuente no encontrada."
|
||||
|
||||
msgid "Cannot delete fonts not imported by this plugin."
|
||||
msgstr "No se pueden eliminar fuentes que no hayan sido importadas por este plugin."
|
||||
|
||||
msgid "Font deleted successfully."
|
||||
msgstr "Fuente eliminada correctamente."
|
||||
|
||||
msgid "Font not found on Google Fonts. Please check the spelling and try again."
|
||||
msgstr "Fuente no encontrada en Google Fonts. Por favor, comprueba la ortografía e inténtalo de nuevo."
|
||||
|
||||
msgid "This font is already installed."
|
||||
msgstr "Esta fuente ya está instalada."
|
||||
|
||||
msgid "Could not connect to Google Fonts. Please check your internet connection and try again."
|
||||
msgstr "No se ha podido conectar a Google Fonts. Por favor, comprueba tu conexión a internet e inténtalo de nuevo."
|
||||
|
||||
msgid "Google Fonts returned an error. Please try again later."
|
||||
msgstr "Google Fonts ha devuelto un error. Por favor, inténtalo más tarde."
|
||||
|
||||
msgid "Could not process the font data. The font may not be available."
|
||||
msgstr "No se han podido procesar los datos de la fuente. La fuente puede no estar disponible."
|
||||
|
||||
msgid "Could not download the font files. Please try again."
|
||||
msgstr "No se han podido descargar los archivos de la fuente. Por favor, inténtalo de nuevo."
|
||||
|
||||
msgid "Could not save font files. Please check that wp-content/fonts is writable."
|
||||
msgstr "No se han podido guardar los archivos de la fuente. Por favor, comprueba que wp-content/fonts tenga permisos de escritura."
|
||||
|
||||
msgid "Could not create fonts directory. Please check file permissions."
|
||||
msgstr "No se ha podido crear el directorio de fuentes. Por favor, comprueba los permisos de archivos."
|
||||
|
||||
msgid "Invalid file path."
|
||||
msgstr "Ruta de archivo no válida."
|
||||
|
||||
msgid "Invalid font URL."
|
||||
msgstr "URL de fuente no válida."
|
||||
|
||||
msgid "Invalid font name."
|
||||
msgstr "Nombre de fuente no válido."
|
||||
|
||||
msgid "No valid weights specified."
|
||||
msgstr "No se han especificado pesos válidos."
|
||||
|
||||
msgid "No valid styles specified."
|
||||
msgstr "No se han especificado estilos válidos."
|
||||
|
||||
msgid "An unexpected error occurred. Please try again."
|
||||
msgstr "Se ha producido un error inesperado. Por favor, inténtalo de nuevo."
|
||||
|
||||
msgid "Thin"
|
||||
msgstr "Fina"
|
||||
|
||||
msgid "Extra Light"
|
||||
msgstr "Extra ligera"
|
||||
|
||||
msgid "Light"
|
||||
msgstr "Ligera"
|
||||
|
||||
msgid "Regular"
|
||||
msgstr "Normal"
|
||||
|
||||
msgid "Medium"
|
||||
msgstr "Media"
|
||||
|
||||
msgid "Semi Bold"
|
||||
msgstr "Semi negrita"
|
||||
|
||||
msgid "Bold"
|
||||
msgstr "Negrita"
|
||||
|
||||
msgid "Extra Bold"
|
||||
msgstr "Extra negrita"
|
||||
|
||||
msgid "Black"
|
||||
msgstr "Negra"
|
||||
|
||||
msgid "You do not have sufficient permissions to access this page."
|
||||
msgstr "No tienes permisos suficientes para acceder a esta página."
|
||||
|
||||
msgid "Import from Google Fonts"
|
||||
msgstr "Importar desde Google Fonts"
|
||||
|
||||
msgid "Font Name"
|
||||
msgstr "Nombre de la fuente"
|
||||
|
||||
msgid "e.g., Open Sans"
|
||||
msgstr "ej., Open Sans"
|
||||
|
||||
msgid "Enter the exact font name as it appears on Google Fonts."
|
||||
msgstr "Introduce el nombre exacto de la fuente como aparece en Google Fonts."
|
||||
|
||||
msgid "Weights"
|
||||
msgstr "Pesos"
|
||||
|
||||
msgid "Styles"
|
||||
msgstr "Estilos"
|
||||
|
||||
msgid "Normal"
|
||||
msgstr "Normal"
|
||||
|
||||
msgid "Italic"
|
||||
msgstr "Cursiva"
|
||||
|
||||
msgid "Files to download:"
|
||||
msgstr "Archivos a descargar:"
|
||||
|
||||
msgid "Download & Install"
|
||||
msgstr "Descargar e instalar"
|
||||
|
||||
msgid "Installed Fonts"
|
||||
msgstr "Fuentes instaladas"
|
||||
|
||||
msgid "No fonts installed yet."
|
||||
msgstr "Aún no hay fuentes instaladas."
|
||||
|
||||
msgid "Delete"
|
||||
msgstr "Eliminar"
|
||||
|
||||
msgid "Use %s to apply fonts to your site."
|
||||
msgstr "Usa %s para aplicar fuentes a tu sitio."
|
||||
|
||||
msgid "Appearance → Editor → Styles → Typography"
|
||||
msgstr "Apariencia → Editor → Estilos → Tipografía"
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
# Translation of Maple Local Fonts in Spanish (Mexico)
|
||||
# Copyright (C) 2024 Maple Open Technologies
|
||||
# This file is distributed under the GPL-2.0-or-later.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Maple Local Fonts 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: https://mapleopentech.org/\n"
|
||||
"Last-Translator: Maple Open Technologies\n"
|
||||
"Language-Team: Spanish (Mexico)\n"
|
||||
"Language: es_MX\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"POT-Creation-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"PO-Revision-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"X-Generator: Manual\n"
|
||||
"X-Domain: maple-local-fonts\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. Plugin Name of the plugin
|
||||
msgid "Maple Local Fonts"
|
||||
msgstr "Maple Local Fonts"
|
||||
|
||||
#. Description of the plugin
|
||||
msgid "Import Google Fonts to local storage and register them with WordPress Font Library for GDPR-compliant, privacy-friendly typography."
|
||||
msgstr "Importa fuentes de Google Fonts al almacenamiento local y regístralas con la biblioteca de fuentes de WordPress para una tipografía compatible con GDPR y respetuosa con la privacidad."
|
||||
|
||||
#. Author of the plugin
|
||||
msgid "Maple Open Technologies"
|
||||
msgstr "Maple Open Technologies"
|
||||
|
||||
msgid "Maple Local Fonts requires WordPress 6.5 or higher for Font Library support."
|
||||
msgstr "Maple Local Fonts requiere WordPress 6.5 o superior para el soporte de la biblioteca de fuentes."
|
||||
|
||||
msgid "Plugin Activation Error"
|
||||
msgstr "Error de activación del plugin"
|
||||
|
||||
msgid "Maple Local Fonts has been deactivated. It requires WordPress 6.5 or higher."
|
||||
msgstr "Maple Local Fonts ha sido desactivado. Requiere WordPress 6.5 o superior."
|
||||
|
||||
msgid "Local Fonts"
|
||||
msgstr "Fuentes locales"
|
||||
|
||||
msgid "Downloading..."
|
||||
msgstr "Descargando..."
|
||||
|
||||
msgid "Deleting..."
|
||||
msgstr "Eliminando..."
|
||||
|
||||
msgid "Are you sure you want to delete this font?"
|
||||
msgstr "¿Estás seguro de que quieres eliminar esta fuente?"
|
||||
|
||||
msgid "An error occurred. Please try again."
|
||||
msgstr "Ocurrió un error. Por favor, inténtalo de nuevo."
|
||||
|
||||
msgid "Please select at least one weight."
|
||||
msgstr "Por favor, selecciona al menos un peso."
|
||||
|
||||
msgid "Please select at least one style."
|
||||
msgstr "Por favor, selecciona al menos un estilo."
|
||||
|
||||
msgid "Please enter a font name."
|
||||
msgstr "Por favor, ingresa un nombre de fuente."
|
||||
|
||||
msgid "Security check failed."
|
||||
msgstr "Falló la verificación de seguridad."
|
||||
|
||||
msgid "Unauthorized."
|
||||
msgstr "No autorizado."
|
||||
|
||||
msgid "Font name is required."
|
||||
msgstr "El nombre de la fuente es requerido."
|
||||
|
||||
msgid "Invalid font name: only letters, numbers, spaces, and hyphens allowed."
|
||||
msgstr "Nombre de fuente inválido: solo se permiten letras, números, espacios y guiones."
|
||||
|
||||
msgid "Font name is too long."
|
||||
msgstr "El nombre de la fuente es demasiado largo."
|
||||
|
||||
msgid "At least one weight is required."
|
||||
msgstr "Se requiere al menos un peso."
|
||||
|
||||
msgid "Too many weights selected."
|
||||
msgstr "Demasiados pesos seleccionados."
|
||||
|
||||
msgid "At least one style is required."
|
||||
msgstr "Se requiere al menos un estilo."
|
||||
|
||||
msgid "Successfully installed %s."
|
||||
msgstr "%s se instaló correctamente."
|
||||
|
||||
msgid "An unexpected error occurred."
|
||||
msgstr "Ocurrió un error inesperado."
|
||||
|
||||
msgid "Invalid font ID."
|
||||
msgstr "ID de fuente inválido."
|
||||
|
||||
msgid "Font not found."
|
||||
msgstr "Fuente no encontrada."
|
||||
|
||||
msgid "Cannot delete fonts not imported by this plugin."
|
||||
msgstr "No se pueden eliminar fuentes que no fueron importadas por este plugin."
|
||||
|
||||
msgid "Font deleted successfully."
|
||||
msgstr "Fuente eliminada correctamente."
|
||||
|
||||
msgid "Font not found on Google Fonts. Please check the spelling and try again."
|
||||
msgstr "Fuente no encontrada en Google Fonts. Por favor, verifica la ortografía e inténtalo de nuevo."
|
||||
|
||||
msgid "This font is already installed."
|
||||
msgstr "Esta fuente ya está instalada."
|
||||
|
||||
msgid "Could not connect to Google Fonts. Please check your internet connection and try again."
|
||||
msgstr "No se pudo conectar a Google Fonts. Por favor, verifica tu conexión a internet e inténtalo de nuevo."
|
||||
|
||||
msgid "Google Fonts returned an error. Please try again later."
|
||||
msgstr "Google Fonts devolvió un error. Por favor, inténtalo más tarde."
|
||||
|
||||
msgid "Could not process the font data. The font may not be available."
|
||||
msgstr "No se pudieron procesar los datos de la fuente. La fuente puede no estar disponible."
|
||||
|
||||
msgid "Could not download the font files. Please try again."
|
||||
msgstr "No se pudieron descargar los archivos de la fuente. Por favor, inténtalo de nuevo."
|
||||
|
||||
msgid "Could not save font files. Please check that wp-content/fonts is writable."
|
||||
msgstr "No se pudieron guardar los archivos de la fuente. Por favor, verifica que wp-content/fonts tenga permisos de escritura."
|
||||
|
||||
msgid "Could not create fonts directory. Please check file permissions."
|
||||
msgstr "No se pudo crear el directorio de fuentes. Por favor, verifica los permisos de archivos."
|
||||
|
||||
msgid "Invalid file path."
|
||||
msgstr "Ruta de archivo inválida."
|
||||
|
||||
msgid "Invalid font URL."
|
||||
msgstr "URL de fuente inválida."
|
||||
|
||||
msgid "Invalid font name."
|
||||
msgstr "Nombre de fuente inválido."
|
||||
|
||||
msgid "No valid weights specified."
|
||||
msgstr "No se especificaron pesos válidos."
|
||||
|
||||
msgid "No valid styles specified."
|
||||
msgstr "No se especificaron estilos válidos."
|
||||
|
||||
msgid "An unexpected error occurred. Please try again."
|
||||
msgstr "Ocurrió un error inesperado. Por favor, inténtalo de nuevo."
|
||||
|
||||
msgid "Thin"
|
||||
msgstr "Delgada"
|
||||
|
||||
msgid "Extra Light"
|
||||
msgstr "Extra ligera"
|
||||
|
||||
msgid "Light"
|
||||
msgstr "Ligera"
|
||||
|
||||
msgid "Regular"
|
||||
msgstr "Normal"
|
||||
|
||||
msgid "Medium"
|
||||
msgstr "Media"
|
||||
|
||||
msgid "Semi Bold"
|
||||
msgstr "Semi negrita"
|
||||
|
||||
msgid "Bold"
|
||||
msgstr "Negrita"
|
||||
|
||||
msgid "Extra Bold"
|
||||
msgstr "Extra negrita"
|
||||
|
||||
msgid "Black"
|
||||
msgstr "Negra"
|
||||
|
||||
msgid "You do not have sufficient permissions to access this page."
|
||||
msgstr "No tienes permisos suficientes para acceder a esta página."
|
||||
|
||||
msgid "Import from Google Fonts"
|
||||
msgstr "Importar desde Google Fonts"
|
||||
|
||||
msgid "Font Name"
|
||||
msgstr "Nombre de la fuente"
|
||||
|
||||
msgid "e.g., Open Sans"
|
||||
msgstr "ej., Open Sans"
|
||||
|
||||
msgid "Enter the exact font name as it appears on Google Fonts."
|
||||
msgstr "Ingresa el nombre exacto de la fuente como aparece en Google Fonts."
|
||||
|
||||
msgid "Weights"
|
||||
msgstr "Pesos"
|
||||
|
||||
msgid "Styles"
|
||||
msgstr "Estilos"
|
||||
|
||||
msgid "Normal"
|
||||
msgstr "Normal"
|
||||
|
||||
msgid "Italic"
|
||||
msgstr "Cursiva"
|
||||
|
||||
msgid "Files to download:"
|
||||
msgstr "Archivos a descargar:"
|
||||
|
||||
msgid "Download & Install"
|
||||
msgstr "Descargar e instalar"
|
||||
|
||||
msgid "Installed Fonts"
|
||||
msgstr "Fuentes instaladas"
|
||||
|
||||
msgid "No fonts installed yet."
|
||||
msgstr "Aún no hay fuentes instaladas."
|
||||
|
||||
msgid "Delete"
|
||||
msgstr "Eliminar"
|
||||
|
||||
msgid "Use %s to apply fonts to your site."
|
||||
msgstr "Usa %s para aplicar fuentes a tu sitio."
|
||||
|
||||
msgid "Appearance → Editor → Styles → Typography"
|
||||
msgstr "Apariencia → Editor → Estilos → Tipografía"
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
# Translation of Maple Local Fonts in French (Canada)
|
||||
# Copyright (C) 2024 Maple Open Technologies
|
||||
# This file is distributed under the GPL-2.0-or-later.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Maple Local Fonts 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: https://mapleopentech.org/\n"
|
||||
"Last-Translator: Maple Open Technologies\n"
|
||||
"Language-Team: French (Canada)\n"
|
||||
"Language: fr_CA\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"POT-Creation-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"PO-Revision-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"X-Generator: Manual\n"
|
||||
"X-Domain: maple-local-fonts\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#. Plugin Name of the plugin
|
||||
msgid "Maple Local Fonts"
|
||||
msgstr "Maple Local Fonts"
|
||||
|
||||
#. Description of the plugin
|
||||
msgid "Import Google Fonts to local storage and register them with WordPress Font Library for GDPR-compliant, privacy-friendly typography."
|
||||
msgstr "Importez des polices Google Fonts vers le stockage local et enregistrez-les avec la bibliothèque de polices WordPress pour une typographie conforme au RGPD et respectueuse de la vie privée."
|
||||
|
||||
#. Author of the plugin
|
||||
msgid "Maple Open Technologies"
|
||||
msgstr "Maple Open Technologies"
|
||||
|
||||
msgid "Maple Local Fonts requires WordPress 6.5 or higher for Font Library support."
|
||||
msgstr "Maple Local Fonts nécessite WordPress 6.5 ou supérieur pour la prise en charge de la bibliothèque de polices."
|
||||
|
||||
msgid "Plugin Activation Error"
|
||||
msgstr "Erreur d'activation de l'extension"
|
||||
|
||||
msgid "Maple Local Fonts has been deactivated. It requires WordPress 6.5 or higher."
|
||||
msgstr "Maple Local Fonts a été désactivé. Il nécessite WordPress 6.5 ou supérieur."
|
||||
|
||||
msgid "Local Fonts"
|
||||
msgstr "Polices locales"
|
||||
|
||||
msgid "Downloading..."
|
||||
msgstr "Téléchargement..."
|
||||
|
||||
msgid "Deleting..."
|
||||
msgstr "Suppression..."
|
||||
|
||||
msgid "Are you sure you want to delete this font?"
|
||||
msgstr "Êtes-vous certain de vouloir supprimer cette police?"
|
||||
|
||||
msgid "An error occurred. Please try again."
|
||||
msgstr "Une erreur s'est produite. Veuillez réessayer."
|
||||
|
||||
msgid "Please select at least one weight."
|
||||
msgstr "Veuillez sélectionner au moins une graisse."
|
||||
|
||||
msgid "Please select at least one style."
|
||||
msgstr "Veuillez sélectionner au moins un style."
|
||||
|
||||
msgid "Please enter a font name."
|
||||
msgstr "Veuillez entrer un nom de police."
|
||||
|
||||
msgid "Security check failed."
|
||||
msgstr "Échec de la vérification de sécurité."
|
||||
|
||||
msgid "Unauthorized."
|
||||
msgstr "Non autorisé."
|
||||
|
||||
msgid "Font name is required."
|
||||
msgstr "Le nom de la police est requis."
|
||||
|
||||
msgid "Invalid font name: only letters, numbers, spaces, and hyphens allowed."
|
||||
msgstr "Nom de police invalide : seuls les lettres, chiffres, espaces et tirets sont autorisés."
|
||||
|
||||
msgid "Font name is too long."
|
||||
msgstr "Le nom de la police est trop long."
|
||||
|
||||
msgid "At least one weight is required."
|
||||
msgstr "Au moins une graisse est requise."
|
||||
|
||||
msgid "Too many weights selected."
|
||||
msgstr "Trop de graisses sélectionnées."
|
||||
|
||||
msgid "At least one style is required."
|
||||
msgstr "Au moins un style est requis."
|
||||
|
||||
msgid "Successfully installed %s."
|
||||
msgstr "%s a été installé avec succès."
|
||||
|
||||
msgid "An unexpected error occurred."
|
||||
msgstr "Une erreur inattendue s'est produite."
|
||||
|
||||
msgid "Invalid font ID."
|
||||
msgstr "ID de police invalide."
|
||||
|
||||
msgid "Font not found."
|
||||
msgstr "Police introuvable."
|
||||
|
||||
msgid "Cannot delete fonts not imported by this plugin."
|
||||
msgstr "Impossible de supprimer les polices non importées par cette extension."
|
||||
|
||||
msgid "Font deleted successfully."
|
||||
msgstr "Police supprimée avec succès."
|
||||
|
||||
msgid "Font not found on Google Fonts. Please check the spelling and try again."
|
||||
msgstr "Police introuvable sur Google Fonts. Veuillez vérifier l'orthographe et réessayer."
|
||||
|
||||
msgid "This font is already installed."
|
||||
msgstr "Cette police est déjà installée."
|
||||
|
||||
msgid "Could not connect to Google Fonts. Please check your internet connection and try again."
|
||||
msgstr "Connexion à Google Fonts impossible. Veuillez vérifier votre connexion Internet et réessayer."
|
||||
|
||||
msgid "Google Fonts returned an error. Please try again later."
|
||||
msgstr "Google Fonts a retourné une erreur. Veuillez réessayer plus tard."
|
||||
|
||||
msgid "Could not process the font data. The font may not be available."
|
||||
msgstr "Impossible de traiter les données de la police. La police n'est peut-être pas disponible."
|
||||
|
||||
msgid "Could not download the font files. Please try again."
|
||||
msgstr "Impossible de télécharger les fichiers de police. Veuillez réessayer."
|
||||
|
||||
msgid "Could not save font files. Please check that wp-content/fonts is writable."
|
||||
msgstr "Impossible d'enregistrer les fichiers de police. Veuillez vérifier que wp-content/fonts est accessible en écriture."
|
||||
|
||||
msgid "Could not create fonts directory. Please check file permissions."
|
||||
msgstr "Impossible de créer le répertoire des polices. Veuillez vérifier les permissions de fichiers."
|
||||
|
||||
msgid "Invalid file path."
|
||||
msgstr "Chemin de fichier invalide."
|
||||
|
||||
msgid "Invalid font URL."
|
||||
msgstr "URL de police invalide."
|
||||
|
||||
msgid "Invalid font name."
|
||||
msgstr "Nom de police invalide."
|
||||
|
||||
msgid "No valid weights specified."
|
||||
msgstr "Aucune graisse valide spécifiée."
|
||||
|
||||
msgid "No valid styles specified."
|
||||
msgstr "Aucun style valide spécifié."
|
||||
|
||||
msgid "An unexpected error occurred. Please try again."
|
||||
msgstr "Une erreur inattendue s'est produite. Veuillez réessayer."
|
||||
|
||||
msgid "Thin"
|
||||
msgstr "Fin"
|
||||
|
||||
msgid "Extra Light"
|
||||
msgstr "Extra léger"
|
||||
|
||||
msgid "Light"
|
||||
msgstr "Léger"
|
||||
|
||||
msgid "Regular"
|
||||
msgstr "Normal"
|
||||
|
||||
msgid "Medium"
|
||||
msgstr "Moyen"
|
||||
|
||||
msgid "Semi Bold"
|
||||
msgstr "Semi-gras"
|
||||
|
||||
msgid "Bold"
|
||||
msgstr "Gras"
|
||||
|
||||
msgid "Extra Bold"
|
||||
msgstr "Extra gras"
|
||||
|
||||
msgid "Black"
|
||||
msgstr "Noir"
|
||||
|
||||
msgid "You do not have sufficient permissions to access this page."
|
||||
msgstr "Vous n'avez pas les permissions suffisantes pour accéder à cette page."
|
||||
|
||||
msgid "Import from Google Fonts"
|
||||
msgstr "Importer depuis Google Fonts"
|
||||
|
||||
msgid "Font Name"
|
||||
msgstr "Nom de la police"
|
||||
|
||||
msgid "e.g., Open Sans"
|
||||
msgstr "ex. : Open Sans"
|
||||
|
||||
msgid "Enter the exact font name as it appears on Google Fonts."
|
||||
msgstr "Entrez le nom exact de la police tel qu'il apparaît sur Google Fonts."
|
||||
|
||||
msgid "Weights"
|
||||
msgstr "Graisses"
|
||||
|
||||
msgid "Styles"
|
||||
msgstr "Styles"
|
||||
|
||||
msgid "Normal"
|
||||
msgstr "Normal"
|
||||
|
||||
msgid "Italic"
|
||||
msgstr "Italique"
|
||||
|
||||
msgid "Files to download:"
|
||||
msgstr "Fichiers à télécharger :"
|
||||
|
||||
msgid "Download & Install"
|
||||
msgstr "Télécharger et installer"
|
||||
|
||||
msgid "Installed Fonts"
|
||||
msgstr "Polices installées"
|
||||
|
||||
msgid "No fonts installed yet."
|
||||
msgstr "Aucune police installée pour le moment."
|
||||
|
||||
msgid "Delete"
|
||||
msgstr "Supprimer"
|
||||
|
||||
msgid "Use %s to apply fonts to your site."
|
||||
msgstr "Utilisez %s pour appliquer les polices à votre site."
|
||||
|
||||
msgid "Appearance → Editor → Styles → Typography"
|
||||
msgstr "Apparence → Éditeur → Styles → Typographie"
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
# Translation of Maple Local Fonts in French (France)
|
||||
# Copyright (C) 2024 Maple Open Technologies
|
||||
# This file is distributed under the GPL-2.0-or-later.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Maple Local Fonts 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: https://mapleopentech.org/\n"
|
||||
"Last-Translator: Maple Open Technologies\n"
|
||||
"Language-Team: French (France)\n"
|
||||
"Language: fr_FR\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"POT-Creation-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"PO-Revision-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"X-Generator: Manual\n"
|
||||
"X-Domain: maple-local-fonts\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#. Plugin Name of the plugin
|
||||
msgid "Maple Local Fonts"
|
||||
msgstr "Maple Local Fonts"
|
||||
|
||||
#. Description of the plugin
|
||||
msgid "Import Google Fonts to local storage and register them with WordPress Font Library for GDPR-compliant, privacy-friendly typography."
|
||||
msgstr "Importez des polices Google Fonts vers le stockage local et enregistrez-les avec la bibliothèque de polices WordPress pour une typographie conforme au RGPD et respectueuse de la vie privée."
|
||||
|
||||
#. Author of the plugin
|
||||
msgid "Maple Open Technologies"
|
||||
msgstr "Maple Open Technologies"
|
||||
|
||||
msgid "Maple Local Fonts requires WordPress 6.5 or higher for Font Library support."
|
||||
msgstr "Maple Local Fonts nécessite WordPress 6.5 ou supérieur pour la prise en charge de la bibliothèque de polices."
|
||||
|
||||
msgid "Plugin Activation Error"
|
||||
msgstr "Erreur d'activation de l'extension"
|
||||
|
||||
msgid "Maple Local Fonts has been deactivated. It requires WordPress 6.5 or higher."
|
||||
msgstr "Maple Local Fonts a été désactivé. Il nécessite WordPress 6.5 ou supérieur."
|
||||
|
||||
msgid "Local Fonts"
|
||||
msgstr "Polices locales"
|
||||
|
||||
msgid "Downloading..."
|
||||
msgstr "Téléchargement..."
|
||||
|
||||
msgid "Deleting..."
|
||||
msgstr "Suppression..."
|
||||
|
||||
msgid "Are you sure you want to delete this font?"
|
||||
msgstr "Êtes-vous sûr de vouloir supprimer cette police ?"
|
||||
|
||||
msgid "An error occurred. Please try again."
|
||||
msgstr "Une erreur s'est produite. Veuillez réessayer."
|
||||
|
||||
msgid "Please select at least one weight."
|
||||
msgstr "Veuillez sélectionner au moins une graisse."
|
||||
|
||||
msgid "Please select at least one style."
|
||||
msgstr "Veuillez sélectionner au moins un style."
|
||||
|
||||
msgid "Please enter a font name."
|
||||
msgstr "Veuillez saisir un nom de police."
|
||||
|
||||
msgid "Security check failed."
|
||||
msgstr "Échec de la vérification de sécurité."
|
||||
|
||||
msgid "Unauthorized."
|
||||
msgstr "Non autorisé."
|
||||
|
||||
msgid "Font name is required."
|
||||
msgstr "Le nom de la police est requis."
|
||||
|
||||
msgid "Invalid font name: only letters, numbers, spaces, and hyphens allowed."
|
||||
msgstr "Nom de police non valide : seuls les lettres, chiffres, espaces et tirets sont autorisés."
|
||||
|
||||
msgid "Font name is too long."
|
||||
msgstr "Le nom de la police est trop long."
|
||||
|
||||
msgid "At least one weight is required."
|
||||
msgstr "Au moins une graisse est requise."
|
||||
|
||||
msgid "Too many weights selected."
|
||||
msgstr "Trop de graisses sélectionnées."
|
||||
|
||||
msgid "At least one style is required."
|
||||
msgstr "Au moins un style est requis."
|
||||
|
||||
msgid "Successfully installed %s."
|
||||
msgstr "%s a été installé avec succès."
|
||||
|
||||
msgid "An unexpected error occurred."
|
||||
msgstr "Une erreur inattendue s'est produite."
|
||||
|
||||
msgid "Invalid font ID."
|
||||
msgstr "ID de police non valide."
|
||||
|
||||
msgid "Font not found."
|
||||
msgstr "Police introuvable."
|
||||
|
||||
msgid "Cannot delete fonts not imported by this plugin."
|
||||
msgstr "Impossible de supprimer les polices non importées par cette extension."
|
||||
|
||||
msgid "Font deleted successfully."
|
||||
msgstr "Police supprimée avec succès."
|
||||
|
||||
msgid "Font not found on Google Fonts. Please check the spelling and try again."
|
||||
msgstr "Police introuvable sur Google Fonts. Veuillez vérifier l'orthographe et réessayer."
|
||||
|
||||
msgid "This font is already installed."
|
||||
msgstr "Cette police est déjà installée."
|
||||
|
||||
msgid "Could not connect to Google Fonts. Please check your internet connection and try again."
|
||||
msgstr "Impossible de se connecter à Google Fonts. Veuillez vérifier votre connexion Internet et réessayer."
|
||||
|
||||
msgid "Google Fonts returned an error. Please try again later."
|
||||
msgstr "Google Fonts a renvoyé une erreur. Veuillez réessayer plus tard."
|
||||
|
||||
msgid "Could not process the font data. The font may not be available."
|
||||
msgstr "Impossible de traiter les données de la police. La police n'est peut-être pas disponible."
|
||||
|
||||
msgid "Could not download the font files. Please try again."
|
||||
msgstr "Impossible de télécharger les fichiers de police. Veuillez réessayer."
|
||||
|
||||
msgid "Could not save font files. Please check that wp-content/fonts is writable."
|
||||
msgstr "Impossible d'enregistrer les fichiers de police. Veuillez vérifier que wp-content/fonts est accessible en écriture."
|
||||
|
||||
msgid "Could not create fonts directory. Please check file permissions."
|
||||
msgstr "Impossible de créer le répertoire des polices. Veuillez vérifier les droits d'accès aux fichiers."
|
||||
|
||||
msgid "Invalid file path."
|
||||
msgstr "Chemin de fichier non valide."
|
||||
|
||||
msgid "Invalid font URL."
|
||||
msgstr "URL de police non valide."
|
||||
|
||||
msgid "Invalid font name."
|
||||
msgstr "Nom de police non valide."
|
||||
|
||||
msgid "No valid weights specified."
|
||||
msgstr "Aucune graisse valide spécifiée."
|
||||
|
||||
msgid "No valid styles specified."
|
||||
msgstr "Aucun style valide spécifié."
|
||||
|
||||
msgid "An unexpected error occurred. Please try again."
|
||||
msgstr "Une erreur inattendue s'est produite. Veuillez réessayer."
|
||||
|
||||
msgid "Thin"
|
||||
msgstr "Fin"
|
||||
|
||||
msgid "Extra Light"
|
||||
msgstr "Extra léger"
|
||||
|
||||
msgid "Light"
|
||||
msgstr "Léger"
|
||||
|
||||
msgid "Regular"
|
||||
msgstr "Normal"
|
||||
|
||||
msgid "Medium"
|
||||
msgstr "Moyen"
|
||||
|
||||
msgid "Semi Bold"
|
||||
msgstr "Semi-gras"
|
||||
|
||||
msgid "Bold"
|
||||
msgstr "Gras"
|
||||
|
||||
msgid "Extra Bold"
|
||||
msgstr "Extra gras"
|
||||
|
||||
msgid "Black"
|
||||
msgstr "Noir"
|
||||
|
||||
msgid "You do not have sufficient permissions to access this page."
|
||||
msgstr "Vous n'avez pas les droits suffisants pour accéder à cette page."
|
||||
|
||||
msgid "Import from Google Fonts"
|
||||
msgstr "Importer depuis Google Fonts"
|
||||
|
||||
msgid "Font Name"
|
||||
msgstr "Nom de la police"
|
||||
|
||||
msgid "e.g., Open Sans"
|
||||
msgstr "ex. : Open Sans"
|
||||
|
||||
msgid "Enter the exact font name as it appears on Google Fonts."
|
||||
msgstr "Saisissez le nom exact de la police tel qu'il apparaît sur Google Fonts."
|
||||
|
||||
msgid "Weights"
|
||||
msgstr "Graisses"
|
||||
|
||||
msgid "Styles"
|
||||
msgstr "Styles"
|
||||
|
||||
msgid "Normal"
|
||||
msgstr "Normal"
|
||||
|
||||
msgid "Italic"
|
||||
msgstr "Italique"
|
||||
|
||||
msgid "Files to download:"
|
||||
msgstr "Fichiers à télécharger :"
|
||||
|
||||
msgid "Download & Install"
|
||||
msgstr "Télécharger et installer"
|
||||
|
||||
msgid "Installed Fonts"
|
||||
msgstr "Polices installées"
|
||||
|
||||
msgid "No fonts installed yet."
|
||||
msgstr "Aucune police installée pour le moment."
|
||||
|
||||
msgid "Delete"
|
||||
msgstr "Supprimer"
|
||||
|
||||
msgid "Use %s to apply fonts to your site."
|
||||
msgstr "Utilisez %s pour appliquer les polices à votre site."
|
||||
|
||||
msgid "Appearance → Editor → Styles → Typography"
|
||||
msgstr "Apparence → Éditeur → Styles → Typographie"
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
# Translation of Maple Local Fonts in Hindi
|
||||
# Copyright (C) 2024 Maple Open Technologies
|
||||
# This file is distributed under the GPL-2.0-or-later.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Maple Local Fonts 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: https://mapleopentech.org/\n"
|
||||
"Last-Translator: Maple Open Technologies\n"
|
||||
"Language-Team: Hindi\n"
|
||||
"Language: hi_IN\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"POT-Creation-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"PO-Revision-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"X-Generator: Manual\n"
|
||||
"X-Domain: maple-local-fonts\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. Plugin Name of the plugin
|
||||
msgid "Maple Local Fonts"
|
||||
msgstr "Maple Local Fonts"
|
||||
|
||||
#. Description of the plugin
|
||||
msgid "Import Google Fonts to local storage and register them with WordPress Font Library for GDPR-compliant, privacy-friendly typography."
|
||||
msgstr "Google Fonts को स्थानीय भंडारण में आयात करें और उन्हें GDPR-अनुपालन और गोपनीयता-अनुकूल टाइपोग्राफी के लिए WordPress फ़ॉन्ट लाइब्रेरी में पंजीकृत करें।"
|
||||
|
||||
#. Author of the plugin
|
||||
msgid "Maple Open Technologies"
|
||||
msgstr "Maple Open Technologies"
|
||||
|
||||
msgid "Maple Local Fonts requires WordPress 6.5 or higher for Font Library support."
|
||||
msgstr "Maple Local Fonts को फ़ॉन्ट लाइब्रेरी सहायता के लिए WordPress 6.5 या उच्चतर की आवश्यकता है।"
|
||||
|
||||
msgid "Plugin Activation Error"
|
||||
msgstr "प्लगइन सक्रियण त्रुटि"
|
||||
|
||||
msgid "Maple Local Fonts has been deactivated. It requires WordPress 6.5 or higher."
|
||||
msgstr "Maple Local Fonts निष्क्रिय कर दिया गया है। इसके लिए WordPress 6.5 या उच्चतर आवश्यक है।"
|
||||
|
||||
msgid "Local Fonts"
|
||||
msgstr "स्थानीय फ़ॉन्ट"
|
||||
|
||||
msgid "Downloading..."
|
||||
msgstr "डाउनलोड हो रहा है..."
|
||||
|
||||
msgid "Deleting..."
|
||||
msgstr "हटाया जा रहा है..."
|
||||
|
||||
msgid "Are you sure you want to delete this font?"
|
||||
msgstr "क्या आप वाकई इस फ़ॉन्ट को हटाना चाहते हैं?"
|
||||
|
||||
msgid "An error occurred. Please try again."
|
||||
msgstr "एक त्रुटि हुई। कृपया पुनः प्रयास करें।"
|
||||
|
||||
msgid "Please select at least one weight."
|
||||
msgstr "कृपया कम से कम एक वज़न चुनें।"
|
||||
|
||||
msgid "Please select at least one style."
|
||||
msgstr "कृपया कम से कम एक शैली चुनें।"
|
||||
|
||||
msgid "Please enter a font name."
|
||||
msgstr "कृपया फ़ॉन्ट का नाम दर्ज करें।"
|
||||
|
||||
msgid "Security check failed."
|
||||
msgstr "सुरक्षा जांच विफल।"
|
||||
|
||||
msgid "Unauthorized."
|
||||
msgstr "अनधिकृत।"
|
||||
|
||||
msgid "Font name is required."
|
||||
msgstr "फ़ॉन्ट नाम आवश्यक है।"
|
||||
|
||||
msgid "Invalid font name: only letters, numbers, spaces, and hyphens allowed."
|
||||
msgstr "अमान्य फ़ॉन्ट नाम: केवल अक्षर, संख्याएं, रिक्त स्थान और हाइफ़न की अनुमति है।"
|
||||
|
||||
msgid "Font name is too long."
|
||||
msgstr "फ़ॉन्ट नाम बहुत लंबा है।"
|
||||
|
||||
msgid "At least one weight is required."
|
||||
msgstr "कम से कम एक वज़न आवश्यक है।"
|
||||
|
||||
msgid "Too many weights selected."
|
||||
msgstr "बहुत अधिक वज़न चुने गए।"
|
||||
|
||||
msgid "At least one style is required."
|
||||
msgstr "कम से कम एक शैली आवश्यक है।"
|
||||
|
||||
msgid "Successfully installed %s."
|
||||
msgstr "%s सफलतापूर्वक स्थापित किया गया।"
|
||||
|
||||
msgid "An unexpected error occurred."
|
||||
msgstr "एक अप्रत्याशित त्रुटि हुई।"
|
||||
|
||||
msgid "Invalid font ID."
|
||||
msgstr "अमान्य फ़ॉन्ट ID।"
|
||||
|
||||
msgid "Font not found."
|
||||
msgstr "फ़ॉन्ट नहीं मिला।"
|
||||
|
||||
msgid "Cannot delete fonts not imported by this plugin."
|
||||
msgstr "इस प्लगइन द्वारा आयात नहीं किए गए फ़ॉन्ट को हटाया नहीं जा सकता।"
|
||||
|
||||
msgid "Font deleted successfully."
|
||||
msgstr "फ़ॉन्ट सफलतापूर्वक हटाया गया।"
|
||||
|
||||
msgid "Font not found on Google Fonts. Please check the spelling and try again."
|
||||
msgstr "Google Fonts पर फ़ॉन्ट नहीं मिला। कृपया वर्तनी जांचें और पुनः प्रयास करें।"
|
||||
|
||||
msgid "This font is already installed."
|
||||
msgstr "यह फ़ॉन्ट पहले से स्थापित है।"
|
||||
|
||||
msgid "Could not connect to Google Fonts. Please check your internet connection and try again."
|
||||
msgstr "Google Fonts से कनेक्ट नहीं हो सका। कृपया अपना इंटरनेट कनेक्शन जांचें और पुनः प्रयास करें।"
|
||||
|
||||
msgid "Google Fonts returned an error. Please try again later."
|
||||
msgstr "Google Fonts ने एक त्रुटि लौटाई। कृपया बाद में पुनः प्रयास करें।"
|
||||
|
||||
msgid "Could not process the font data. The font may not be available."
|
||||
msgstr "फ़ॉन्ट डेटा को संसाधित नहीं किया जा सका। फ़ॉन्ट उपलब्ध नहीं हो सकता है।"
|
||||
|
||||
msgid "Could not download the font files. Please try again."
|
||||
msgstr "फ़ॉन्ट फ़ाइलें डाउनलोड नहीं हो सकीं। कृपया पुनः प्रयास करें।"
|
||||
|
||||
msgid "Could not save font files. Please check that wp-content/fonts is writable."
|
||||
msgstr "फ़ॉन्ट फ़ाइलें सहेजी नहीं जा सकीं। कृपया जांचें कि wp-content/fonts लिखने योग्य है।"
|
||||
|
||||
msgid "Could not create fonts directory. Please check file permissions."
|
||||
msgstr "फ़ॉन्ट निर्देशिका नहीं बनाई जा सकी। कृपया फ़ाइल अनुमतियां जांचें।"
|
||||
|
||||
msgid "Invalid file path."
|
||||
msgstr "अमान्य फ़ाइल पथ।"
|
||||
|
||||
msgid "Invalid font URL."
|
||||
msgstr "अमान्य फ़ॉन्ट URL।"
|
||||
|
||||
msgid "Invalid font name."
|
||||
msgstr "अमान्य फ़ॉन्ट नाम।"
|
||||
|
||||
msgid "No valid weights specified."
|
||||
msgstr "कोई वैध वज़न निर्दिष्ट नहीं।"
|
||||
|
||||
msgid "No valid styles specified."
|
||||
msgstr "कोई वैध शैली निर्दिष्ट नहीं।"
|
||||
|
||||
msgid "An unexpected error occurred. Please try again."
|
||||
msgstr "एक अप्रत्याशित त्रुटि हुई। कृपया पुनः प्रयास करें।"
|
||||
|
||||
msgid "Thin"
|
||||
msgstr "पतला"
|
||||
|
||||
msgid "Extra Light"
|
||||
msgstr "अति हल्का"
|
||||
|
||||
msgid "Light"
|
||||
msgstr "हल्का"
|
||||
|
||||
msgid "Regular"
|
||||
msgstr "सामान्य"
|
||||
|
||||
msgid "Medium"
|
||||
msgstr "मध्यम"
|
||||
|
||||
msgid "Semi Bold"
|
||||
msgstr "अर्ध-मोटा"
|
||||
|
||||
msgid "Bold"
|
||||
msgstr "मोटा"
|
||||
|
||||
msgid "Extra Bold"
|
||||
msgstr "अति मोटा"
|
||||
|
||||
msgid "Black"
|
||||
msgstr "काला"
|
||||
|
||||
msgid "You do not have sufficient permissions to access this page."
|
||||
msgstr "आपके पास इस पृष्ठ तक पहुंचने की पर्याप्त अनुमति नहीं है।"
|
||||
|
||||
msgid "Import from Google Fonts"
|
||||
msgstr "Google Fonts से आयात करें"
|
||||
|
||||
msgid "Font Name"
|
||||
msgstr "फ़ॉन्ट नाम"
|
||||
|
||||
msgid "e.g., Open Sans"
|
||||
msgstr "उदा., Open Sans"
|
||||
|
||||
msgid "Enter the exact font name as it appears on Google Fonts."
|
||||
msgstr "Google Fonts पर दिखाई देने वाला सटीक फ़ॉन्ट नाम दर्ज करें।"
|
||||
|
||||
msgid "Weights"
|
||||
msgstr "वज़न"
|
||||
|
||||
msgid "Styles"
|
||||
msgstr "शैलियां"
|
||||
|
||||
msgid "Normal"
|
||||
msgstr "सामान्य"
|
||||
|
||||
msgid "Italic"
|
||||
msgstr "इटैलिक"
|
||||
|
||||
msgid "Files to download:"
|
||||
msgstr "डाउनलोड करने के लिए फ़ाइलें:"
|
||||
|
||||
msgid "Download & Install"
|
||||
msgstr "डाउनलोड और इंस्टॉल करें"
|
||||
|
||||
msgid "Installed Fonts"
|
||||
msgstr "स्थापित फ़ॉन्ट"
|
||||
|
||||
msgid "No fonts installed yet."
|
||||
msgstr "अभी तक कोई फ़ॉन्ट स्थापित नहीं।"
|
||||
|
||||
msgid "Delete"
|
||||
msgstr "हटाएं"
|
||||
|
||||
msgid "Use %s to apply fonts to your site."
|
||||
msgstr "अपनी साइट पर फ़ॉन्ट लागू करने के लिए %s का उपयोग करें।"
|
||||
|
||||
msgid "Appearance → Editor → Styles → Typography"
|
||||
msgstr "प्रकटन → संपादक → शैलियां → टाइपोग्राफी"
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
# Translation of Maple Local Fonts in Indonesian
|
||||
# Copyright (C) 2024 Maple Open Technologies
|
||||
# This file is distributed under the GPL-2.0-or-later.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Maple Local Fonts 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: https://mapleopentech.org/\n"
|
||||
"Last-Translator: Maple Open Technologies\n"
|
||||
"Language-Team: Indonesian\n"
|
||||
"Language: id_ID\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"POT-Creation-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"PO-Revision-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"X-Generator: Manual\n"
|
||||
"X-Domain: maple-local-fonts\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#. Plugin Name of the plugin
|
||||
msgid "Maple Local Fonts"
|
||||
msgstr "Maple Local Fonts"
|
||||
|
||||
#. Description of the plugin
|
||||
msgid "Import Google Fonts to local storage and register them with WordPress Font Library for GDPR-compliant, privacy-friendly typography."
|
||||
msgstr "Impor Google Fonts ke penyimpanan lokal dan daftarkan ke Perpustakaan Font WordPress untuk tipografi yang sesuai GDPR dan ramah privasi."
|
||||
|
||||
#. Author of the plugin
|
||||
msgid "Maple Open Technologies"
|
||||
msgstr "Maple Open Technologies"
|
||||
|
||||
msgid "Maple Local Fonts requires WordPress 6.5 or higher for Font Library support."
|
||||
msgstr "Maple Local Fonts memerlukan WordPress 6.5 atau lebih tinggi untuk dukungan Perpustakaan Font."
|
||||
|
||||
msgid "Plugin Activation Error"
|
||||
msgstr "Kesalahan Aktivasi Plugin"
|
||||
|
||||
msgid "Maple Local Fonts has been deactivated. It requires WordPress 6.5 or higher."
|
||||
msgstr "Maple Local Fonts telah dinonaktifkan. Memerlukan WordPress 6.5 atau lebih tinggi."
|
||||
|
||||
msgid "Local Fonts"
|
||||
msgstr "Font Lokal"
|
||||
|
||||
msgid "Downloading..."
|
||||
msgstr "Mengunduh..."
|
||||
|
||||
msgid "Deleting..."
|
||||
msgstr "Menghapus..."
|
||||
|
||||
msgid "Are you sure you want to delete this font?"
|
||||
msgstr "Apakah Anda yakin ingin menghapus font ini?"
|
||||
|
||||
msgid "An error occurred. Please try again."
|
||||
msgstr "Terjadi kesalahan. Silakan coba lagi."
|
||||
|
||||
msgid "Please select at least one weight."
|
||||
msgstr "Silakan pilih setidaknya satu ketebalan."
|
||||
|
||||
msgid "Please select at least one style."
|
||||
msgstr "Silakan pilih setidaknya satu gaya."
|
||||
|
||||
msgid "Please enter a font name."
|
||||
msgstr "Silakan masukkan nama font."
|
||||
|
||||
msgid "Security check failed."
|
||||
msgstr "Pemeriksaan keamanan gagal."
|
||||
|
||||
msgid "Unauthorized."
|
||||
msgstr "Tidak diizinkan."
|
||||
|
||||
msgid "Font name is required."
|
||||
msgstr "Nama font diperlukan."
|
||||
|
||||
msgid "Invalid font name: only letters, numbers, spaces, and hyphens allowed."
|
||||
msgstr "Nama font tidak valid: hanya huruf, angka, spasi, dan tanda hubung yang diperbolehkan."
|
||||
|
||||
msgid "Font name is too long."
|
||||
msgstr "Nama font terlalu panjang."
|
||||
|
||||
msgid "At least one weight is required."
|
||||
msgstr "Setidaknya satu ketebalan diperlukan."
|
||||
|
||||
msgid "Too many weights selected."
|
||||
msgstr "Terlalu banyak ketebalan dipilih."
|
||||
|
||||
msgid "At least one style is required."
|
||||
msgstr "Setidaknya satu gaya diperlukan."
|
||||
|
||||
msgid "Successfully installed %s."
|
||||
msgstr "Berhasil menginstal %s."
|
||||
|
||||
msgid "An unexpected error occurred."
|
||||
msgstr "Terjadi kesalahan yang tidak terduga."
|
||||
|
||||
msgid "Invalid font ID."
|
||||
msgstr "ID font tidak valid."
|
||||
|
||||
msgid "Font not found."
|
||||
msgstr "Font tidak ditemukan."
|
||||
|
||||
msgid "Cannot delete fonts not imported by this plugin."
|
||||
msgstr "Tidak dapat menghapus font yang tidak diimpor oleh plugin ini."
|
||||
|
||||
msgid "Font deleted successfully."
|
||||
msgstr "Font berhasil dihapus."
|
||||
|
||||
msgid "Font not found on Google Fonts. Please check the spelling and try again."
|
||||
msgstr "Font tidak ditemukan di Google Fonts. Silakan periksa ejaan dan coba lagi."
|
||||
|
||||
msgid "This font is already installed."
|
||||
msgstr "Font ini sudah terinstal."
|
||||
|
||||
msgid "Could not connect to Google Fonts. Please check your internet connection and try again."
|
||||
msgstr "Tidak dapat terhubung ke Google Fonts. Silakan periksa koneksi internet Anda dan coba lagi."
|
||||
|
||||
msgid "Google Fonts returned an error. Please try again later."
|
||||
msgstr "Google Fonts mengembalikan kesalahan. Silakan coba lagi nanti."
|
||||
|
||||
msgid "Could not process the font data. The font may not be available."
|
||||
msgstr "Tidak dapat memproses data font. Font mungkin tidak tersedia."
|
||||
|
||||
msgid "Could not download the font files. Please try again."
|
||||
msgstr "Tidak dapat mengunduh file font. Silakan coba lagi."
|
||||
|
||||
msgid "Could not save font files. Please check that wp-content/fonts is writable."
|
||||
msgstr "Tidak dapat menyimpan file font. Silakan periksa apakah wp-content/fonts dapat ditulis."
|
||||
|
||||
msgid "Could not create fonts directory. Please check file permissions."
|
||||
msgstr "Tidak dapat membuat direktori font. Silakan periksa izin file."
|
||||
|
||||
msgid "Invalid file path."
|
||||
msgstr "Jalur file tidak valid."
|
||||
|
||||
msgid "Invalid font URL."
|
||||
msgstr "URL font tidak valid."
|
||||
|
||||
msgid "Invalid font name."
|
||||
msgstr "Nama font tidak valid."
|
||||
|
||||
msgid "No valid weights specified."
|
||||
msgstr "Tidak ada ketebalan valid yang ditentukan."
|
||||
|
||||
msgid "No valid styles specified."
|
||||
msgstr "Tidak ada gaya valid yang ditentukan."
|
||||
|
||||
msgid "An unexpected error occurred. Please try again."
|
||||
msgstr "Terjadi kesalahan yang tidak terduga. Silakan coba lagi."
|
||||
|
||||
msgid "Thin"
|
||||
msgstr "Tipis"
|
||||
|
||||
msgid "Extra Light"
|
||||
msgstr "Ekstra Ringan"
|
||||
|
||||
msgid "Light"
|
||||
msgstr "Ringan"
|
||||
|
||||
msgid "Regular"
|
||||
msgstr "Reguler"
|
||||
|
||||
msgid "Medium"
|
||||
msgstr "Sedang"
|
||||
|
||||
msgid "Semi Bold"
|
||||
msgstr "Semi Tebal"
|
||||
|
||||
msgid "Bold"
|
||||
msgstr "Tebal"
|
||||
|
||||
msgid "Extra Bold"
|
||||
msgstr "Ekstra Tebal"
|
||||
|
||||
msgid "Black"
|
||||
msgstr "Hitam"
|
||||
|
||||
msgid "You do not have sufficient permissions to access this page."
|
||||
msgstr "Anda tidak memiliki izin yang cukup untuk mengakses halaman ini."
|
||||
|
||||
msgid "Import from Google Fonts"
|
||||
msgstr "Impor dari Google Fonts"
|
||||
|
||||
msgid "Font Name"
|
||||
msgstr "Nama Font"
|
||||
|
||||
msgid "e.g., Open Sans"
|
||||
msgstr "contoh: Open Sans"
|
||||
|
||||
msgid "Enter the exact font name as it appears on Google Fonts."
|
||||
msgstr "Masukkan nama font persis seperti yang muncul di Google Fonts."
|
||||
|
||||
msgid "Weights"
|
||||
msgstr "Ketebalan"
|
||||
|
||||
msgid "Styles"
|
||||
msgstr "Gaya"
|
||||
|
||||
msgid "Normal"
|
||||
msgstr "Normal"
|
||||
|
||||
msgid "Italic"
|
||||
msgstr "Miring"
|
||||
|
||||
msgid "Files to download:"
|
||||
msgstr "File untuk diunduh:"
|
||||
|
||||
msgid "Download & Install"
|
||||
msgstr "Unduh & Instal"
|
||||
|
||||
msgid "Installed Fonts"
|
||||
msgstr "Font Terinstal"
|
||||
|
||||
msgid "No fonts installed yet."
|
||||
msgstr "Belum ada font yang terinstal."
|
||||
|
||||
msgid "Delete"
|
||||
msgstr "Hapus"
|
||||
|
||||
msgid "Use %s to apply fonts to your site."
|
||||
msgstr "Gunakan %s untuk menerapkan font ke situs Anda."
|
||||
|
||||
msgid "Appearance → Editor → Styles → Typography"
|
||||
msgstr "Tampilan → Editor → Gaya → Tipografi"
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
# Translation of Maple Local Fonts in Italian
|
||||
# Copyright (C) 2024 Maple Open Technologies
|
||||
# This file is distributed under the GPL-2.0-or-later.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Maple Local Fonts 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: https://mapleopentech.org/\n"
|
||||
"Last-Translator: Maple Open Technologies\n"
|
||||
"Language-Team: Italian\n"
|
||||
"Language: it_IT\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"POT-Creation-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"PO-Revision-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"X-Generator: Manual\n"
|
||||
"X-Domain: maple-local-fonts\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. Plugin Name of the plugin
|
||||
msgid "Maple Local Fonts"
|
||||
msgstr "Maple Local Fonts"
|
||||
|
||||
#. Description of the plugin
|
||||
msgid "Import Google Fonts to local storage and register them with WordPress Font Library for GDPR-compliant, privacy-friendly typography."
|
||||
msgstr "Importa i font di Google Fonts nello storage locale e registrali nella libreria font di WordPress per una tipografia conforme al GDPR e rispettosa della privacy."
|
||||
|
||||
#. Author of the plugin
|
||||
msgid "Maple Open Technologies"
|
||||
msgstr "Maple Open Technologies"
|
||||
|
||||
msgid "Maple Local Fonts requires WordPress 6.5 or higher for Font Library support."
|
||||
msgstr "Maple Local Fonts richiede WordPress 6.5 o superiore per il supporto della libreria font."
|
||||
|
||||
msgid "Plugin Activation Error"
|
||||
msgstr "Errore di attivazione del plugin"
|
||||
|
||||
msgid "Maple Local Fonts has been deactivated. It requires WordPress 6.5 or higher."
|
||||
msgstr "Maple Local Fonts è stato disattivato. Richiede WordPress 6.5 o superiore."
|
||||
|
||||
msgid "Local Fonts"
|
||||
msgstr "Font locali"
|
||||
|
||||
msgid "Downloading..."
|
||||
msgstr "Download in corso..."
|
||||
|
||||
msgid "Deleting..."
|
||||
msgstr "Eliminazione in corso..."
|
||||
|
||||
msgid "Are you sure you want to delete this font?"
|
||||
msgstr "Sei sicuro di voler eliminare questo font?"
|
||||
|
||||
msgid "An error occurred. Please try again."
|
||||
msgstr "Si è verificato un errore. Riprova."
|
||||
|
||||
msgid "Please select at least one weight."
|
||||
msgstr "Seleziona almeno un peso."
|
||||
|
||||
msgid "Please select at least one style."
|
||||
msgstr "Seleziona almeno uno stile."
|
||||
|
||||
msgid "Please enter a font name."
|
||||
msgstr "Inserisci un nome del font."
|
||||
|
||||
msgid "Security check failed."
|
||||
msgstr "Controllo di sicurezza fallito."
|
||||
|
||||
msgid "Unauthorized."
|
||||
msgstr "Non autorizzato."
|
||||
|
||||
msgid "Font name is required."
|
||||
msgstr "Il nome del font è obbligatorio."
|
||||
|
||||
msgid "Invalid font name: only letters, numbers, spaces, and hyphens allowed."
|
||||
msgstr "Nome del font non valido: sono consentiti solo lettere, numeri, spazi e trattini."
|
||||
|
||||
msgid "Font name is too long."
|
||||
msgstr "Il nome del font è troppo lungo."
|
||||
|
||||
msgid "At least one weight is required."
|
||||
msgstr "È richiesto almeno un peso."
|
||||
|
||||
msgid "Too many weights selected."
|
||||
msgstr "Troppi pesi selezionati."
|
||||
|
||||
msgid "At least one style is required."
|
||||
msgstr "È richiesto almeno uno stile."
|
||||
|
||||
msgid "Successfully installed %s."
|
||||
msgstr "%s è stato installato con successo."
|
||||
|
||||
msgid "An unexpected error occurred."
|
||||
msgstr "Si è verificato un errore imprevisto."
|
||||
|
||||
msgid "Invalid font ID."
|
||||
msgstr "ID font non valido."
|
||||
|
||||
msgid "Font not found."
|
||||
msgstr "Font non trovato."
|
||||
|
||||
msgid "Cannot delete fonts not imported by this plugin."
|
||||
msgstr "Impossibile eliminare i font non importati da questo plugin."
|
||||
|
||||
msgid "Font deleted successfully."
|
||||
msgstr "Font eliminato con successo."
|
||||
|
||||
msgid "Font not found on Google Fonts. Please check the spelling and try again."
|
||||
msgstr "Font non trovato su Google Fonts. Controlla l'ortografia e riprova."
|
||||
|
||||
msgid "This font is already installed."
|
||||
msgstr "Questo font è già installato."
|
||||
|
||||
msgid "Could not connect to Google Fonts. Please check your internet connection and try again."
|
||||
msgstr "Impossibile connettersi a Google Fonts. Controlla la tua connessione internet e riprova."
|
||||
|
||||
msgid "Google Fonts returned an error. Please try again later."
|
||||
msgstr "Google Fonts ha restituito un errore. Riprova più tardi."
|
||||
|
||||
msgid "Could not process the font data. The font may not be available."
|
||||
msgstr "Impossibile elaborare i dati del font. Il font potrebbe non essere disponibile."
|
||||
|
||||
msgid "Could not download the font files. Please try again."
|
||||
msgstr "Impossibile scaricare i file del font. Riprova."
|
||||
|
||||
msgid "Could not save font files. Please check that wp-content/fonts is writable."
|
||||
msgstr "Impossibile salvare i file del font. Verifica che wp-content/fonts sia scrivibile."
|
||||
|
||||
msgid "Could not create fonts directory. Please check file permissions."
|
||||
msgstr "Impossibile creare la directory dei font. Controlla i permessi dei file."
|
||||
|
||||
msgid "Invalid file path."
|
||||
msgstr "Percorso file non valido."
|
||||
|
||||
msgid "Invalid font URL."
|
||||
msgstr "URL del font non valido."
|
||||
|
||||
msgid "Invalid font name."
|
||||
msgstr "Nome del font non valido."
|
||||
|
||||
msgid "No valid weights specified."
|
||||
msgstr "Nessun peso valido specificato."
|
||||
|
||||
msgid "No valid styles specified."
|
||||
msgstr "Nessuno stile valido specificato."
|
||||
|
||||
msgid "An unexpected error occurred. Please try again."
|
||||
msgstr "Si è verificato un errore imprevisto. Riprova."
|
||||
|
||||
msgid "Thin"
|
||||
msgstr "Sottile"
|
||||
|
||||
msgid "Extra Light"
|
||||
msgstr "Extra leggero"
|
||||
|
||||
msgid "Light"
|
||||
msgstr "Leggero"
|
||||
|
||||
msgid "Regular"
|
||||
msgstr "Normale"
|
||||
|
||||
msgid "Medium"
|
||||
msgstr "Medio"
|
||||
|
||||
msgid "Semi Bold"
|
||||
msgstr "Semi grassetto"
|
||||
|
||||
msgid "Bold"
|
||||
msgstr "Grassetto"
|
||||
|
||||
msgid "Extra Bold"
|
||||
msgstr "Extra grassetto"
|
||||
|
||||
msgid "Black"
|
||||
msgstr "Nero"
|
||||
|
||||
msgid "You do not have sufficient permissions to access this page."
|
||||
msgstr "Non hai i permessi sufficienti per accedere a questa pagina."
|
||||
|
||||
msgid "Import from Google Fonts"
|
||||
msgstr "Importa da Google Fonts"
|
||||
|
||||
msgid "Font Name"
|
||||
msgstr "Nome del font"
|
||||
|
||||
msgid "e.g., Open Sans"
|
||||
msgstr "es. Open Sans"
|
||||
|
||||
msgid "Enter the exact font name as it appears on Google Fonts."
|
||||
msgstr "Inserisci il nome esatto del font come appare su Google Fonts."
|
||||
|
||||
msgid "Weights"
|
||||
msgstr "Pesi"
|
||||
|
||||
msgid "Styles"
|
||||
msgstr "Stili"
|
||||
|
||||
msgid "Normal"
|
||||
msgstr "Normale"
|
||||
|
||||
msgid "Italic"
|
||||
msgstr "Corsivo"
|
||||
|
||||
msgid "Files to download:"
|
||||
msgstr "File da scaricare:"
|
||||
|
||||
msgid "Download & Install"
|
||||
msgstr "Scarica e installa"
|
||||
|
||||
msgid "Installed Fonts"
|
||||
msgstr "Font installati"
|
||||
|
||||
msgid "No fonts installed yet."
|
||||
msgstr "Nessun font installato."
|
||||
|
||||
msgid "Delete"
|
||||
msgstr "Elimina"
|
||||
|
||||
msgid "Use %s to apply fonts to your site."
|
||||
msgstr "Usa %s per applicare i font al tuo sito."
|
||||
|
||||
msgid "Appearance → Editor → Styles → Typography"
|
||||
msgstr "Aspetto → Editor → Stili → Tipografia"
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
# Translation of Maple Local Fonts in Japanese
|
||||
# Copyright (C) 2024 Maple Open Technologies
|
||||
# This file is distributed under the GPL-2.0-or-later.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Maple Local Fonts 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: https://mapleopentech.org/\n"
|
||||
"Last-Translator: Maple Open Technologies\n"
|
||||
"Language-Team: Japanese\n"
|
||||
"Language: ja\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"POT-Creation-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"PO-Revision-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"X-Generator: Manual\n"
|
||||
"X-Domain: maple-local-fonts\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#. Plugin Name of the plugin
|
||||
msgid "Maple Local Fonts"
|
||||
msgstr "Maple Local Fonts"
|
||||
|
||||
#. Description of the plugin
|
||||
msgid "Import Google Fonts to local storage and register them with WordPress Font Library for GDPR-compliant, privacy-friendly typography."
|
||||
msgstr "Google FontsをローカルストレージにインポートしてWordPressフォントライブラリに登録し、GDPR準拠でプライバシーに配慮したタイポグラフィを実現します。"
|
||||
|
||||
#. Author of the plugin
|
||||
msgid "Maple Open Technologies"
|
||||
msgstr "Maple Open Technologies"
|
||||
|
||||
msgid "Maple Local Fonts requires WordPress 6.5 or higher for Font Library support."
|
||||
msgstr "Maple Local FontsはフォントライブラリのサポートにWordPress 6.5以上が必要です。"
|
||||
|
||||
msgid "Plugin Activation Error"
|
||||
msgstr "プラグイン有効化エラー"
|
||||
|
||||
msgid "Maple Local Fonts has been deactivated. It requires WordPress 6.5 or higher."
|
||||
msgstr "Maple Local Fontsは無効化されました。WordPress 6.5以上が必要です。"
|
||||
|
||||
msgid "Local Fonts"
|
||||
msgstr "ローカルフォント"
|
||||
|
||||
msgid "Downloading..."
|
||||
msgstr "ダウンロード中..."
|
||||
|
||||
msgid "Deleting..."
|
||||
msgstr "削除中..."
|
||||
|
||||
msgid "Are you sure you want to delete this font?"
|
||||
msgstr "このフォントを削除してもよろしいですか?"
|
||||
|
||||
msgid "An error occurred. Please try again."
|
||||
msgstr "エラーが発生しました。もう一度お試しください。"
|
||||
|
||||
msgid "Please select at least one weight."
|
||||
msgstr "少なくとも1つのウェイトを選択してください。"
|
||||
|
||||
msgid "Please select at least one style."
|
||||
msgstr "少なくとも1つのスタイルを選択してください。"
|
||||
|
||||
msgid "Please enter a font name."
|
||||
msgstr "フォント名を入力してください。"
|
||||
|
||||
msgid "Security check failed."
|
||||
msgstr "セキュリティチェックに失敗しました。"
|
||||
|
||||
msgid "Unauthorized."
|
||||
msgstr "権限がありません。"
|
||||
|
||||
msgid "Font name is required."
|
||||
msgstr "フォント名は必須です。"
|
||||
|
||||
msgid "Invalid font name: only letters, numbers, spaces, and hyphens allowed."
|
||||
msgstr "無効なフォント名:文字、数字、スペース、ハイフンのみ使用できます。"
|
||||
|
||||
msgid "Font name is too long."
|
||||
msgstr "フォント名が長すぎます。"
|
||||
|
||||
msgid "At least one weight is required."
|
||||
msgstr "少なくとも1つのウェイトが必要です。"
|
||||
|
||||
msgid "Too many weights selected."
|
||||
msgstr "選択されたウェイトが多すぎます。"
|
||||
|
||||
msgid "At least one style is required."
|
||||
msgstr "少なくとも1つのスタイルが必要です。"
|
||||
|
||||
msgid "Successfully installed %s."
|
||||
msgstr "%sのインストールに成功しました。"
|
||||
|
||||
msgid "An unexpected error occurred."
|
||||
msgstr "予期しないエラーが発生しました。"
|
||||
|
||||
msgid "Invalid font ID."
|
||||
msgstr "無効なフォントID。"
|
||||
|
||||
msgid "Font not found."
|
||||
msgstr "フォントが見つかりません。"
|
||||
|
||||
msgid "Cannot delete fonts not imported by this plugin."
|
||||
msgstr "このプラグインでインポートされていないフォントは削除できません。"
|
||||
|
||||
msgid "Font deleted successfully."
|
||||
msgstr "フォントが正常に削除されました。"
|
||||
|
||||
msgid "Font not found on Google Fonts. Please check the spelling and try again."
|
||||
msgstr "Google Fontsでフォントが見つかりません。スペルを確認して、もう一度お試しください。"
|
||||
|
||||
msgid "This font is already installed."
|
||||
msgstr "このフォントは既にインストールされています。"
|
||||
|
||||
msgid "Could not connect to Google Fonts. Please check your internet connection and try again."
|
||||
msgstr "Google Fontsに接続できませんでした。インターネット接続を確認して、もう一度お試しください。"
|
||||
|
||||
msgid "Google Fonts returned an error. Please try again later."
|
||||
msgstr "Google Fontsがエラーを返しました。後でもう一度お試しください。"
|
||||
|
||||
msgid "Could not process the font data. The font may not be available."
|
||||
msgstr "フォントデータを処理できませんでした。フォントは利用できない可能性があります。"
|
||||
|
||||
msgid "Could not download the font files. Please try again."
|
||||
msgstr "フォントファイルをダウンロードできませんでした。もう一度お試しください。"
|
||||
|
||||
msgid "Could not save font files. Please check that wp-content/fonts is writable."
|
||||
msgstr "フォントファイルを保存できませんでした。wp-content/fontsが書き込み可能か確認してください。"
|
||||
|
||||
msgid "Could not create fonts directory. Please check file permissions."
|
||||
msgstr "フォントディレクトリを作成できませんでした。ファイルのパーミッションを確認してください。"
|
||||
|
||||
msgid "Invalid file path."
|
||||
msgstr "無効なファイルパス。"
|
||||
|
||||
msgid "Invalid font URL."
|
||||
msgstr "無効なフォントURL。"
|
||||
|
||||
msgid "Invalid font name."
|
||||
msgstr "無効なフォント名。"
|
||||
|
||||
msgid "No valid weights specified."
|
||||
msgstr "有効なウェイトが指定されていません。"
|
||||
|
||||
msgid "No valid styles specified."
|
||||
msgstr "有効なスタイルが指定されていません。"
|
||||
|
||||
msgid "An unexpected error occurred. Please try again."
|
||||
msgstr "予期しないエラーが発生しました。もう一度お試しください。"
|
||||
|
||||
msgid "Thin"
|
||||
msgstr "極細"
|
||||
|
||||
msgid "Extra Light"
|
||||
msgstr "極細"
|
||||
|
||||
msgid "Light"
|
||||
msgstr "細字"
|
||||
|
||||
msgid "Regular"
|
||||
msgstr "標準"
|
||||
|
||||
msgid "Medium"
|
||||
msgstr "中字"
|
||||
|
||||
msgid "Semi Bold"
|
||||
msgstr "やや太字"
|
||||
|
||||
msgid "Bold"
|
||||
msgstr "太字"
|
||||
|
||||
msgid "Extra Bold"
|
||||
msgstr "極太"
|
||||
|
||||
msgid "Black"
|
||||
msgstr "極太"
|
||||
|
||||
msgid "You do not have sufficient permissions to access this page."
|
||||
msgstr "このページにアクセスする権限がありません。"
|
||||
|
||||
msgid "Import from Google Fonts"
|
||||
msgstr "Google Fontsからインポート"
|
||||
|
||||
msgid "Font Name"
|
||||
msgstr "フォント名"
|
||||
|
||||
msgid "e.g., Open Sans"
|
||||
msgstr "例:Open Sans"
|
||||
|
||||
msgid "Enter the exact font name as it appears on Google Fonts."
|
||||
msgstr "Google Fontsに表示されている正確なフォント名を入力してください。"
|
||||
|
||||
msgid "Weights"
|
||||
msgstr "ウェイト"
|
||||
|
||||
msgid "Styles"
|
||||
msgstr "スタイル"
|
||||
|
||||
msgid "Normal"
|
||||
msgstr "標準"
|
||||
|
||||
msgid "Italic"
|
||||
msgstr "イタリック"
|
||||
|
||||
msgid "Files to download:"
|
||||
msgstr "ダウンロードするファイル:"
|
||||
|
||||
msgid "Download & Install"
|
||||
msgstr "ダウンロード&インストール"
|
||||
|
||||
msgid "Installed Fonts"
|
||||
msgstr "インストール済みフォント"
|
||||
|
||||
msgid "No fonts installed yet."
|
||||
msgstr "まだフォントがインストールされていません。"
|
||||
|
||||
msgid "Delete"
|
||||
msgstr "削除"
|
||||
|
||||
msgid "Use %s to apply fonts to your site."
|
||||
msgstr "%sを使用してサイトにフォントを適用してください。"
|
||||
|
||||
msgid "Appearance → Editor → Styles → Typography"
|
||||
msgstr "外観 → エディター → スタイル → タイポグラフィ"
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
# Translation of Maple Local Fonts in Korean
|
||||
# Copyright (C) 2024 Maple Open Technologies
|
||||
# This file is distributed under the GPL-2.0-or-later.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Maple Local Fonts 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: https://mapleopentech.org/\n"
|
||||
"Last-Translator: Maple Open Technologies\n"
|
||||
"Language-Team: Korean\n"
|
||||
"Language: ko_KR\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"POT-Creation-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"PO-Revision-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"X-Generator: Manual\n"
|
||||
"X-Domain: maple-local-fonts\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#. Plugin Name of the plugin
|
||||
msgid "Maple Local Fonts"
|
||||
msgstr "Maple Local Fonts"
|
||||
|
||||
#. Description of the plugin
|
||||
msgid "Import Google Fonts to local storage and register them with WordPress Font Library for GDPR-compliant, privacy-friendly typography."
|
||||
msgstr "Google Fonts를 로컬 저장소로 가져와 WordPress 글꼴 라이브러리에 등록하여 GDPR 준수 및 개인정보 보호 친화적인 타이포그래피를 구현합니다."
|
||||
|
||||
#. Author of the plugin
|
||||
msgid "Maple Open Technologies"
|
||||
msgstr "Maple Open Technologies"
|
||||
|
||||
msgid "Maple Local Fonts requires WordPress 6.5 or higher for Font Library support."
|
||||
msgstr "Maple Local Fonts는 글꼴 라이브러리 지원을 위해 WordPress 6.5 이상이 필요합니다."
|
||||
|
||||
msgid "Plugin Activation Error"
|
||||
msgstr "플러그인 활성화 오류"
|
||||
|
||||
msgid "Maple Local Fonts has been deactivated. It requires WordPress 6.5 or higher."
|
||||
msgstr "Maple Local Fonts가 비활성화되었습니다. WordPress 6.5 이상이 필요합니다."
|
||||
|
||||
msgid "Local Fonts"
|
||||
msgstr "로컬 글꼴"
|
||||
|
||||
msgid "Downloading..."
|
||||
msgstr "다운로드 중..."
|
||||
|
||||
msgid "Deleting..."
|
||||
msgstr "삭제 중..."
|
||||
|
||||
msgid "Are you sure you want to delete this font?"
|
||||
msgstr "이 글꼴을 삭제하시겠습니까?"
|
||||
|
||||
msgid "An error occurred. Please try again."
|
||||
msgstr "오류가 발생했습니다. 다시 시도해 주세요."
|
||||
|
||||
msgid "Please select at least one weight."
|
||||
msgstr "최소 하나의 굵기를 선택해 주세요."
|
||||
|
||||
msgid "Please select at least one style."
|
||||
msgstr "최소 하나의 스타일을 선택해 주세요."
|
||||
|
||||
msgid "Please enter a font name."
|
||||
msgstr "글꼴 이름을 입력해 주세요."
|
||||
|
||||
msgid "Security check failed."
|
||||
msgstr "보안 검사 실패."
|
||||
|
||||
msgid "Unauthorized."
|
||||
msgstr "권한이 없습니다."
|
||||
|
||||
msgid "Font name is required."
|
||||
msgstr "글꼴 이름은 필수입니다."
|
||||
|
||||
msgid "Invalid font name: only letters, numbers, spaces, and hyphens allowed."
|
||||
msgstr "잘못된 글꼴 이름: 문자, 숫자, 공백 및 하이픈만 허용됩니다."
|
||||
|
||||
msgid "Font name is too long."
|
||||
msgstr "글꼴 이름이 너무 깁니다."
|
||||
|
||||
msgid "At least one weight is required."
|
||||
msgstr "최소 하나의 굵기가 필요합니다."
|
||||
|
||||
msgid "Too many weights selected."
|
||||
msgstr "너무 많은 굵기가 선택되었습니다."
|
||||
|
||||
msgid "At least one style is required."
|
||||
msgstr "최소 하나의 스타일이 필요합니다."
|
||||
|
||||
msgid "Successfully installed %s."
|
||||
msgstr "%s이(가) 성공적으로 설치되었습니다."
|
||||
|
||||
msgid "An unexpected error occurred."
|
||||
msgstr "예기치 않은 오류가 발생했습니다."
|
||||
|
||||
msgid "Invalid font ID."
|
||||
msgstr "잘못된 글꼴 ID."
|
||||
|
||||
msgid "Font not found."
|
||||
msgstr "글꼴을 찾을 수 없습니다."
|
||||
|
||||
msgid "Cannot delete fonts not imported by this plugin."
|
||||
msgstr "이 플러그인으로 가져오지 않은 글꼴은 삭제할 수 없습니다."
|
||||
|
||||
msgid "Font deleted successfully."
|
||||
msgstr "글꼴이 성공적으로 삭제되었습니다."
|
||||
|
||||
msgid "Font not found on Google Fonts. Please check the spelling and try again."
|
||||
msgstr "Google Fonts에서 글꼴을 찾을 수 없습니다. 철자를 확인하고 다시 시도해 주세요."
|
||||
|
||||
msgid "This font is already installed."
|
||||
msgstr "이 글꼴은 이미 설치되어 있습니다."
|
||||
|
||||
msgid "Could not connect to Google Fonts. Please check your internet connection and try again."
|
||||
msgstr "Google Fonts에 연결할 수 없습니다. 인터넷 연결을 확인하고 다시 시도해 주세요."
|
||||
|
||||
msgid "Google Fonts returned an error. Please try again later."
|
||||
msgstr "Google Fonts에서 오류가 반환되었습니다. 나중에 다시 시도해 주세요."
|
||||
|
||||
msgid "Could not process the font data. The font may not be available."
|
||||
msgstr "글꼴 데이터를 처리할 수 없습니다. 글꼴을 사용할 수 없을 수 있습니다."
|
||||
|
||||
msgid "Could not download the font files. Please try again."
|
||||
msgstr "글꼴 파일을 다운로드할 수 없습니다. 다시 시도해 주세요."
|
||||
|
||||
msgid "Could not save font files. Please check that wp-content/fonts is writable."
|
||||
msgstr "글꼴 파일을 저장할 수 없습니다. wp-content/fonts가 쓰기 가능한지 확인해 주세요."
|
||||
|
||||
msgid "Could not create fonts directory. Please check file permissions."
|
||||
msgstr "글꼴 디렉토리를 생성할 수 없습니다. 파일 권한을 확인해 주세요."
|
||||
|
||||
msgid "Invalid file path."
|
||||
msgstr "잘못된 파일 경로."
|
||||
|
||||
msgid "Invalid font URL."
|
||||
msgstr "잘못된 글꼴 URL."
|
||||
|
||||
msgid "Invalid font name."
|
||||
msgstr "잘못된 글꼴 이름."
|
||||
|
||||
msgid "No valid weights specified."
|
||||
msgstr "유효한 굵기가 지정되지 않았습니다."
|
||||
|
||||
msgid "No valid styles specified."
|
||||
msgstr "유효한 스타일이 지정되지 않았습니다."
|
||||
|
||||
msgid "An unexpected error occurred. Please try again."
|
||||
msgstr "예기치 않은 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
|
||||
msgid "Thin"
|
||||
msgstr "가늘게"
|
||||
|
||||
msgid "Extra Light"
|
||||
msgstr "매우 가볍게"
|
||||
|
||||
msgid "Light"
|
||||
msgstr "가볍게"
|
||||
|
||||
msgid "Regular"
|
||||
msgstr "보통"
|
||||
|
||||
msgid "Medium"
|
||||
msgstr "중간"
|
||||
|
||||
msgid "Semi Bold"
|
||||
msgstr "약간 굵게"
|
||||
|
||||
msgid "Bold"
|
||||
msgstr "굵게"
|
||||
|
||||
msgid "Extra Bold"
|
||||
msgstr "매우 굵게"
|
||||
|
||||
msgid "Black"
|
||||
msgstr "검정"
|
||||
|
||||
msgid "You do not have sufficient permissions to access this page."
|
||||
msgstr "이 페이지에 접근할 권한이 없습니다."
|
||||
|
||||
msgid "Import from Google Fonts"
|
||||
msgstr "Google Fonts에서 가져오기"
|
||||
|
||||
msgid "Font Name"
|
||||
msgstr "글꼴 이름"
|
||||
|
||||
msgid "e.g., Open Sans"
|
||||
msgstr "예: Open Sans"
|
||||
|
||||
msgid "Enter the exact font name as it appears on Google Fonts."
|
||||
msgstr "Google Fonts에 표시된 정확한 글꼴 이름을 입력하세요."
|
||||
|
||||
msgid "Weights"
|
||||
msgstr "굵기"
|
||||
|
||||
msgid "Styles"
|
||||
msgstr "스타일"
|
||||
|
||||
msgid "Normal"
|
||||
msgstr "보통"
|
||||
|
||||
msgid "Italic"
|
||||
msgstr "기울임꼴"
|
||||
|
||||
msgid "Files to download:"
|
||||
msgstr "다운로드할 파일:"
|
||||
|
||||
msgid "Download & Install"
|
||||
msgstr "다운로드 및 설치"
|
||||
|
||||
msgid "Installed Fonts"
|
||||
msgstr "설치된 글꼴"
|
||||
|
||||
msgid "No fonts installed yet."
|
||||
msgstr "아직 설치된 글꼴이 없습니다."
|
||||
|
||||
msgid "Delete"
|
||||
msgstr "삭제"
|
||||
|
||||
msgid "Use %s to apply fonts to your site."
|
||||
msgstr "%s을(를) 사용하여 사이트에 글꼴을 적용하세요."
|
||||
|
||||
msgid "Appearance → Editor → Styles → Typography"
|
||||
msgstr "외모 → 편집기 → 스타일 → 타이포그래피"
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
# Translation of Maple Local Fonts in Dutch
|
||||
# Copyright (C) 2024 Maple Open Technologies
|
||||
# This file is distributed under the GPL-2.0-or-later.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Maple Local Fonts 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: https://mapleopentech.org/\n"
|
||||
"Last-Translator: Maple Open Technologies\n"
|
||||
"Language-Team: Dutch\n"
|
||||
"Language: nl_NL\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"POT-Creation-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"PO-Revision-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"X-Generator: Manual\n"
|
||||
"X-Domain: maple-local-fonts\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. Plugin Name of the plugin
|
||||
msgid "Maple Local Fonts"
|
||||
msgstr "Maple Local Fonts"
|
||||
|
||||
#. Description of the plugin
|
||||
msgid "Import Google Fonts to local storage and register them with WordPress Font Library for GDPR-compliant, privacy-friendly typography."
|
||||
msgstr "Importeer Google Fonts naar lokale opslag en registreer ze bij de WordPress Lettertypebibliotheek voor GDPR-conforme, privacyvriendelijke typografie."
|
||||
|
||||
#. Author of the plugin
|
||||
msgid "Maple Open Technologies"
|
||||
msgstr "Maple Open Technologies"
|
||||
|
||||
msgid "Maple Local Fonts requires WordPress 6.5 or higher for Font Library support."
|
||||
msgstr "Maple Local Fonts vereist WordPress 6.5 of hoger voor ondersteuning van de Lettertypebibliotheek."
|
||||
|
||||
msgid "Plugin Activation Error"
|
||||
msgstr "Plugin-activeringsfout"
|
||||
|
||||
msgid "Maple Local Fonts has been deactivated. It requires WordPress 6.5 or higher."
|
||||
msgstr "Maple Local Fonts is gedeactiveerd. Het vereist WordPress 6.5 of hoger."
|
||||
|
||||
msgid "Local Fonts"
|
||||
msgstr "Lokale lettertypen"
|
||||
|
||||
msgid "Downloading..."
|
||||
msgstr "Downloaden..."
|
||||
|
||||
msgid "Deleting..."
|
||||
msgstr "Verwijderen..."
|
||||
|
||||
msgid "Are you sure you want to delete this font?"
|
||||
msgstr "Weet je zeker dat je dit lettertype wilt verwijderen?"
|
||||
|
||||
msgid "An error occurred. Please try again."
|
||||
msgstr "Er is een fout opgetreden. Probeer het opnieuw."
|
||||
|
||||
msgid "Please select at least one weight."
|
||||
msgstr "Selecteer ten minste één gewicht."
|
||||
|
||||
msgid "Please select at least one style."
|
||||
msgstr "Selecteer ten minste één stijl."
|
||||
|
||||
msgid "Please enter a font name."
|
||||
msgstr "Voer een lettertypenaam in."
|
||||
|
||||
msgid "Security check failed."
|
||||
msgstr "Beveiligingscontrole mislukt."
|
||||
|
||||
msgid "Unauthorized."
|
||||
msgstr "Niet geautoriseerd."
|
||||
|
||||
msgid "Font name is required."
|
||||
msgstr "Lettertypenaam is vereist."
|
||||
|
||||
msgid "Invalid font name: only letters, numbers, spaces, and hyphens allowed."
|
||||
msgstr "Ongeldige lettertypenaam: alleen letters, cijfers, spaties en koppeltekens toegestaan."
|
||||
|
||||
msgid "Font name is too long."
|
||||
msgstr "Lettertypenaam is te lang."
|
||||
|
||||
msgid "At least one weight is required."
|
||||
msgstr "Ten minste één gewicht is vereist."
|
||||
|
||||
msgid "Too many weights selected."
|
||||
msgstr "Te veel gewichten geselecteerd."
|
||||
|
||||
msgid "At least one style is required."
|
||||
msgstr "Ten minste één stijl is vereist."
|
||||
|
||||
msgid "Successfully installed %s."
|
||||
msgstr "%s is succesvol geïnstalleerd."
|
||||
|
||||
msgid "An unexpected error occurred."
|
||||
msgstr "Er is een onverwachte fout opgetreden."
|
||||
|
||||
msgid "Invalid font ID."
|
||||
msgstr "Ongeldige lettertype-ID."
|
||||
|
||||
msgid "Font not found."
|
||||
msgstr "Lettertype niet gevonden."
|
||||
|
||||
msgid "Cannot delete fonts not imported by this plugin."
|
||||
msgstr "Kan lettertypen die niet door deze plugin zijn geïmporteerd niet verwijderen."
|
||||
|
||||
msgid "Font deleted successfully."
|
||||
msgstr "Lettertype succesvol verwijderd."
|
||||
|
||||
msgid "Font not found on Google Fonts. Please check the spelling and try again."
|
||||
msgstr "Lettertype niet gevonden op Google Fonts. Controleer de spelling en probeer het opnieuw."
|
||||
|
||||
msgid "This font is already installed."
|
||||
msgstr "Dit lettertype is al geïnstalleerd."
|
||||
|
||||
msgid "Could not connect to Google Fonts. Please check your internet connection and try again."
|
||||
msgstr "Kan geen verbinding maken met Google Fonts. Controleer je internetverbinding en probeer het opnieuw."
|
||||
|
||||
msgid "Google Fonts returned an error. Please try again later."
|
||||
msgstr "Google Fonts heeft een fout geretourneerd. Probeer het later opnieuw."
|
||||
|
||||
msgid "Could not process the font data. The font may not be available."
|
||||
msgstr "Kan de lettertypegegevens niet verwerken. Het lettertype is mogelijk niet beschikbaar."
|
||||
|
||||
msgid "Could not download the font files. Please try again."
|
||||
msgstr "Kan de lettertypebestanden niet downloaden. Probeer het opnieuw."
|
||||
|
||||
msgid "Could not save font files. Please check that wp-content/fonts is writable."
|
||||
msgstr "Kan lettertypebestanden niet opslaan. Controleer of wp-content/fonts schrijfbaar is."
|
||||
|
||||
msgid "Could not create fonts directory. Please check file permissions."
|
||||
msgstr "Kan lettertypemap niet aanmaken. Controleer de bestandsrechten."
|
||||
|
||||
msgid "Invalid file path."
|
||||
msgstr "Ongeldig bestandspad."
|
||||
|
||||
msgid "Invalid font URL."
|
||||
msgstr "Ongeldige lettertype-URL."
|
||||
|
||||
msgid "Invalid font name."
|
||||
msgstr "Ongeldige lettertypenaam."
|
||||
|
||||
msgid "No valid weights specified."
|
||||
msgstr "Geen geldige gewichten opgegeven."
|
||||
|
||||
msgid "No valid styles specified."
|
||||
msgstr "Geen geldige stijlen opgegeven."
|
||||
|
||||
msgid "An unexpected error occurred. Please try again."
|
||||
msgstr "Er is een onverwachte fout opgetreden. Probeer het opnieuw."
|
||||
|
||||
msgid "Thin"
|
||||
msgstr "Dun"
|
||||
|
||||
msgid "Extra Light"
|
||||
msgstr "Extra licht"
|
||||
|
||||
msgid "Light"
|
||||
msgstr "Licht"
|
||||
|
||||
msgid "Regular"
|
||||
msgstr "Normaal"
|
||||
|
||||
msgid "Medium"
|
||||
msgstr "Medium"
|
||||
|
||||
msgid "Semi Bold"
|
||||
msgstr "Halfvet"
|
||||
|
||||
msgid "Bold"
|
||||
msgstr "Vet"
|
||||
|
||||
msgid "Extra Bold"
|
||||
msgstr "Extra vet"
|
||||
|
||||
msgid "Black"
|
||||
msgstr "Zwart"
|
||||
|
||||
msgid "You do not have sufficient permissions to access this page."
|
||||
msgstr "Je hebt onvoldoende rechten om deze pagina te openen."
|
||||
|
||||
msgid "Import from Google Fonts"
|
||||
msgstr "Importeren van Google Fonts"
|
||||
|
||||
msgid "Font Name"
|
||||
msgstr "Lettertypenaam"
|
||||
|
||||
msgid "e.g., Open Sans"
|
||||
msgstr "bijv. Open Sans"
|
||||
|
||||
msgid "Enter the exact font name as it appears on Google Fonts."
|
||||
msgstr "Voer de exacte lettertypenaam in zoals deze wordt weergegeven op Google Fonts."
|
||||
|
||||
msgid "Weights"
|
||||
msgstr "Gewichten"
|
||||
|
||||
msgid "Styles"
|
||||
msgstr "Stijlen"
|
||||
|
||||
msgid "Normal"
|
||||
msgstr "Normaal"
|
||||
|
||||
msgid "Italic"
|
||||
msgstr "Cursief"
|
||||
|
||||
msgid "Files to download:"
|
||||
msgstr "Te downloaden bestanden:"
|
||||
|
||||
msgid "Download & Install"
|
||||
msgstr "Downloaden en installeren"
|
||||
|
||||
msgid "Installed Fonts"
|
||||
msgstr "Geïnstalleerde lettertypen"
|
||||
|
||||
msgid "No fonts installed yet."
|
||||
msgstr "Nog geen lettertypen geïnstalleerd."
|
||||
|
||||
msgid "Delete"
|
||||
msgstr "Verwijderen"
|
||||
|
||||
msgid "Use %s to apply fonts to your site."
|
||||
msgstr "Gebruik %s om lettertypen op je site toe te passen."
|
||||
|
||||
msgid "Appearance → Editor → Styles → Typography"
|
||||
msgstr "Weergave → Editor → Stijlen → Typografie"
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
# Translation of Maple Local Fonts in Polish
|
||||
# Copyright (C) 2024 Maple Open Technologies
|
||||
# This file is distributed under the GPL-2.0-or-later.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Maple Local Fonts 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: https://mapleopentech.org/\n"
|
||||
"Last-Translator: Maple Open Technologies\n"
|
||||
"Language-Team: Polish\n"
|
||||
"Language: pl_PL\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"POT-Creation-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"PO-Revision-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"X-Generator: Manual\n"
|
||||
"X-Domain: maple-local-fonts\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n"
|
||||
|
||||
#. Plugin Name of the plugin
|
||||
msgid "Maple Local Fonts"
|
||||
msgstr "Maple Local Fonts"
|
||||
|
||||
#. Description of the plugin
|
||||
msgid "Import Google Fonts to local storage and register them with WordPress Font Library for GDPR-compliant, privacy-friendly typography."
|
||||
msgstr "Importuj czcionki Google Fonts do lokalnego magazynu i zarejestruj je w bibliotece czcionek WordPress dla typografii zgodnej z RODO i przyjaznej dla prywatności."
|
||||
|
||||
#. Author of the plugin
|
||||
msgid "Maple Open Technologies"
|
||||
msgstr "Maple Open Technologies"
|
||||
|
||||
msgid "Maple Local Fonts requires WordPress 6.5 or higher for Font Library support."
|
||||
msgstr "Maple Local Fonts wymaga WordPress 6.5 lub nowszego do obsługi biblioteki czcionek."
|
||||
|
||||
msgid "Plugin Activation Error"
|
||||
msgstr "Błąd aktywacji wtyczki"
|
||||
|
||||
msgid "Maple Local Fonts has been deactivated. It requires WordPress 6.5 or higher."
|
||||
msgstr "Maple Local Fonts została dezaktywowana. Wymaga WordPress 6.5 lub nowszego."
|
||||
|
||||
msgid "Local Fonts"
|
||||
msgstr "Lokalne czcionki"
|
||||
|
||||
msgid "Downloading..."
|
||||
msgstr "Pobieranie..."
|
||||
|
||||
msgid "Deleting..."
|
||||
msgstr "Usuwanie..."
|
||||
|
||||
msgid "Are you sure you want to delete this font?"
|
||||
msgstr "Czy na pewno chcesz usunąć tę czcionkę?"
|
||||
|
||||
msgid "An error occurred. Please try again."
|
||||
msgstr "Wystąpił błąd. Spróbuj ponownie."
|
||||
|
||||
msgid "Please select at least one weight."
|
||||
msgstr "Wybierz co najmniej jedną grubość."
|
||||
|
||||
msgid "Please select at least one style."
|
||||
msgstr "Wybierz co najmniej jeden styl."
|
||||
|
||||
msgid "Please enter a font name."
|
||||
msgstr "Wprowadź nazwę czcionki."
|
||||
|
||||
msgid "Security check failed."
|
||||
msgstr "Sprawdzenie zabezpieczeń nie powiodło się."
|
||||
|
||||
msgid "Unauthorized."
|
||||
msgstr "Brak autoryzacji."
|
||||
|
||||
msgid "Font name is required."
|
||||
msgstr "Nazwa czcionki jest wymagana."
|
||||
|
||||
msgid "Invalid font name: only letters, numbers, spaces, and hyphens allowed."
|
||||
msgstr "Nieprawidłowa nazwa czcionki: dozwolone są tylko litery, cyfry, spacje i myślniki."
|
||||
|
||||
msgid "Font name is too long."
|
||||
msgstr "Nazwa czcionki jest za długa."
|
||||
|
||||
msgid "At least one weight is required."
|
||||
msgstr "Wymagana jest co najmniej jedna grubość."
|
||||
|
||||
msgid "Too many weights selected."
|
||||
msgstr "Wybrano za dużo grubości."
|
||||
|
||||
msgid "At least one style is required."
|
||||
msgstr "Wymagany jest co najmniej jeden styl."
|
||||
|
||||
msgid "Successfully installed %s."
|
||||
msgstr "Pomyślnie zainstalowano %s."
|
||||
|
||||
msgid "An unexpected error occurred."
|
||||
msgstr "Wystąpił nieoczekiwany błąd."
|
||||
|
||||
msgid "Invalid font ID."
|
||||
msgstr "Nieprawidłowy identyfikator czcionki."
|
||||
|
||||
msgid "Font not found."
|
||||
msgstr "Czcionka nie została znaleziona."
|
||||
|
||||
msgid "Cannot delete fonts not imported by this plugin."
|
||||
msgstr "Nie można usunąć czcionek, które nie zostały zaimportowane przez tę wtyczkę."
|
||||
|
||||
msgid "Font deleted successfully."
|
||||
msgstr "Czcionka została pomyślnie usunięta."
|
||||
|
||||
msgid "Font not found on Google Fonts. Please check the spelling and try again."
|
||||
msgstr "Czcionka nie została znaleziona w Google Fonts. Sprawdź pisownię i spróbuj ponownie."
|
||||
|
||||
msgid "This font is already installed."
|
||||
msgstr "Ta czcionka jest już zainstalowana."
|
||||
|
||||
msgid "Could not connect to Google Fonts. Please check your internet connection and try again."
|
||||
msgstr "Nie można połączyć się z Google Fonts. Sprawdź połączenie internetowe i spróbuj ponownie."
|
||||
|
||||
msgid "Google Fonts returned an error. Please try again later."
|
||||
msgstr "Google Fonts zwróciło błąd. Spróbuj ponownie później."
|
||||
|
||||
msgid "Could not process the font data. The font may not be available."
|
||||
msgstr "Nie można przetworzyć danych czcionki. Czcionka może być niedostępna."
|
||||
|
||||
msgid "Could not download the font files. Please try again."
|
||||
msgstr "Nie można pobrać plików czcionki. Spróbuj ponownie."
|
||||
|
||||
msgid "Could not save font files. Please check that wp-content/fonts is writable."
|
||||
msgstr "Nie można zapisać plików czcionki. Sprawdź, czy wp-content/fonts jest zapisywalny."
|
||||
|
||||
msgid "Could not create fonts directory. Please check file permissions."
|
||||
msgstr "Nie można utworzyć katalogu czcionek. Sprawdź uprawnienia plików."
|
||||
|
||||
msgid "Invalid file path."
|
||||
msgstr "Nieprawidłowa ścieżka pliku."
|
||||
|
||||
msgid "Invalid font URL."
|
||||
msgstr "Nieprawidłowy adres URL czcionki."
|
||||
|
||||
msgid "Invalid font name."
|
||||
msgstr "Nieprawidłowa nazwa czcionki."
|
||||
|
||||
msgid "No valid weights specified."
|
||||
msgstr "Nie określono prawidłowych grubości."
|
||||
|
||||
msgid "No valid styles specified."
|
||||
msgstr "Nie określono prawidłowych stylów."
|
||||
|
||||
msgid "An unexpected error occurred. Please try again."
|
||||
msgstr "Wystąpił nieoczekiwany błąd. Spróbuj ponownie."
|
||||
|
||||
msgid "Thin"
|
||||
msgstr "Cienka"
|
||||
|
||||
msgid "Extra Light"
|
||||
msgstr "Bardzo lekka"
|
||||
|
||||
msgid "Light"
|
||||
msgstr "Lekka"
|
||||
|
||||
msgid "Regular"
|
||||
msgstr "Normalna"
|
||||
|
||||
msgid "Medium"
|
||||
msgstr "Średnia"
|
||||
|
||||
msgid "Semi Bold"
|
||||
msgstr "Półgruba"
|
||||
|
||||
msgid "Bold"
|
||||
msgstr "Gruba"
|
||||
|
||||
msgid "Extra Bold"
|
||||
msgstr "Bardzo gruba"
|
||||
|
||||
msgid "Black"
|
||||
msgstr "Czarna"
|
||||
|
||||
msgid "You do not have sufficient permissions to access this page."
|
||||
msgstr "Nie masz wystarczających uprawnień, aby uzyskać dostęp do tej strony."
|
||||
|
||||
msgid "Import from Google Fonts"
|
||||
msgstr "Importuj z Google Fonts"
|
||||
|
||||
msgid "Font Name"
|
||||
msgstr "Nazwa czcionki"
|
||||
|
||||
msgid "e.g., Open Sans"
|
||||
msgstr "np. Open Sans"
|
||||
|
||||
msgid "Enter the exact font name as it appears on Google Fonts."
|
||||
msgstr "Wprowadź dokładną nazwę czcionki, jak jest wyświetlana w Google Fonts."
|
||||
|
||||
msgid "Weights"
|
||||
msgstr "Grubości"
|
||||
|
||||
msgid "Styles"
|
||||
msgstr "Style"
|
||||
|
||||
msgid "Normal"
|
||||
msgstr "Normalna"
|
||||
|
||||
msgid "Italic"
|
||||
msgstr "Kursywa"
|
||||
|
||||
msgid "Files to download:"
|
||||
msgstr "Pliki do pobrania:"
|
||||
|
||||
msgid "Download & Install"
|
||||
msgstr "Pobierz i zainstaluj"
|
||||
|
||||
msgid "Installed Fonts"
|
||||
msgstr "Zainstalowane czcionki"
|
||||
|
||||
msgid "No fonts installed yet."
|
||||
msgstr "Nie zainstalowano jeszcze żadnych czcionek."
|
||||
|
||||
msgid "Delete"
|
||||
msgstr "Usuń"
|
||||
|
||||
msgid "Use %s to apply fonts to your site."
|
||||
msgstr "Użyj %s, aby zastosować czcionki na swojej stronie."
|
||||
|
||||
msgid "Appearance → Editor → Styles → Typography"
|
||||
msgstr "Wygląd → Edytor → Style → Typografia"
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
# Translation of Maple Local Fonts in Portuguese (Brazil)
|
||||
# Copyright (C) 2024 Maple Open Technologies
|
||||
# This file is distributed under the GPL-2.0-or-later.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Maple Local Fonts 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: https://mapleopentech.org/\n"
|
||||
"Last-Translator: Maple Open Technologies\n"
|
||||
"Language-Team: Portuguese (Brazil)\n"
|
||||
"Language: pt_BR\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"POT-Creation-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"PO-Revision-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"X-Generator: Manual\n"
|
||||
"X-Domain: maple-local-fonts\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#. Plugin Name of the plugin
|
||||
msgid "Maple Local Fonts"
|
||||
msgstr "Maple Local Fonts"
|
||||
|
||||
#. Description of the plugin
|
||||
msgid "Import Google Fonts to local storage and register them with WordPress Font Library for GDPR-compliant, privacy-friendly typography."
|
||||
msgstr "Importe fontes do Google Fonts para o armazenamento local e registre-as na biblioteca de fontes do WordPress para uma tipografia compatível com GDPR e que respeita a privacidade."
|
||||
|
||||
#. Author of the plugin
|
||||
msgid "Maple Open Technologies"
|
||||
msgstr "Maple Open Technologies"
|
||||
|
||||
msgid "Maple Local Fonts requires WordPress 6.5 or higher for Font Library support."
|
||||
msgstr "O Maple Local Fonts requer WordPress 6.5 ou superior para suporte à biblioteca de fontes."
|
||||
|
||||
msgid "Plugin Activation Error"
|
||||
msgstr "Erro de ativação do plugin"
|
||||
|
||||
msgid "Maple Local Fonts has been deactivated. It requires WordPress 6.5 or higher."
|
||||
msgstr "O Maple Local Fonts foi desativado. Requer WordPress 6.5 ou superior."
|
||||
|
||||
msgid "Local Fonts"
|
||||
msgstr "Fontes locais"
|
||||
|
||||
msgid "Downloading..."
|
||||
msgstr "Baixando..."
|
||||
|
||||
msgid "Deleting..."
|
||||
msgstr "Excluindo..."
|
||||
|
||||
msgid "Are you sure you want to delete this font?"
|
||||
msgstr "Tem certeza de que deseja excluir esta fonte?"
|
||||
|
||||
msgid "An error occurred. Please try again."
|
||||
msgstr "Ocorreu um erro. Por favor, tente novamente."
|
||||
|
||||
msgid "Please select at least one weight."
|
||||
msgstr "Por favor, selecione pelo menos um peso."
|
||||
|
||||
msgid "Please select at least one style."
|
||||
msgstr "Por favor, selecione pelo menos um estilo."
|
||||
|
||||
msgid "Please enter a font name."
|
||||
msgstr "Por favor, digite um nome de fonte."
|
||||
|
||||
msgid "Security check failed."
|
||||
msgstr "Falha na verificação de segurança."
|
||||
|
||||
msgid "Unauthorized."
|
||||
msgstr "Não autorizado."
|
||||
|
||||
msgid "Font name is required."
|
||||
msgstr "O nome da fonte é obrigatório."
|
||||
|
||||
msgid "Invalid font name: only letters, numbers, spaces, and hyphens allowed."
|
||||
msgstr "Nome de fonte inválido: apenas letras, números, espaços e hífens são permitidos."
|
||||
|
||||
msgid "Font name is too long."
|
||||
msgstr "O nome da fonte é muito longo."
|
||||
|
||||
msgid "At least one weight is required."
|
||||
msgstr "Pelo menos um peso é necessário."
|
||||
|
||||
msgid "Too many weights selected."
|
||||
msgstr "Muitos pesos selecionados."
|
||||
|
||||
msgid "At least one style is required."
|
||||
msgstr "Pelo menos um estilo é necessário."
|
||||
|
||||
msgid "Successfully installed %s."
|
||||
msgstr "%s foi instalado com sucesso."
|
||||
|
||||
msgid "An unexpected error occurred."
|
||||
msgstr "Ocorreu um erro inesperado."
|
||||
|
||||
msgid "Invalid font ID."
|
||||
msgstr "ID de fonte inválido."
|
||||
|
||||
msgid "Font not found."
|
||||
msgstr "Fonte não encontrada."
|
||||
|
||||
msgid "Cannot delete fonts not imported by this plugin."
|
||||
msgstr "Não é possível excluir fontes que não foram importadas por este plugin."
|
||||
|
||||
msgid "Font deleted successfully."
|
||||
msgstr "Fonte excluída com sucesso."
|
||||
|
||||
msgid "Font not found on Google Fonts. Please check the spelling and try again."
|
||||
msgstr "Fonte não encontrada no Google Fonts. Verifique a ortografia e tente novamente."
|
||||
|
||||
msgid "This font is already installed."
|
||||
msgstr "Esta fonte já está instalada."
|
||||
|
||||
msgid "Could not connect to Google Fonts. Please check your internet connection and try again."
|
||||
msgstr "Não foi possível conectar ao Google Fonts. Verifique sua conexão com a internet e tente novamente."
|
||||
|
||||
msgid "Google Fonts returned an error. Please try again later."
|
||||
msgstr "O Google Fonts retornou um erro. Por favor, tente novamente mais tarde."
|
||||
|
||||
msgid "Could not process the font data. The font may not be available."
|
||||
msgstr "Não foi possível processar os dados da fonte. A fonte pode não estar disponível."
|
||||
|
||||
msgid "Could not download the font files. Please try again."
|
||||
msgstr "Não foi possível baixar os arquivos da fonte. Por favor, tente novamente."
|
||||
|
||||
msgid "Could not save font files. Please check that wp-content/fonts is writable."
|
||||
msgstr "Não foi possível salvar os arquivos da fonte. Verifique se wp-content/fonts tem permissão de escrita."
|
||||
|
||||
msgid "Could not create fonts directory. Please check file permissions."
|
||||
msgstr "Não foi possível criar o diretório de fontes. Verifique as permissões de arquivo."
|
||||
|
||||
msgid "Invalid file path."
|
||||
msgstr "Caminho de arquivo inválido."
|
||||
|
||||
msgid "Invalid font URL."
|
||||
msgstr "URL de fonte inválida."
|
||||
|
||||
msgid "Invalid font name."
|
||||
msgstr "Nome de fonte inválido."
|
||||
|
||||
msgid "No valid weights specified."
|
||||
msgstr "Nenhum peso válido especificado."
|
||||
|
||||
msgid "No valid styles specified."
|
||||
msgstr "Nenhum estilo válido especificado."
|
||||
|
||||
msgid "An unexpected error occurred. Please try again."
|
||||
msgstr "Ocorreu um erro inesperado. Por favor, tente novamente."
|
||||
|
||||
msgid "Thin"
|
||||
msgstr "Fina"
|
||||
|
||||
msgid "Extra Light"
|
||||
msgstr "Extra leve"
|
||||
|
||||
msgid "Light"
|
||||
msgstr "Leve"
|
||||
|
||||
msgid "Regular"
|
||||
msgstr "Normal"
|
||||
|
||||
msgid "Medium"
|
||||
msgstr "Média"
|
||||
|
||||
msgid "Semi Bold"
|
||||
msgstr "Semi negrito"
|
||||
|
||||
msgid "Bold"
|
||||
msgstr "Negrito"
|
||||
|
||||
msgid "Extra Bold"
|
||||
msgstr "Extra negrito"
|
||||
|
||||
msgid "Black"
|
||||
msgstr "Preta"
|
||||
|
||||
msgid "You do not have sufficient permissions to access this page."
|
||||
msgstr "Você não tem permissões suficientes para acessar esta página."
|
||||
|
||||
msgid "Import from Google Fonts"
|
||||
msgstr "Importar do Google Fonts"
|
||||
|
||||
msgid "Font Name"
|
||||
msgstr "Nome da fonte"
|
||||
|
||||
msgid "e.g., Open Sans"
|
||||
msgstr "ex.: Open Sans"
|
||||
|
||||
msgid "Enter the exact font name as it appears on Google Fonts."
|
||||
msgstr "Digite o nome exato da fonte como aparece no Google Fonts."
|
||||
|
||||
msgid "Weights"
|
||||
msgstr "Pesos"
|
||||
|
||||
msgid "Styles"
|
||||
msgstr "Estilos"
|
||||
|
||||
msgid "Normal"
|
||||
msgstr "Normal"
|
||||
|
||||
msgid "Italic"
|
||||
msgstr "Itálico"
|
||||
|
||||
msgid "Files to download:"
|
||||
msgstr "Arquivos para baixar:"
|
||||
|
||||
msgid "Download & Install"
|
||||
msgstr "Baixar e instalar"
|
||||
|
||||
msgid "Installed Fonts"
|
||||
msgstr "Fontes instaladas"
|
||||
|
||||
msgid "No fonts installed yet."
|
||||
msgstr "Nenhuma fonte instalada ainda."
|
||||
|
||||
msgid "Delete"
|
||||
msgstr "Excluir"
|
||||
|
||||
msgid "Use %s to apply fonts to your site."
|
||||
msgstr "Use %s para aplicar fontes ao seu site."
|
||||
|
||||
msgid "Appearance → Editor → Styles → Typography"
|
||||
msgstr "Aparência → Editor → Estilos → Tipografia"
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
# Translation of Maple Local Fonts in Russian
|
||||
# Copyright (C) 2024 Maple Open Technologies
|
||||
# This file is distributed under the GPL-2.0-or-later.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Maple Local Fonts 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: https://mapleopentech.org/\n"
|
||||
"Last-Translator: Maple Open Technologies\n"
|
||||
"Language-Team: Russian\n"
|
||||
"Language: ru_RU\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"POT-Creation-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"PO-Revision-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"X-Generator: Manual\n"
|
||||
"X-Domain: maple-local-fonts\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n"
|
||||
|
||||
#. Plugin Name of the plugin
|
||||
msgid "Maple Local Fonts"
|
||||
msgstr "Maple Local Fonts"
|
||||
|
||||
#. Description of the plugin
|
||||
msgid "Import Google Fonts to local storage and register them with WordPress Font Library for GDPR-compliant, privacy-friendly typography."
|
||||
msgstr "Импортируйте шрифты Google Fonts в локальное хранилище и зарегистрируйте их в библиотеке шрифтов WordPress для типографики, соответствующей GDPR и защищающей конфиденциальность."
|
||||
|
||||
#. Author of the plugin
|
||||
msgid "Maple Open Technologies"
|
||||
msgstr "Maple Open Technologies"
|
||||
|
||||
msgid "Maple Local Fonts requires WordPress 6.5 or higher for Font Library support."
|
||||
msgstr "Maple Local Fonts требует WordPress 6.5 или выше для поддержки библиотеки шрифтов."
|
||||
|
||||
msgid "Plugin Activation Error"
|
||||
msgstr "Ошибка активации плагина"
|
||||
|
||||
msgid "Maple Local Fonts has been deactivated. It requires WordPress 6.5 or higher."
|
||||
msgstr "Maple Local Fonts был деактивирован. Требуется WordPress 6.5 или выше."
|
||||
|
||||
msgid "Local Fonts"
|
||||
msgstr "Локальные шрифты"
|
||||
|
||||
msgid "Downloading..."
|
||||
msgstr "Загрузка..."
|
||||
|
||||
msgid "Deleting..."
|
||||
msgstr "Удаление..."
|
||||
|
||||
msgid "Are you sure you want to delete this font?"
|
||||
msgstr "Вы уверены, что хотите удалить этот шрифт?"
|
||||
|
||||
msgid "An error occurred. Please try again."
|
||||
msgstr "Произошла ошибка. Пожалуйста, попробуйте снова."
|
||||
|
||||
msgid "Please select at least one weight."
|
||||
msgstr "Пожалуйста, выберите хотя бы одну толщину."
|
||||
|
||||
msgid "Please select at least one style."
|
||||
msgstr "Пожалуйста, выберите хотя бы один стиль."
|
||||
|
||||
msgid "Please enter a font name."
|
||||
msgstr "Пожалуйста, введите название шрифта."
|
||||
|
||||
msgid "Security check failed."
|
||||
msgstr "Проверка безопасности не пройдена."
|
||||
|
||||
msgid "Unauthorized."
|
||||
msgstr "Не авторизован."
|
||||
|
||||
msgid "Font name is required."
|
||||
msgstr "Название шрифта обязательно."
|
||||
|
||||
msgid "Invalid font name: only letters, numbers, spaces, and hyphens allowed."
|
||||
msgstr "Недопустимое название шрифта: разрешены только буквы, цифры, пробелы и дефисы."
|
||||
|
||||
msgid "Font name is too long."
|
||||
msgstr "Название шрифта слишком длинное."
|
||||
|
||||
msgid "At least one weight is required."
|
||||
msgstr "Требуется хотя бы одна толщина."
|
||||
|
||||
msgid "Too many weights selected."
|
||||
msgstr "Выбрано слишком много толщин."
|
||||
|
||||
msgid "At least one style is required."
|
||||
msgstr "Требуется хотя бы один стиль."
|
||||
|
||||
msgid "Successfully installed %s."
|
||||
msgstr "%s успешно установлен."
|
||||
|
||||
msgid "An unexpected error occurred."
|
||||
msgstr "Произошла непредвиденная ошибка."
|
||||
|
||||
msgid "Invalid font ID."
|
||||
msgstr "Недопустимый ID шрифта."
|
||||
|
||||
msgid "Font not found."
|
||||
msgstr "Шрифт не найден."
|
||||
|
||||
msgid "Cannot delete fonts not imported by this plugin."
|
||||
msgstr "Невозможно удалить шрифты, не импортированные этим плагином."
|
||||
|
||||
msgid "Font deleted successfully."
|
||||
msgstr "Шрифт успешно удалён."
|
||||
|
||||
msgid "Font not found on Google Fonts. Please check the spelling and try again."
|
||||
msgstr "Шрифт не найден в Google Fonts. Проверьте правописание и попробуйте снова."
|
||||
|
||||
msgid "This font is already installed."
|
||||
msgstr "Этот шрифт уже установлен."
|
||||
|
||||
msgid "Could not connect to Google Fonts. Please check your internet connection and try again."
|
||||
msgstr "Не удалось подключиться к Google Fonts. Проверьте подключение к интернету и попробуйте снова."
|
||||
|
||||
msgid "Google Fonts returned an error. Please try again later."
|
||||
msgstr "Google Fonts вернул ошибку. Пожалуйста, попробуйте позже."
|
||||
|
||||
msgid "Could not process the font data. The font may not be available."
|
||||
msgstr "Не удалось обработать данные шрифта. Шрифт может быть недоступен."
|
||||
|
||||
msgid "Could not download the font files. Please try again."
|
||||
msgstr "Не удалось загрузить файлы шрифта. Пожалуйста, попробуйте снова."
|
||||
|
||||
msgid "Could not save font files. Please check that wp-content/fonts is writable."
|
||||
msgstr "Не удалось сохранить файлы шрифта. Проверьте, что wp-content/fonts доступен для записи."
|
||||
|
||||
msgid "Could not create fonts directory. Please check file permissions."
|
||||
msgstr "Не удалось создать каталог шрифтов. Проверьте права доступа к файлам."
|
||||
|
||||
msgid "Invalid file path."
|
||||
msgstr "Недопустимый путь к файлу."
|
||||
|
||||
msgid "Invalid font URL."
|
||||
msgstr "Недопустимый URL шрифта."
|
||||
|
||||
msgid "Invalid font name."
|
||||
msgstr "Недопустимое название шрифта."
|
||||
|
||||
msgid "No valid weights specified."
|
||||
msgstr "Не указаны допустимые толщины."
|
||||
|
||||
msgid "No valid styles specified."
|
||||
msgstr "Не указаны допустимые стили."
|
||||
|
||||
msgid "An unexpected error occurred. Please try again."
|
||||
msgstr "Произошла непредвиденная ошибка. Пожалуйста, попробуйте снова."
|
||||
|
||||
msgid "Thin"
|
||||
msgstr "Тонкий"
|
||||
|
||||
msgid "Extra Light"
|
||||
msgstr "Сверхлёгкий"
|
||||
|
||||
msgid "Light"
|
||||
msgstr "Лёгкий"
|
||||
|
||||
msgid "Regular"
|
||||
msgstr "Обычный"
|
||||
|
||||
msgid "Medium"
|
||||
msgstr "Средний"
|
||||
|
||||
msgid "Semi Bold"
|
||||
msgstr "Полужирный"
|
||||
|
||||
msgid "Bold"
|
||||
msgstr "Жирный"
|
||||
|
||||
msgid "Extra Bold"
|
||||
msgstr "Сверхжирный"
|
||||
|
||||
msgid "Black"
|
||||
msgstr "Чёрный"
|
||||
|
||||
msgid "You do not have sufficient permissions to access this page."
|
||||
msgstr "У вас недостаточно прав для доступа к этой странице."
|
||||
|
||||
msgid "Import from Google Fonts"
|
||||
msgstr "Импорт из Google Fonts"
|
||||
|
||||
msgid "Font Name"
|
||||
msgstr "Название шрифта"
|
||||
|
||||
msgid "e.g., Open Sans"
|
||||
msgstr "напр., Open Sans"
|
||||
|
||||
msgid "Enter the exact font name as it appears on Google Fonts."
|
||||
msgstr "Введите точное название шрифта, как оно отображается в Google Fonts."
|
||||
|
||||
msgid "Weights"
|
||||
msgstr "Толщины"
|
||||
|
||||
msgid "Styles"
|
||||
msgstr "Стили"
|
||||
|
||||
msgid "Normal"
|
||||
msgstr "Обычный"
|
||||
|
||||
msgid "Italic"
|
||||
msgstr "Курсив"
|
||||
|
||||
msgid "Files to download:"
|
||||
msgstr "Файлов для загрузки:"
|
||||
|
||||
msgid "Download & Install"
|
||||
msgstr "Скачать и установить"
|
||||
|
||||
msgid "Installed Fonts"
|
||||
msgstr "Установленные шрифты"
|
||||
|
||||
msgid "No fonts installed yet."
|
||||
msgstr "Шрифты ещё не установлены."
|
||||
|
||||
msgid "Delete"
|
||||
msgstr "Удалить"
|
||||
|
||||
msgid "Use %s to apply fonts to your site."
|
||||
msgstr "Используйте %s для применения шрифтов к вашему сайту."
|
||||
|
||||
msgid "Appearance → Editor → Styles → Typography"
|
||||
msgstr "Внешний вид → Редактор → Стили → Типографика"
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
# Translation of Maple Local Fonts in Turkish
|
||||
# Copyright (C) 2024 Maple Open Technologies
|
||||
# This file is distributed under the GPL-2.0-or-later.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Maple Local Fonts 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: https://mapleopentech.org/\n"
|
||||
"Last-Translator: Maple Open Technologies\n"
|
||||
"Language-Team: Turkish\n"
|
||||
"Language: tr_TR\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"POT-Creation-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"PO-Revision-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"X-Generator: Manual\n"
|
||||
"X-Domain: maple-local-fonts\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#. Plugin Name of the plugin
|
||||
msgid "Maple Local Fonts"
|
||||
msgstr "Maple Local Fonts"
|
||||
|
||||
#. Description of the plugin
|
||||
msgid "Import Google Fonts to local storage and register them with WordPress Font Library for GDPR-compliant, privacy-friendly typography."
|
||||
msgstr "Google Fonts'u yerel depolamaya aktarın ve GDPR uyumlu, gizlilik dostu tipografi için WordPress Yazı Tipi Kitaplığı'na kaydedin."
|
||||
|
||||
#. Author of the plugin
|
||||
msgid "Maple Open Technologies"
|
||||
msgstr "Maple Open Technologies"
|
||||
|
||||
msgid "Maple Local Fonts requires WordPress 6.5 or higher for Font Library support."
|
||||
msgstr "Maple Local Fonts, Yazı Tipi Kitaplığı desteği için WordPress 6.5 veya üstünü gerektirir."
|
||||
|
||||
msgid "Plugin Activation Error"
|
||||
msgstr "Eklenti Etkinleştirme Hatası"
|
||||
|
||||
msgid "Maple Local Fonts has been deactivated. It requires WordPress 6.5 or higher."
|
||||
msgstr "Maple Local Fonts devre dışı bırakıldı. WordPress 6.5 veya üstünü gerektirir."
|
||||
|
||||
msgid "Local Fonts"
|
||||
msgstr "Yerel Yazı Tipleri"
|
||||
|
||||
msgid "Downloading..."
|
||||
msgstr "İndiriliyor..."
|
||||
|
||||
msgid "Deleting..."
|
||||
msgstr "Siliniyor..."
|
||||
|
||||
msgid "Are you sure you want to delete this font?"
|
||||
msgstr "Bu yazı tipini silmek istediğinizden emin misiniz?"
|
||||
|
||||
msgid "An error occurred. Please try again."
|
||||
msgstr "Bir hata oluştu. Lütfen tekrar deneyin."
|
||||
|
||||
msgid "Please select at least one weight."
|
||||
msgstr "Lütfen en az bir kalınlık seçin."
|
||||
|
||||
msgid "Please select at least one style."
|
||||
msgstr "Lütfen en az bir stil seçin."
|
||||
|
||||
msgid "Please enter a font name."
|
||||
msgstr "Lütfen bir yazı tipi adı girin."
|
||||
|
||||
msgid "Security check failed."
|
||||
msgstr "Güvenlik kontrolü başarısız."
|
||||
|
||||
msgid "Unauthorized."
|
||||
msgstr "Yetkisiz."
|
||||
|
||||
msgid "Font name is required."
|
||||
msgstr "Yazı tipi adı gereklidir."
|
||||
|
||||
msgid "Invalid font name: only letters, numbers, spaces, and hyphens allowed."
|
||||
msgstr "Geçersiz yazı tipi adı: yalnızca harfler, sayılar, boşluklar ve tireler kullanılabilir."
|
||||
|
||||
msgid "Font name is too long."
|
||||
msgstr "Yazı tipi adı çok uzun."
|
||||
|
||||
msgid "At least one weight is required."
|
||||
msgstr "En az bir kalınlık gereklidir."
|
||||
|
||||
msgid "Too many weights selected."
|
||||
msgstr "Çok fazla kalınlık seçildi."
|
||||
|
||||
msgid "At least one style is required."
|
||||
msgstr "En az bir stil gereklidir."
|
||||
|
||||
msgid "Successfully installed %s."
|
||||
msgstr "%s başarıyla yüklendi."
|
||||
|
||||
msgid "An unexpected error occurred."
|
||||
msgstr "Beklenmeyen bir hata oluştu."
|
||||
|
||||
msgid "Invalid font ID."
|
||||
msgstr "Geçersiz yazı tipi kimliği."
|
||||
|
||||
msgid "Font not found."
|
||||
msgstr "Yazı tipi bulunamadı."
|
||||
|
||||
msgid "Cannot delete fonts not imported by this plugin."
|
||||
msgstr "Bu eklenti tarafından içe aktarılmayan yazı tipleri silinemez."
|
||||
|
||||
msgid "Font deleted successfully."
|
||||
msgstr "Yazı tipi başarıyla silindi."
|
||||
|
||||
msgid "Font not found on Google Fonts. Please check the spelling and try again."
|
||||
msgstr "Yazı tipi Google Fonts'ta bulunamadı. Lütfen yazımı kontrol edin ve tekrar deneyin."
|
||||
|
||||
msgid "This font is already installed."
|
||||
msgstr "Bu yazı tipi zaten yüklü."
|
||||
|
||||
msgid "Could not connect to Google Fonts. Please check your internet connection and try again."
|
||||
msgstr "Google Fonts'a bağlanılamadı. Lütfen internet bağlantınızı kontrol edin ve tekrar deneyin."
|
||||
|
||||
msgid "Google Fonts returned an error. Please try again later."
|
||||
msgstr "Google Fonts bir hata döndürdü. Lütfen daha sonra tekrar deneyin."
|
||||
|
||||
msgid "Could not process the font data. The font may not be available."
|
||||
msgstr "Yazı tipi verileri işlenemedi. Yazı tipi kullanılamıyor olabilir."
|
||||
|
||||
msgid "Could not download the font files. Please try again."
|
||||
msgstr "Yazı tipi dosyaları indirilemedi. Lütfen tekrar deneyin."
|
||||
|
||||
msgid "Could not save font files. Please check that wp-content/fonts is writable."
|
||||
msgstr "Yazı tipi dosyaları kaydedilemedi. Lütfen wp-content/fonts klasörünün yazılabilir olduğunu kontrol edin."
|
||||
|
||||
msgid "Could not create fonts directory. Please check file permissions."
|
||||
msgstr "Yazı tipi dizini oluşturulamadı. Lütfen dosya izinlerini kontrol edin."
|
||||
|
||||
msgid "Invalid file path."
|
||||
msgstr "Geçersiz dosya yolu."
|
||||
|
||||
msgid "Invalid font URL."
|
||||
msgstr "Geçersiz yazı tipi URL'si."
|
||||
|
||||
msgid "Invalid font name."
|
||||
msgstr "Geçersiz yazı tipi adı."
|
||||
|
||||
msgid "No valid weights specified."
|
||||
msgstr "Geçerli kalınlık belirtilmedi."
|
||||
|
||||
msgid "No valid styles specified."
|
||||
msgstr "Geçerli stil belirtilmedi."
|
||||
|
||||
msgid "An unexpected error occurred. Please try again."
|
||||
msgstr "Beklenmeyen bir hata oluştu. Lütfen tekrar deneyin."
|
||||
|
||||
msgid "Thin"
|
||||
msgstr "İnce"
|
||||
|
||||
msgid "Extra Light"
|
||||
msgstr "Ekstra Hafif"
|
||||
|
||||
msgid "Light"
|
||||
msgstr "Hafif"
|
||||
|
||||
msgid "Regular"
|
||||
msgstr "Normal"
|
||||
|
||||
msgid "Medium"
|
||||
msgstr "Orta"
|
||||
|
||||
msgid "Semi Bold"
|
||||
msgstr "Yarı Kalın"
|
||||
|
||||
msgid "Bold"
|
||||
msgstr "Kalın"
|
||||
|
||||
msgid "Extra Bold"
|
||||
msgstr "Ekstra Kalın"
|
||||
|
||||
msgid "Black"
|
||||
msgstr "Siyah"
|
||||
|
||||
msgid "You do not have sufficient permissions to access this page."
|
||||
msgstr "Bu sayfaya erişmek için yeterli izniniz yok."
|
||||
|
||||
msgid "Import from Google Fonts"
|
||||
msgstr "Google Fonts'tan İçe Aktar"
|
||||
|
||||
msgid "Font Name"
|
||||
msgstr "Yazı Tipi Adı"
|
||||
|
||||
msgid "e.g., Open Sans"
|
||||
msgstr "örn., Open Sans"
|
||||
|
||||
msgid "Enter the exact font name as it appears on Google Fonts."
|
||||
msgstr "Google Fonts'ta göründüğü şekliyle tam yazı tipi adını girin."
|
||||
|
||||
msgid "Weights"
|
||||
msgstr "Kalınlıklar"
|
||||
|
||||
msgid "Styles"
|
||||
msgstr "Stiller"
|
||||
|
||||
msgid "Normal"
|
||||
msgstr "Normal"
|
||||
|
||||
msgid "Italic"
|
||||
msgstr "İtalik"
|
||||
|
||||
msgid "Files to download:"
|
||||
msgstr "İndirilecek dosyalar:"
|
||||
|
||||
msgid "Download & Install"
|
||||
msgstr "İndir ve Yükle"
|
||||
|
||||
msgid "Installed Fonts"
|
||||
msgstr "Yüklü Yazı Tipleri"
|
||||
|
||||
msgid "No fonts installed yet."
|
||||
msgstr "Henüz yazı tipi yüklenmedi."
|
||||
|
||||
msgid "Delete"
|
||||
msgstr "Sil"
|
||||
|
||||
msgid "Use %s to apply fonts to your site."
|
||||
msgstr "Sitenize yazı tipleri uygulamak için %s kullanın."
|
||||
|
||||
msgid "Appearance → Editor → Styles → Typography"
|
||||
msgstr "Görünüm → Düzenleyici → Stiller → Tipografi"
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
# Translation of Maple Local Fonts in Chinese (Simplified)
|
||||
# Copyright (C) 2024 Maple Open Technologies
|
||||
# This file is distributed under the GPL-2.0-or-later.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Maple Local Fonts 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: https://mapleopentech.org/\n"
|
||||
"Last-Translator: Maple Open Technologies\n"
|
||||
"Language-Team: Chinese (Simplified)\n"
|
||||
"Language: zh_CN\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"POT-Creation-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"PO-Revision-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"X-Generator: Manual\n"
|
||||
"X-Domain: maple-local-fonts\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#. Plugin Name of the plugin
|
||||
msgid "Maple Local Fonts"
|
||||
msgstr "Maple Local Fonts"
|
||||
|
||||
#. Description of the plugin
|
||||
msgid "Import Google Fonts to local storage and register them with WordPress Font Library for GDPR-compliant, privacy-friendly typography."
|
||||
msgstr "将 Google Fonts 导入本地存储并在 WordPress 字体库中注册,实现符合 GDPR 且注重隐私的排版。"
|
||||
|
||||
#. Author of the plugin
|
||||
msgid "Maple Open Technologies"
|
||||
msgstr "Maple Open Technologies"
|
||||
|
||||
msgid "Maple Local Fonts requires WordPress 6.5 or higher for Font Library support."
|
||||
msgstr "Maple Local Fonts 需要 WordPress 6.5 或更高版本以支持字体库。"
|
||||
|
||||
msgid "Plugin Activation Error"
|
||||
msgstr "插件激活错误"
|
||||
|
||||
msgid "Maple Local Fonts has been deactivated. It requires WordPress 6.5 or higher."
|
||||
msgstr "Maple Local Fonts 已停用。需要 WordPress 6.5 或更高版本。"
|
||||
|
||||
msgid "Local Fonts"
|
||||
msgstr "本地字体"
|
||||
|
||||
msgid "Downloading..."
|
||||
msgstr "下载中..."
|
||||
|
||||
msgid "Deleting..."
|
||||
msgstr "删除中..."
|
||||
|
||||
msgid "Are you sure you want to delete this font?"
|
||||
msgstr "您确定要删除此字体吗?"
|
||||
|
||||
msgid "An error occurred. Please try again."
|
||||
msgstr "发生错误。请重试。"
|
||||
|
||||
msgid "Please select at least one weight."
|
||||
msgstr "请至少选择一个字重。"
|
||||
|
||||
msgid "Please select at least one style."
|
||||
msgstr "请至少选择一个样式。"
|
||||
|
||||
msgid "Please enter a font name."
|
||||
msgstr "请输入字体名称。"
|
||||
|
||||
msgid "Security check failed."
|
||||
msgstr "安全检查失败。"
|
||||
|
||||
msgid "Unauthorized."
|
||||
msgstr "未授权。"
|
||||
|
||||
msgid "Font name is required."
|
||||
msgstr "字体名称为必填项。"
|
||||
|
||||
msgid "Invalid font name: only letters, numbers, spaces, and hyphens allowed."
|
||||
msgstr "字体名称无效:只允许使用字母、数字、空格和连字符。"
|
||||
|
||||
msgid "Font name is too long."
|
||||
msgstr "字体名称过长。"
|
||||
|
||||
msgid "At least one weight is required."
|
||||
msgstr "至少需要一个字重。"
|
||||
|
||||
msgid "Too many weights selected."
|
||||
msgstr "选择的字重过多。"
|
||||
|
||||
msgid "At least one style is required."
|
||||
msgstr "至少需要一个样式。"
|
||||
|
||||
msgid "Successfully installed %s."
|
||||
msgstr "成功安装 %s。"
|
||||
|
||||
msgid "An unexpected error occurred."
|
||||
msgstr "发生意外错误。"
|
||||
|
||||
msgid "Invalid font ID."
|
||||
msgstr "无效的字体 ID。"
|
||||
|
||||
msgid "Font not found."
|
||||
msgstr "未找到字体。"
|
||||
|
||||
msgid "Cannot delete fonts not imported by this plugin."
|
||||
msgstr "无法删除非此插件导入的字体。"
|
||||
|
||||
msgid "Font deleted successfully."
|
||||
msgstr "字体删除成功。"
|
||||
|
||||
msgid "Font not found on Google Fonts. Please check the spelling and try again."
|
||||
msgstr "在 Google Fonts 上未找到字体。请检查拼写并重试。"
|
||||
|
||||
msgid "This font is already installed."
|
||||
msgstr "此字体已安装。"
|
||||
|
||||
msgid "Could not connect to Google Fonts. Please check your internet connection and try again."
|
||||
msgstr "无法连接到 Google Fonts。请检查您的网络连接并重试。"
|
||||
|
||||
msgid "Google Fonts returned an error. Please try again later."
|
||||
msgstr "Google Fonts 返回错误。请稍后重试。"
|
||||
|
||||
msgid "Could not process the font data. The font may not be available."
|
||||
msgstr "无法处理字体数据。该字体可能不可用。"
|
||||
|
||||
msgid "Could not download the font files. Please try again."
|
||||
msgstr "无法下载字体文件。请重试。"
|
||||
|
||||
msgid "Could not save font files. Please check that wp-content/fonts is writable."
|
||||
msgstr "无法保存字体文件。请检查 wp-content/fonts 是否可写。"
|
||||
|
||||
msgid "Could not create fonts directory. Please check file permissions."
|
||||
msgstr "无法创建字体目录。请检查文件权限。"
|
||||
|
||||
msgid "Invalid file path."
|
||||
msgstr "无效的文件路径。"
|
||||
|
||||
msgid "Invalid font URL."
|
||||
msgstr "无效的字体 URL。"
|
||||
|
||||
msgid "Invalid font name."
|
||||
msgstr "无效的字体名称。"
|
||||
|
||||
msgid "No valid weights specified."
|
||||
msgstr "未指定有效的字重。"
|
||||
|
||||
msgid "No valid styles specified."
|
||||
msgstr "未指定有效的样式。"
|
||||
|
||||
msgid "An unexpected error occurred. Please try again."
|
||||
msgstr "发生意外错误。请重试。"
|
||||
|
||||
msgid "Thin"
|
||||
msgstr "极细"
|
||||
|
||||
msgid "Extra Light"
|
||||
msgstr "特细"
|
||||
|
||||
msgid "Light"
|
||||
msgstr "细体"
|
||||
|
||||
msgid "Regular"
|
||||
msgstr "常规"
|
||||
|
||||
msgid "Medium"
|
||||
msgstr "中等"
|
||||
|
||||
msgid "Semi Bold"
|
||||
msgstr "半粗"
|
||||
|
||||
msgid "Bold"
|
||||
msgstr "粗体"
|
||||
|
||||
msgid "Extra Bold"
|
||||
msgstr "特粗"
|
||||
|
||||
msgid "Black"
|
||||
msgstr "黑体"
|
||||
|
||||
msgid "You do not have sufficient permissions to access this page."
|
||||
msgstr "您没有足够的权限访问此页面。"
|
||||
|
||||
msgid "Import from Google Fonts"
|
||||
msgstr "从 Google Fonts 导入"
|
||||
|
||||
msgid "Font Name"
|
||||
msgstr "字体名称"
|
||||
|
||||
msgid "e.g., Open Sans"
|
||||
msgstr "例如:Open Sans"
|
||||
|
||||
msgid "Enter the exact font name as it appears on Google Fonts."
|
||||
msgstr "输入 Google Fonts 上显示的确切字体名称。"
|
||||
|
||||
msgid "Weights"
|
||||
msgstr "字重"
|
||||
|
||||
msgid "Styles"
|
||||
msgstr "样式"
|
||||
|
||||
msgid "Normal"
|
||||
msgstr "正常"
|
||||
|
||||
msgid "Italic"
|
||||
msgstr "斜体"
|
||||
|
||||
msgid "Files to download:"
|
||||
msgstr "要下载的文件:"
|
||||
|
||||
msgid "Download & Install"
|
||||
msgstr "下载并安装"
|
||||
|
||||
msgid "Installed Fonts"
|
||||
msgstr "已安装的字体"
|
||||
|
||||
msgid "No fonts installed yet."
|
||||
msgstr "尚未安装任何字体。"
|
||||
|
||||
msgid "Delete"
|
||||
msgstr "删除"
|
||||
|
||||
msgid "Use %s to apply fonts to your site."
|
||||
msgstr "使用 %s 将字体应用到您的网站。"
|
||||
|
||||
msgid "Appearance → Editor → Styles → Typography"
|
||||
msgstr "外观 → 编辑器 → 样式 → 排版"
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
# Translation of Maple Local Fonts in Chinese (Traditional)
|
||||
# Copyright (C) 2024 Maple Open Technologies
|
||||
# This file is distributed under the GPL-2.0-or-later.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Maple Local Fonts 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: https://mapleopentech.org/\n"
|
||||
"Last-Translator: Maple Open Technologies\n"
|
||||
"Language-Team: Chinese (Traditional)\n"
|
||||
"Language: zh_TW\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"POT-Creation-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"PO-Revision-Date: 2024-01-01T00:00:00+00:00\n"
|
||||
"X-Generator: Manual\n"
|
||||
"X-Domain: maple-local-fonts\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#. Plugin Name of the plugin
|
||||
msgid "Maple Local Fonts"
|
||||
msgstr "Maple Local Fonts"
|
||||
|
||||
#. Description of the plugin
|
||||
msgid "Import Google Fonts to local storage and register them with WordPress Font Library for GDPR-compliant, privacy-friendly typography."
|
||||
msgstr "將 Google Fonts 匯入本機儲存空間並在 WordPress 字型庫中註冊,實現符合 GDPR 且注重隱私的排版。"
|
||||
|
||||
#. Author of the plugin
|
||||
msgid "Maple Open Technologies"
|
||||
msgstr "Maple Open Technologies"
|
||||
|
||||
msgid "Maple Local Fonts requires WordPress 6.5 or higher for Font Library support."
|
||||
msgstr "Maple Local Fonts 需要 WordPress 6.5 或更高版本以支援字型庫。"
|
||||
|
||||
msgid "Plugin Activation Error"
|
||||
msgstr "外掛啟用錯誤"
|
||||
|
||||
msgid "Maple Local Fonts has been deactivated. It requires WordPress 6.5 or higher."
|
||||
msgstr "Maple Local Fonts 已停用。需要 WordPress 6.5 或更高版本。"
|
||||
|
||||
msgid "Local Fonts"
|
||||
msgstr "本機字型"
|
||||
|
||||
msgid "Downloading..."
|
||||
msgstr "下載中..."
|
||||
|
||||
msgid "Deleting..."
|
||||
msgstr "刪除中..."
|
||||
|
||||
msgid "Are you sure you want to delete this font?"
|
||||
msgstr "您確定要刪除此字型嗎?"
|
||||
|
||||
msgid "An error occurred. Please try again."
|
||||
msgstr "發生錯誤。請重試。"
|
||||
|
||||
msgid "Please select at least one weight."
|
||||
msgstr "請至少選擇一個字重。"
|
||||
|
||||
msgid "Please select at least one style."
|
||||
msgstr "請至少選擇一個樣式。"
|
||||
|
||||
msgid "Please enter a font name."
|
||||
msgstr "請輸入字型名稱。"
|
||||
|
||||
msgid "Security check failed."
|
||||
msgstr "安全性檢查失敗。"
|
||||
|
||||
msgid "Unauthorized."
|
||||
msgstr "未授權。"
|
||||
|
||||
msgid "Font name is required."
|
||||
msgstr "字型名稱為必填項。"
|
||||
|
||||
msgid "Invalid font name: only letters, numbers, spaces, and hyphens allowed."
|
||||
msgstr "字型名稱無效:只允許使用字母、數字、空格和連字號。"
|
||||
|
||||
msgid "Font name is too long."
|
||||
msgstr "字型名稱過長。"
|
||||
|
||||
msgid "At least one weight is required."
|
||||
msgstr "至少需要一個字重。"
|
||||
|
||||
msgid "Too many weights selected."
|
||||
msgstr "選擇的字重過多。"
|
||||
|
||||
msgid "At least one style is required."
|
||||
msgstr "至少需要一個樣式。"
|
||||
|
||||
msgid "Successfully installed %s."
|
||||
msgstr "成功安裝 %s。"
|
||||
|
||||
msgid "An unexpected error occurred."
|
||||
msgstr "發生意外錯誤。"
|
||||
|
||||
msgid "Invalid font ID."
|
||||
msgstr "無效的字型 ID。"
|
||||
|
||||
msgid "Font not found."
|
||||
msgstr "找不到字型。"
|
||||
|
||||
msgid "Cannot delete fonts not imported by this plugin."
|
||||
msgstr "無法刪除非此外掛匯入的字型。"
|
||||
|
||||
msgid "Font deleted successfully."
|
||||
msgstr "字型刪除成功。"
|
||||
|
||||
msgid "Font not found on Google Fonts. Please check the spelling and try again."
|
||||
msgstr "在 Google Fonts 上找不到字型。請檢查拼寫並重試。"
|
||||
|
||||
msgid "This font is already installed."
|
||||
msgstr "此字型已安裝。"
|
||||
|
||||
msgid "Could not connect to Google Fonts. Please check your internet connection and try again."
|
||||
msgstr "無法連線到 Google Fonts。請檢查您的網路連線並重試。"
|
||||
|
||||
msgid "Google Fonts returned an error. Please try again later."
|
||||
msgstr "Google Fonts 傳回錯誤。請稍後重試。"
|
||||
|
||||
msgid "Could not process the font data. The font may not be available."
|
||||
msgstr "無法處理字型資料。該字型可能不可用。"
|
||||
|
||||
msgid "Could not download the font files. Please try again."
|
||||
msgstr "無法下載字型檔案。請重試。"
|
||||
|
||||
msgid "Could not save font files. Please check that wp-content/fonts is writable."
|
||||
msgstr "無法儲存字型檔案。請檢查 wp-content/fonts 是否可寫入。"
|
||||
|
||||
msgid "Could not create fonts directory. Please check file permissions."
|
||||
msgstr "無法建立字型目錄。請檢查檔案權限。"
|
||||
|
||||
msgid "Invalid file path."
|
||||
msgstr "無效的檔案路徑。"
|
||||
|
||||
msgid "Invalid font URL."
|
||||
msgstr "無效的字型 URL。"
|
||||
|
||||
msgid "Invalid font name."
|
||||
msgstr "無效的字型名稱。"
|
||||
|
||||
msgid "No valid weights specified."
|
||||
msgstr "未指定有效的字重。"
|
||||
|
||||
msgid "No valid styles specified."
|
||||
msgstr "未指定有效的樣式。"
|
||||
|
||||
msgid "An unexpected error occurred. Please try again."
|
||||
msgstr "發生意外錯誤。請重試。"
|
||||
|
||||
msgid "Thin"
|
||||
msgstr "極細"
|
||||
|
||||
msgid "Extra Light"
|
||||
msgstr "特細"
|
||||
|
||||
msgid "Light"
|
||||
msgstr "細體"
|
||||
|
||||
msgid "Regular"
|
||||
msgstr "標準"
|
||||
|
||||
msgid "Medium"
|
||||
msgstr "中等"
|
||||
|
||||
msgid "Semi Bold"
|
||||
msgstr "半粗"
|
||||
|
||||
msgid "Bold"
|
||||
msgstr "粗體"
|
||||
|
||||
msgid "Extra Bold"
|
||||
msgstr "特粗"
|
||||
|
||||
msgid "Black"
|
||||
msgstr "黑體"
|
||||
|
||||
msgid "You do not have sufficient permissions to access this page."
|
||||
msgstr "您沒有足夠的權限存取此頁面。"
|
||||
|
||||
msgid "Import from Google Fonts"
|
||||
msgstr "從 Google Fonts 匯入"
|
||||
|
||||
msgid "Font Name"
|
||||
msgstr "字型名稱"
|
||||
|
||||
msgid "e.g., Open Sans"
|
||||
msgstr "例如:Open Sans"
|
||||
|
||||
msgid "Enter the exact font name as it appears on Google Fonts."
|
||||
msgstr "輸入 Google Fonts 上顯示的確切字型名稱。"
|
||||
|
||||
msgid "Weights"
|
||||
msgstr "字重"
|
||||
|
||||
msgid "Styles"
|
||||
msgstr "樣式"
|
||||
|
||||
msgid "Normal"
|
||||
msgstr "正常"
|
||||
|
||||
msgid "Italic"
|
||||
msgstr "斜體"
|
||||
|
||||
msgid "Files to download:"
|
||||
msgstr "要下載的檔案:"
|
||||
|
||||
msgid "Download & Install"
|
||||
msgstr "下載並安裝"
|
||||
|
||||
msgid "Installed Fonts"
|
||||
msgstr "已安裝的字型"
|
||||
|
||||
msgid "No fonts installed yet."
|
||||
msgstr "尚未安裝任何字型。"
|
||||
|
||||
msgid "Delete"
|
||||
msgstr "刪除"
|
||||
|
||||
msgid "Use %s to apply fonts to your site."
|
||||
msgstr "使用 %s 將字型套用到您的網站。"
|
||||
|
||||
msgid "Appearance → Editor → Styles → Typography"
|
||||
msgstr "外觀 → 編輯器 → 樣式 → 排版"
|
||||
216
native/wordpress/maple-fonts-wp/maple-local-fonts.php
Normal file
216
native/wordpress/maple-fonts-wp/maple-local-fonts.php
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
<?php
|
||||
/**
|
||||
* Plugin Name: Maple Local Fonts
|
||||
* Plugin URI: https://mapleopentech.org/plugins/maple-local-fonts
|
||||
* Description: Import Google Fonts to local storage and register them with WordPress Font Library for GDPR-compliant, privacy-friendly typography.
|
||||
* Version: 1.0.0
|
||||
* Requires at least: 6.5
|
||||
* Requires PHP: 7.4
|
||||
* Author: Maple Open Technologies
|
||||
* Author URI: https://mapleopentech.org
|
||||
* License: GPL-2.0-or-later
|
||||
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
||||
* Text Domain: maple-local-fonts
|
||||
* Domain Path: /languages
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Plugin constants
|
||||
define('MLF_VERSION', '1.0.0');
|
||||
define('MLF_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
||||
define('MLF_PLUGIN_URL', plugin_dir_url(__FILE__));
|
||||
define('MLF_PLUGIN_BASENAME', plugin_basename(__FILE__));
|
||||
|
||||
// Limits
|
||||
define('MLF_MAX_FONTS_PER_REQUEST', 10);
|
||||
define('MLF_MAX_WEIGHTS_PER_FONT', 9);
|
||||
define('MLF_REQUEST_TIMEOUT', 30);
|
||||
define('MLF_MAX_CSS_SIZE', 512 * 1024); // 512KB max CSS response
|
||||
define('MLF_MAX_FONT_FILE_SIZE', 5 * 1024 * 1024); // 5MB max font file
|
||||
define('MLF_MAX_FONT_FACES', 20); // Max font faces per import (9 weights × 2 styles + buffer)
|
||||
|
||||
/**
|
||||
* Check WordPress version on activation.
|
||||
*/
|
||||
function mlf_activate() {
|
||||
if (version_compare(get_bloginfo('version'), '6.5', '<')) {
|
||||
deactivate_plugins(plugin_basename(__FILE__));
|
||||
wp_die(
|
||||
esc_html__('Maple Local Fonts requires WordPress 6.5 or higher for Font Library support.', 'maple-local-fonts'),
|
||||
esc_html__('Plugin Activation Error', 'maple-local-fonts'),
|
||||
['back_link' => true]
|
||||
);
|
||||
}
|
||||
|
||||
// Ensure fonts directory exists
|
||||
$font_dir = wp_get_font_dir();
|
||||
if (!file_exists($font_dir['path'])) {
|
||||
wp_mkdir_p($font_dir['path']);
|
||||
}
|
||||
}
|
||||
register_activation_hook(__FILE__, 'mlf_activate');
|
||||
|
||||
/**
|
||||
* Check WordPress version on admin init (in case WP was downgraded).
|
||||
*/
|
||||
function mlf_check_version() {
|
||||
if (version_compare(get_bloginfo('version'), '6.5', '<')) {
|
||||
deactivate_plugins(plugin_basename(__FILE__));
|
||||
add_action('admin_notices', 'mlf_version_notice');
|
||||
}
|
||||
}
|
||||
add_action('admin_init', 'mlf_check_version');
|
||||
|
||||
/**
|
||||
* Display version notice.
|
||||
*/
|
||||
function mlf_version_notice() {
|
||||
echo '<div class="error"><p>';
|
||||
esc_html_e('Maple Local Fonts has been deactivated. It requires WordPress 6.5 or higher.', 'maple-local-fonts');
|
||||
echo '</p></div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare WooCommerce HPOS compatibility.
|
||||
*/
|
||||
function mlf_declare_hpos_compatibility() {
|
||||
if (class_exists(\Automattic\WooCommerce\Utilities\FeaturesUtil::class)) {
|
||||
\Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility(
|
||||
'custom_order_tables',
|
||||
__FILE__,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
add_action('before_woocommerce_init', 'mlf_declare_hpos_compatibility');
|
||||
|
||||
/**
|
||||
* Load plugin text domain.
|
||||
*/
|
||||
function mlf_load_textdomain() {
|
||||
load_plugin_textdomain('maple-local-fonts', false, dirname(MLF_PLUGIN_BASENAME) . '/languages');
|
||||
}
|
||||
add_action('plugins_loaded', 'mlf_load_textdomain');
|
||||
|
||||
/**
|
||||
* Autoload plugin classes.
|
||||
*/
|
||||
function mlf_autoload($class) {
|
||||
$prefix = 'MLF_';
|
||||
if (strpos($class, $prefix) !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$class_name = str_replace($prefix, '', $class);
|
||||
$class_name = str_replace('_', '-', strtolower($class_name));
|
||||
$file = MLF_PLUGIN_DIR . 'includes/class-mlf-' . $class_name . '.php';
|
||||
|
||||
if (file_exists($file)) {
|
||||
require_once $file;
|
||||
}
|
||||
}
|
||||
spl_autoload_register('mlf_autoload');
|
||||
|
||||
/**
|
||||
* Initialize the plugin.
|
||||
*/
|
||||
function mlf_init() {
|
||||
// Only load admin functionality
|
||||
if (is_admin()) {
|
||||
new MLF_Admin_Page();
|
||||
new MLF_Ajax_Handler();
|
||||
}
|
||||
}
|
||||
add_action('plugins_loaded', 'mlf_init', 20);
|
||||
|
||||
/**
|
||||
* Register REST API routes.
|
||||
*/
|
||||
function mlf_register_rest_routes() {
|
||||
$controller = new MLF_Rest_Controller();
|
||||
$controller->register_routes();
|
||||
}
|
||||
add_action('rest_api_init', 'mlf_register_rest_routes');
|
||||
|
||||
/**
|
||||
* Get the required capability for managing fonts.
|
||||
*
|
||||
* @return string The capability required to manage fonts.
|
||||
*/
|
||||
function mlf_get_capability() {
|
||||
/**
|
||||
* Filter the capability required to manage local fonts.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @param string $capability Default capability is 'edit_theme_options'.
|
||||
*/
|
||||
return apply_filters('mlf_manage_fonts_capability', 'edit_theme_options');
|
||||
}
|
||||
|
||||
/**
|
||||
* Register admin menu.
|
||||
*/
|
||||
function mlf_register_menu() {
|
||||
add_submenu_page(
|
||||
'themes.php',
|
||||
__('Maple Local Fonts', 'maple-local-fonts'),
|
||||
__('Local Fonts', 'maple-local-fonts'),
|
||||
mlf_get_capability(),
|
||||
'maple-local-fonts',
|
||||
'mlf_render_admin_page'
|
||||
);
|
||||
}
|
||||
add_action('admin_menu', 'mlf_register_menu');
|
||||
|
||||
/**
|
||||
* Render admin page (delegates to MLF_Admin_Page).
|
||||
*/
|
||||
function mlf_render_admin_page() {
|
||||
$admin_page = new MLF_Admin_Page();
|
||||
$admin_page->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue admin assets.
|
||||
*
|
||||
* @param string $hook The current admin page hook.
|
||||
*/
|
||||
function mlf_enqueue_admin_assets($hook) {
|
||||
if ($hook !== 'appearance_page_maple-local-fonts') {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_enqueue_style(
|
||||
'mlf-admin',
|
||||
MLF_PLUGIN_URL . 'assets/admin.css',
|
||||
[],
|
||||
MLF_VERSION
|
||||
);
|
||||
|
||||
wp_enqueue_script(
|
||||
'mlf-admin',
|
||||
MLF_PLUGIN_URL . 'assets/admin.js',
|
||||
['jquery'],
|
||||
MLF_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_localize_script('mlf-admin', 'mapleLocalFontsData', [
|
||||
'ajaxUrl' => admin_url('admin-ajax.php'),
|
||||
'downloadNonce' => wp_create_nonce('mlf_download_font'),
|
||||
'deleteNonce' => wp_create_nonce('mlf_delete_font'),
|
||||
'strings' => [
|
||||
'downloading' => __('Downloading...', 'maple-local-fonts'),
|
||||
'deleting' => __('Deleting...', 'maple-local-fonts'),
|
||||
'confirmDelete' => __('Are you sure you want to delete this font?', 'maple-local-fonts'),
|
||||
'error' => __('An error occurred. Please try again.', 'maple-local-fonts'),
|
||||
'selectWeight' => __('Please select at least one weight.', 'maple-local-fonts'),
|
||||
'selectStyle' => __('Please select at least one style.', 'maple-local-fonts'),
|
||||
'enterFontName' => __('Please enter a font name.', 'maple-local-fonts'),
|
||||
],
|
||||
]);
|
||||
}
|
||||
add_action('admin_enqueue_scripts', 'mlf_enqueue_admin_assets');
|
||||
61
native/wordpress/maple-fonts-wp/phpcs.xml.dist
Normal file
61
native/wordpress/maple-fonts-wp/phpcs.xml.dist
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<?xml version="1.0"?>
|
||||
<ruleset name="Maple Local Fonts">
|
||||
<description>PHP Coding Standards for Maple Local Fonts WordPress Plugin</description>
|
||||
|
||||
<!-- Scan these files -->
|
||||
<file>.</file>
|
||||
|
||||
<!-- Exclude these files/directories -->
|
||||
<exclude-pattern>/vendor/*</exclude-pattern>
|
||||
<exclude-pattern>/tests/*</exclude-pattern>
|
||||
<exclude-pattern>/bin/*</exclude-pattern>
|
||||
<exclude-pattern>/build/*</exclude-pattern>
|
||||
<exclude-pattern>/node_modules/*</exclude-pattern>
|
||||
<exclude-pattern>*.min.js</exclude-pattern>
|
||||
<exclude-pattern>*.min.css</exclude-pattern>
|
||||
|
||||
<!-- Show progress and sniff codes -->
|
||||
<arg value="ps"/>
|
||||
<arg name="colors"/>
|
||||
|
||||
<!-- Use WordPress Coding Standards -->
|
||||
<rule ref="WordPress">
|
||||
<!-- Allow short array syntax -->
|
||||
<exclude name="Generic.Arrays.DisallowShortArraySyntax"/>
|
||||
<!-- Allow file names that don't match class names (for index.php files) -->
|
||||
<exclude name="WordPress.Files.FileName.InvalidClassFileName"/>
|
||||
<exclude name="WordPress.Files.FileName.NotHyphenatedLowercase"/>
|
||||
</rule>
|
||||
|
||||
<!-- Use WordPress Extra Standards -->
|
||||
<rule ref="WordPress-Extra">
|
||||
<exclude name="WordPress.Files.FileName"/>
|
||||
</rule>
|
||||
|
||||
<!-- Check for PHP cross-version compatibility -->
|
||||
<config name="testVersion" value="7.4-"/>
|
||||
<rule ref="PHPCompatibilityWP"/>
|
||||
|
||||
<!-- WordPress-specific settings -->
|
||||
<config name="minimum_supported_wp_version" value="6.5"/>
|
||||
|
||||
<!-- Text domain for i18n -->
|
||||
<rule ref="WordPress.WP.I18n">
|
||||
<properties>
|
||||
<property name="text_domain" type="array">
|
||||
<element value="maple-local-fonts"/>
|
||||
</property>
|
||||
</properties>
|
||||
</rule>
|
||||
|
||||
<!-- Prefix all globals -->
|
||||
<rule ref="WordPress.NamingConventions.PrefixAllGlobals">
|
||||
<properties>
|
||||
<property name="prefixes" type="array">
|
||||
<element value="mlf"/>
|
||||
<element value="MLF"/>
|
||||
<element value="maple_local_fonts"/>
|
||||
</property>
|
||||
</properties>
|
||||
</rule>
|
||||
</ruleset>
|
||||
27
native/wordpress/maple-fonts-wp/phpunit.xml.dist
Normal file
27
native/wordpress/maple-fonts-wp/phpunit.xml.dist
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0"?>
|
||||
<phpunit
|
||||
bootstrap="tests/bootstrap.php"
|
||||
backupGlobals="false"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="Maple Local Fonts Test Suite">
|
||||
<directory suffix=".php">./tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<filter>
|
||||
<whitelist processUncoveredFilesFromWhitelist="true">
|
||||
<directory suffix=".php">./includes/</directory>
|
||||
<file>./maple-local-fonts.php</file>
|
||||
</whitelist>
|
||||
</filter>
|
||||
<php>
|
||||
<const name="WP_TESTS_DOMAIN" value="example.org"/>
|
||||
<const name="WP_TESTS_EMAIL" value="admin@example.org"/>
|
||||
<const name="WP_TESTS_TITLE" value="Test Blog"/>
|
||||
<const name="WP_PHP_BINARY" value="php"/>
|
||||
</php>
|
||||
</phpunit>
|
||||
155
native/wordpress/maple-fonts-wp/readme.txt
Normal file
155
native/wordpress/maple-fonts-wp/readme.txt
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
=== Maple Local Fonts ===
|
||||
Contributors: mapleopentech
|
||||
Tags: fonts, google fonts, local fonts, gdpr, typography, privacy
|
||||
Requires at least: 6.5
|
||||
Tested up to: 6.7
|
||||
Requires PHP: 7.4
|
||||
Stable tag: 1.0.0
|
||||
License: GPL-2.0-or-later
|
||||
License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
||||
|
||||
Import Google Fonts to local storage for GDPR-compliant, privacy-friendly typography. Fonts are served from your server, not Google.
|
||||
|
||||
== Description ==
|
||||
|
||||
**Maple Local Fonts** allows you to import Google Fonts to your WordPress site's local storage. Once imported, fonts are served directly from your server — Google is never contacted again.
|
||||
|
||||
= Why Local Fonts? =
|
||||
|
||||
* **GDPR Compliance**: Serving fonts from Google's servers can transfer visitor IP addresses to Google, which may violate GDPR. Local fonts eliminate this concern.
|
||||
* **Performance**: Local fonts can load faster since they're served from your own server without additional DNS lookups.
|
||||
* **Privacy**: Your visitors' data stays private — no third-party requests for fonts.
|
||||
* **Reliability**: Fonts are always available, even if Google's services are blocked or unavailable.
|
||||
|
||||
= Features =
|
||||
|
||||
* **Simple Import**: Enter a font name, select weights and styles, and click to import.
|
||||
* **WordPress Integration**: Fonts automatically appear in the Full Site Editor typography settings.
|
||||
* **Clean Removal**: Uninstalling the plugin removes all imported fonts and files.
|
||||
* **No Frontend Impact**: Zero JavaScript or CSS added to your frontend — only font files are served.
|
||||
|
||||
= How It Works =
|
||||
|
||||
1. Enter the Google Fonts name (e.g., "Open Sans")
|
||||
2. Select the weights (400, 700, etc.) and styles (normal, italic)
|
||||
3. Click "Download & Install"
|
||||
4. The plugin downloads the font files and registers them with WordPress
|
||||
5. Use the fonts in Appearance → Editor → Styles → Typography
|
||||
|
||||
= Requirements =
|
||||
|
||||
* WordPress 6.5 or higher (required for Font Library API)
|
||||
* PHP 7.4 or higher
|
||||
|
||||
== Installation ==
|
||||
|
||||
1. Upload the `maple-local-fonts` folder to `/wp-content/plugins/`
|
||||
2. Activate the plugin through the Plugins menu
|
||||
3. Go to Appearance → Local Fonts
|
||||
4. Import your desired fonts
|
||||
|
||||
== Frequently Asked Questions ==
|
||||
|
||||
= Does this plugin contact Google after importing a font? =
|
||||
|
||||
No. The plugin only contacts Google Fonts once, during the import process. After that, fonts are served entirely from your server.
|
||||
|
||||
= Where are the font files stored? =
|
||||
|
||||
Font files are stored in `wp-content/fonts/`, which is WordPress's default font directory.
|
||||
|
||||
= Can I use these fonts with any theme? =
|
||||
|
||||
**Block Themes (FSE):** Imported fonts automatically appear in Appearance → Editor → Styles → Typography.
|
||||
|
||||
**Classic Themes:** Fonts are registered but must be applied via custom CSS. After importing, add CSS like this to your theme or Customizer:
|
||||
|
||||
`body { font-family: "Open Sans", sans-serif; }`
|
||||
|
||||
The plugin will detect your theme type and show appropriate instructions.
|
||||
|
||||
= What happens to the fonts if I uninstall the plugin? =
|
||||
|
||||
When you delete the plugin, all imported fonts and their files are removed. This ensures a clean uninstall.
|
||||
|
||||
= Is this plugin GDPR compliant? =
|
||||
|
||||
Yes. After importing, fonts are served from your own server. No visitor data is sent to Google or any third party.
|
||||
|
||||
= What font formats are downloaded? =
|
||||
|
||||
The plugin downloads WOFF2 format, which is the most efficient and widely supported web font format.
|
||||
|
||||
= Can I import fonts that aren't on Google Fonts? =
|
||||
|
||||
No, this plugin only supports importing from Google Fonts. For other fonts, you would need to manually upload them.
|
||||
|
||||
= I'm using Wordfence/a firewall and imports are failing =
|
||||
|
||||
If font imports fail, your firewall may be blocking outbound requests. Whitelist these domains:
|
||||
|
||||
* `fonts.googleapis.com` (for font CSS)
|
||||
* `fonts.gstatic.com` (for font files)
|
||||
|
||||
**Wordfence:** Go to Wordfence → Firewall → Blocking and ensure these domains aren't blocked. If using Learning Mode, imports should work and the domains will be automatically learned.
|
||||
|
||||
**Other firewalls:** Add the above domains to your outbound request allowlist.
|
||||
|
||||
= The plugin says it requires WordPress 6.5 =
|
||||
|
||||
WordPress 6.5 introduced the Font Library API that this plugin uses. Earlier versions don't support local font registration. Please update WordPress to use this plugin.
|
||||
|
||||
== Compatibility ==
|
||||
|
||||
= Tested With =
|
||||
|
||||
* **WordPress:** 6.5, 6.6, 6.7
|
||||
* **WooCommerce:** 8.0+ (HPOS compatible)
|
||||
* **Wordfence:** 7.10+ (see FAQ for firewall settings)
|
||||
* **LearnDash:** 4.10+
|
||||
* **Popular Block Themes:** Twenty Twenty-Four, Twenty Twenty-Five
|
||||
|
||||
= Theme Compatibility =
|
||||
|
||||
* **Block Themes (FSE):** Full support via Site Editor typography settings
|
||||
* **Classic Themes:** Supported via custom CSS (instructions shown in admin)
|
||||
|
||||
== Privacy ==
|
||||
|
||||
= Data Collection =
|
||||
|
||||
This plugin does not collect any personal data from your website visitors.
|
||||
|
||||
= External Services =
|
||||
|
||||
This plugin connects to Google Fonts (fonts.googleapis.com and fonts.gstatic.com) **only during the font import process** in the WordPress admin area. This connection is:
|
||||
|
||||
* Initiated only by administrators
|
||||
* Used only to download font files
|
||||
* Not repeated after initial download
|
||||
|
||||
After import, no external connections are made. Fonts are served entirely from your server.
|
||||
|
||||
= Cookies =
|
||||
|
||||
This plugin does not set any cookies.
|
||||
|
||||
== Screenshots ==
|
||||
|
||||
1. The font import interface
|
||||
2. Installed fonts list with delete option
|
||||
3. Fonts appearing in the Full Site Editor typography settings
|
||||
|
||||
== Changelog ==
|
||||
|
||||
= 1.0.0 =
|
||||
* Initial release
|
||||
* Import Google Fonts to local storage
|
||||
* WordPress Font Library integration
|
||||
* Admin interface for font management
|
||||
* Clean uninstall functionality
|
||||
|
||||
== Upgrade Notice ==
|
||||
|
||||
= 1.0.0 =
|
||||
Initial release of Maple Local Fonts.
|
||||
40
native/wordpress/maple-fonts-wp/tests/bootstrap.php
Normal file
40
native/wordpress/maple-fonts-wp/tests/bootstrap.php
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
/**
|
||||
* PHPUnit bootstrap file for Maple Local Fonts.
|
||||
*
|
||||
* @package Maple_Local_Fonts
|
||||
*/
|
||||
|
||||
// Composer autoloader.
|
||||
$autoload = dirname(__DIR__) . '/vendor/autoload.php';
|
||||
if (file_exists($autoload)) {
|
||||
require_once $autoload;
|
||||
}
|
||||
|
||||
// Get the tests directory.
|
||||
$_tests_dir = getenv('WP_TESTS_DIR');
|
||||
|
||||
// Try to find the WP test suite.
|
||||
if (!$_tests_dir) {
|
||||
$_tests_dir = rtrim(sys_get_temp_dir(), '/\\') . '/wordpress-tests-lib';
|
||||
}
|
||||
|
||||
// Check if the test suite exists.
|
||||
if (!file_exists($_tests_dir . '/includes/functions.php')) {
|
||||
echo "Could not find $_tests_dir/includes/functions.php, have you run bin/install-wp-tests.sh?" . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Give access to tests_add_filter() function.
|
||||
require_once $_tests_dir . '/includes/functions.php';
|
||||
|
||||
/**
|
||||
* Manually load the plugin being tested.
|
||||
*/
|
||||
function _manually_load_plugin() {
|
||||
require dirname(__DIR__) . '/maple-local-fonts.php';
|
||||
}
|
||||
tests_add_filter('muplugins_loaded', '_manually_load_plugin');
|
||||
|
||||
// Start up the WP testing environment.
|
||||
require $_tests_dir . '/includes/bootstrap.php';
|
||||
2
native/wordpress/maple-fonts-wp/tests/index.php
Normal file
2
native/wordpress/maple-fonts-wp/tests/index.php
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<?php
|
||||
// Silence is golden.
|
||||
426
native/wordpress/maple-fonts-wp/tests/test-ajax-handler.php
Normal file
426
native/wordpress/maple-fonts-wp/tests/test-ajax-handler.php
Normal file
|
|
@ -0,0 +1,426 @@
|
|||
<?php
|
||||
/**
|
||||
* Tests for MLF_Ajax_Handler class.
|
||||
*
|
||||
* @package Maple_Local_Fonts
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class Test_MLF_Ajax_Handler
|
||||
*
|
||||
* @covers MLF_Ajax_Handler
|
||||
*/
|
||||
class Test_MLF_Ajax_Handler extends WP_Ajax_UnitTestCase {
|
||||
|
||||
/**
|
||||
* Admin user ID.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $admin_id;
|
||||
|
||||
/**
|
||||
* Subscriber user ID.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $subscriber_id;
|
||||
|
||||
/**
|
||||
* Set up test fixtures.
|
||||
*/
|
||||
public function set_up() {
|
||||
parent::set_up();
|
||||
|
||||
// Create users
|
||||
$this->admin_id = $this->factory->user->create(['role' => 'administrator']);
|
||||
$this->subscriber_id = $this->factory->user->create(['role' => 'subscriber']);
|
||||
|
||||
// Initialize the handler
|
||||
new MLF_Ajax_Handler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up after tests.
|
||||
*/
|
||||
public function tear_down() {
|
||||
// Clean up test fonts
|
||||
$fonts = get_posts([
|
||||
'post_type' => 'wp_font_family',
|
||||
'posts_per_page' => -1,
|
||||
'meta_key' => '_mlf_imported',
|
||||
'meta_value' => '1',
|
||||
'fields' => 'ids',
|
||||
]);
|
||||
|
||||
foreach ($fonts as $font_id) {
|
||||
wp_delete_post($font_id, true);
|
||||
}
|
||||
|
||||
wp_set_current_user(0);
|
||||
delete_transient('mlf_imported_fonts_list');
|
||||
|
||||
parent::tear_down();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test download without nonce.
|
||||
*/
|
||||
public function test_download_without_nonce() {
|
||||
wp_set_current_user($this->admin_id);
|
||||
|
||||
$_POST['font_name'] = 'Open Sans';
|
||||
$_POST['weights'] = [400];
|
||||
$_POST['styles'] = ['normal'];
|
||||
|
||||
$this->expectException('WPAjaxDieStopException');
|
||||
|
||||
try {
|
||||
$this->_handleAjax('mlf_download_font');
|
||||
} catch (WPAjaxDieStopException $e) {
|
||||
$response = json_decode($this->_last_response, true);
|
||||
$this->assertFalse($response['success']);
|
||||
$this->assertStringContainsString('Security', $response['data']['message']);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test download with invalid nonce.
|
||||
*/
|
||||
public function test_download_with_invalid_nonce() {
|
||||
wp_set_current_user($this->admin_id);
|
||||
|
||||
$_POST['nonce'] = 'invalid_nonce';
|
||||
$_POST['font_name'] = 'Open Sans';
|
||||
$_POST['weights'] = [400];
|
||||
$_POST['styles'] = ['normal'];
|
||||
|
||||
$this->expectException('WPAjaxDieStopException');
|
||||
|
||||
try {
|
||||
$this->_handleAjax('mlf_download_font');
|
||||
} catch (WPAjaxDieStopException $e) {
|
||||
$response = json_decode($this->_last_response, true);
|
||||
$this->assertFalse($response['success']);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test download without proper capability.
|
||||
*/
|
||||
public function test_download_without_capability() {
|
||||
wp_set_current_user($this->subscriber_id);
|
||||
|
||||
$_POST['nonce'] = wp_create_nonce('mlf_download_font');
|
||||
$_POST['font_name'] = 'Open Sans';
|
||||
$_POST['weights'] = [400];
|
||||
$_POST['styles'] = ['normal'];
|
||||
|
||||
$this->expectException('WPAjaxDieStopException');
|
||||
|
||||
try {
|
||||
$this->_handleAjax('mlf_download_font');
|
||||
} catch (WPAjaxDieStopException $e) {
|
||||
$response = json_decode($this->_last_response, true);
|
||||
$this->assertFalse($response['success']);
|
||||
$this->assertStringContainsString('Unauthorized', $response['data']['message']);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test download with empty font name.
|
||||
*/
|
||||
public function test_download_empty_font_name() {
|
||||
wp_set_current_user($this->admin_id);
|
||||
|
||||
$_POST['nonce'] = wp_create_nonce('mlf_download_font');
|
||||
$_POST['font_name'] = '';
|
||||
$_POST['weights'] = [400];
|
||||
$_POST['styles'] = ['normal'];
|
||||
|
||||
$this->expectException('WPAjaxDieStopException');
|
||||
|
||||
try {
|
||||
$this->_handleAjax('mlf_download_font');
|
||||
} catch (WPAjaxDieStopException $e) {
|
||||
$response = json_decode($this->_last_response, true);
|
||||
$this->assertFalse($response['success']);
|
||||
$this->assertStringContainsString('required', $response['data']['message']);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test download with invalid font name (XSS attempt).
|
||||
*/
|
||||
public function test_download_invalid_font_name_xss() {
|
||||
wp_set_current_user($this->admin_id);
|
||||
|
||||
$_POST['nonce'] = wp_create_nonce('mlf_download_font');
|
||||
$_POST['font_name'] = '<script>alert("xss")</script>';
|
||||
$_POST['weights'] = [400];
|
||||
$_POST['styles'] = ['normal'];
|
||||
|
||||
$this->expectException('WPAjaxDieStopException');
|
||||
|
||||
try {
|
||||
$this->_handleAjax('mlf_download_font');
|
||||
} catch (WPAjaxDieStopException $e) {
|
||||
$response = json_decode($this->_last_response, true);
|
||||
$this->assertFalse($response['success']);
|
||||
$this->assertStringContainsString('Invalid font name', $response['data']['message']);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test download with path traversal in font name.
|
||||
*/
|
||||
public function test_download_path_traversal() {
|
||||
wp_set_current_user($this->admin_id);
|
||||
|
||||
$_POST['nonce'] = wp_create_nonce('mlf_download_font');
|
||||
$_POST['font_name'] = '../../../etc/passwd';
|
||||
$_POST['weights'] = [400];
|
||||
$_POST['styles'] = ['normal'];
|
||||
|
||||
$this->expectException('WPAjaxDieStopException');
|
||||
|
||||
try {
|
||||
$this->_handleAjax('mlf_download_font');
|
||||
} catch (WPAjaxDieStopException $e) {
|
||||
$response = json_decode($this->_last_response, true);
|
||||
$this->assertFalse($response['success']);
|
||||
$this->assertStringContainsString('Invalid font name', $response['data']['message']);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test download with font name too long.
|
||||
*/
|
||||
public function test_download_font_name_too_long() {
|
||||
wp_set_current_user($this->admin_id);
|
||||
|
||||
$_POST['nonce'] = wp_create_nonce('mlf_download_font');
|
||||
$_POST['font_name'] = str_repeat('a', 101);
|
||||
$_POST['weights'] = [400];
|
||||
$_POST['styles'] = ['normal'];
|
||||
|
||||
$this->expectException('WPAjaxDieStopException');
|
||||
|
||||
try {
|
||||
$this->_handleAjax('mlf_download_font');
|
||||
} catch (WPAjaxDieStopException $e) {
|
||||
$response = json_decode($this->_last_response, true);
|
||||
$this->assertFalse($response['success']);
|
||||
$this->assertStringContainsString('too long', $response['data']['message']);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test download with no weights.
|
||||
*/
|
||||
public function test_download_no_weights() {
|
||||
wp_set_current_user($this->admin_id);
|
||||
|
||||
$_POST['nonce'] = wp_create_nonce('mlf_download_font');
|
||||
$_POST['font_name'] = 'Open Sans';
|
||||
$_POST['weights'] = [];
|
||||
$_POST['styles'] = ['normal'];
|
||||
|
||||
$this->expectException('WPAjaxDieStopException');
|
||||
|
||||
try {
|
||||
$this->_handleAjax('mlf_download_font');
|
||||
} catch (WPAjaxDieStopException $e) {
|
||||
$response = json_decode($this->_last_response, true);
|
||||
$this->assertFalse($response['success']);
|
||||
$this->assertStringContainsString('weight', $response['data']['message']);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test download with invalid weights.
|
||||
*/
|
||||
public function test_download_invalid_weights() {
|
||||
wp_set_current_user($this->admin_id);
|
||||
|
||||
$_POST['nonce'] = wp_create_nonce('mlf_download_font');
|
||||
$_POST['font_name'] = 'Open Sans';
|
||||
$_POST['weights'] = [999, 1000]; // Invalid weights
|
||||
$_POST['styles'] = ['normal'];
|
||||
|
||||
$this->expectException('WPAjaxDieStopException');
|
||||
|
||||
try {
|
||||
$this->_handleAjax('mlf_download_font');
|
||||
} catch (WPAjaxDieStopException $e) {
|
||||
$response = json_decode($this->_last_response, true);
|
||||
$this->assertFalse($response['success']);
|
||||
$this->assertStringContainsString('weight', $response['data']['message']);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test download with no styles.
|
||||
*/
|
||||
public function test_download_no_styles() {
|
||||
wp_set_current_user($this->admin_id);
|
||||
|
||||
$_POST['nonce'] = wp_create_nonce('mlf_download_font');
|
||||
$_POST['font_name'] = 'Open Sans';
|
||||
$_POST['weights'] = [400];
|
||||
$_POST['styles'] = [];
|
||||
|
||||
$this->expectException('WPAjaxDieStopException');
|
||||
|
||||
try {
|
||||
$this->_handleAjax('mlf_download_font');
|
||||
} catch (WPAjaxDieStopException $e) {
|
||||
$response = json_decode($this->_last_response, true);
|
||||
$this->assertFalse($response['success']);
|
||||
$this->assertStringContainsString('style', $response['data']['message']);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test delete without nonce.
|
||||
*/
|
||||
public function test_delete_without_nonce() {
|
||||
wp_set_current_user($this->admin_id);
|
||||
|
||||
$font_id = $this->create_test_font();
|
||||
$_POST['font_id'] = $font_id;
|
||||
|
||||
$this->expectException('WPAjaxDieStopException');
|
||||
|
||||
try {
|
||||
$this->_handleAjax('mlf_delete_font');
|
||||
} catch (WPAjaxDieStopException $e) {
|
||||
$response = json_decode($this->_last_response, true);
|
||||
$this->assertFalse($response['success']);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test delete without capability.
|
||||
*/
|
||||
public function test_delete_without_capability() {
|
||||
wp_set_current_user($this->subscriber_id);
|
||||
|
||||
$font_id = $this->create_test_font();
|
||||
$_POST['nonce'] = wp_create_nonce('mlf_delete_font');
|
||||
$_POST['font_id'] = $font_id;
|
||||
|
||||
$this->expectException('WPAjaxDieStopException');
|
||||
|
||||
try {
|
||||
$this->_handleAjax('mlf_delete_font');
|
||||
} catch (WPAjaxDieStopException $e) {
|
||||
$response = json_decode($this->_last_response, true);
|
||||
$this->assertFalse($response['success']);
|
||||
$this->assertStringContainsString('Unauthorized', $response['data']['message']);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test delete with invalid font ID.
|
||||
*/
|
||||
public function test_delete_invalid_font_id() {
|
||||
wp_set_current_user($this->admin_id);
|
||||
|
||||
$_POST['nonce'] = wp_create_nonce('mlf_delete_font');
|
||||
$_POST['font_id'] = 0;
|
||||
|
||||
$this->expectException('WPAjaxDieStopException');
|
||||
|
||||
try {
|
||||
$this->_handleAjax('mlf_delete_font');
|
||||
} catch (WPAjaxDieStopException $e) {
|
||||
$response = json_decode($this->_last_response, true);
|
||||
$this->assertFalse($response['success']);
|
||||
$this->assertStringContainsString('Invalid', $response['data']['message']);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test delete font not found.
|
||||
*/
|
||||
public function test_delete_font_not_found() {
|
||||
wp_set_current_user($this->admin_id);
|
||||
|
||||
$_POST['nonce'] = wp_create_nonce('mlf_delete_font');
|
||||
$_POST['font_id'] = 99999;
|
||||
|
||||
$this->expectException('WPAjaxDieStopException');
|
||||
|
||||
try {
|
||||
$this->_handleAjax('mlf_delete_font');
|
||||
} catch (WPAjaxDieStopException $e) {
|
||||
$response = json_decode($this->_last_response, true);
|
||||
$this->assertFalse($response['success']);
|
||||
$this->assertStringContainsString('not found', $response['data']['message']);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test delete font not imported by plugin.
|
||||
*/
|
||||
public function test_delete_font_not_ours() {
|
||||
wp_set_current_user($this->admin_id);
|
||||
|
||||
// Create font without our meta
|
||||
$font_id = wp_insert_post([
|
||||
'post_type' => 'wp_font_family',
|
||||
'post_title' => 'Theme Font',
|
||||
'post_status' => 'publish',
|
||||
]);
|
||||
|
||||
$_POST['nonce'] = wp_create_nonce('mlf_delete_font');
|
||||
$_POST['font_id'] = $font_id;
|
||||
|
||||
$this->expectException('WPAjaxDieStopException');
|
||||
|
||||
try {
|
||||
$this->_handleAjax('mlf_delete_font');
|
||||
} catch (WPAjaxDieStopException $e) {
|
||||
$response = json_decode($this->_last_response, true);
|
||||
$this->assertFalse($response['success']);
|
||||
$this->assertStringContainsString('not imported', $response['data']['message']);
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// Clean up
|
||||
wp_delete_post($font_id, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to create a test font.
|
||||
*
|
||||
* @return int Font ID.
|
||||
*/
|
||||
private function create_test_font() {
|
||||
$font_id = wp_insert_post([
|
||||
'post_type' => 'wp_font_family',
|
||||
'post_title' => 'Test Font',
|
||||
'post_name' => 'test-font',
|
||||
'post_status' => 'publish',
|
||||
]);
|
||||
|
||||
update_post_meta($font_id, '_mlf_imported', '1');
|
||||
|
||||
return $font_id;
|
||||
}
|
||||
}
|
||||
303
native/wordpress/maple-fonts-wp/tests/test-font-registry.php
Normal file
303
native/wordpress/maple-fonts-wp/tests/test-font-registry.php
Normal file
|
|
@ -0,0 +1,303 @@
|
|||
<?php
|
||||
/**
|
||||
* Tests for MLF_Font_Registry class.
|
||||
*
|
||||
* @package Maple_Local_Fonts
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class Test_MLF_Font_Registry
|
||||
*
|
||||
* @covers MLF_Font_Registry
|
||||
*/
|
||||
class Test_MLF_Font_Registry extends WP_UnitTestCase {
|
||||
|
||||
/**
|
||||
* Font registry instance.
|
||||
*
|
||||
* @var MLF_Font_Registry
|
||||
*/
|
||||
private $registry;
|
||||
|
||||
/**
|
||||
* Set up test fixtures.
|
||||
*/
|
||||
public function set_up() {
|
||||
parent::set_up();
|
||||
$this->registry = new MLF_Font_Registry();
|
||||
|
||||
// Clear font cache
|
||||
delete_transient('mlf_imported_fonts_list');
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up after tests.
|
||||
*/
|
||||
public function tear_down() {
|
||||
// Clean up any test fonts
|
||||
$fonts = get_posts([
|
||||
'post_type' => 'wp_font_family',
|
||||
'posts_per_page' => -1,
|
||||
'meta_key' => '_mlf_imported',
|
||||
'meta_value' => '1',
|
||||
'fields' => 'ids',
|
||||
]);
|
||||
|
||||
foreach ($fonts as $font_id) {
|
||||
wp_delete_post($font_id, true);
|
||||
}
|
||||
|
||||
delete_transient('mlf_imported_fonts_list');
|
||||
parent::tear_down();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that get_imported_fonts returns empty array when no fonts installed.
|
||||
*/
|
||||
public function test_get_imported_fonts_empty() {
|
||||
$fonts = $this->registry->get_imported_fonts();
|
||||
$this->assertIsArray($fonts);
|
||||
$this->assertEmpty($fonts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test font registration.
|
||||
*/
|
||||
public function test_register_font() {
|
||||
$font_name = 'Test Font';
|
||||
$font_slug = 'test-font';
|
||||
$files = [
|
||||
[
|
||||
'path' => '/tmp/test-font_normal_400.woff2',
|
||||
'weight' => '400',
|
||||
'style' => 'normal',
|
||||
],
|
||||
];
|
||||
|
||||
// Create a dummy file for the test
|
||||
file_put_contents($files[0]['path'], 'wOF2dummy');
|
||||
|
||||
$result = $this->registry->register_font($font_name, $font_slug, $files);
|
||||
|
||||
$this->assertIsInt($result);
|
||||
$this->assertGreaterThan(0, $result);
|
||||
|
||||
// Verify font was registered
|
||||
$font = get_post($result);
|
||||
$this->assertEquals('wp_font_family', $font->post_type);
|
||||
$this->assertEquals($font_name, $font->post_title);
|
||||
|
||||
// Clean up
|
||||
unlink($files[0]['path']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that duplicate fonts are rejected.
|
||||
*/
|
||||
public function test_register_font_duplicate() {
|
||||
$font_name = 'Duplicate Font';
|
||||
$font_slug = 'duplicate-font';
|
||||
$files = [
|
||||
[
|
||||
'path' => '/tmp/duplicate-font_normal_400.woff2',
|
||||
'weight' => '400',
|
||||
'style' => 'normal',
|
||||
],
|
||||
];
|
||||
|
||||
file_put_contents($files[0]['path'], 'wOF2dummy');
|
||||
|
||||
// Register first time
|
||||
$result1 = $this->registry->register_font($font_name, $font_slug, $files);
|
||||
$this->assertIsInt($result1);
|
||||
|
||||
// Try to register again
|
||||
$result2 = $this->registry->register_font($font_name, $font_slug, $files);
|
||||
$this->assertWPError($result2);
|
||||
$this->assertEquals('font_exists', $result2->get_error_code());
|
||||
|
||||
// Clean up
|
||||
unlink($files[0]['path']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_imported_fonts returns registered fonts.
|
||||
*/
|
||||
public function test_get_imported_fonts_returns_fonts() {
|
||||
$font_name = 'Listed Font';
|
||||
$font_slug = 'listed-font';
|
||||
$files = [
|
||||
[
|
||||
'path' => '/tmp/listed-font_normal_400.woff2',
|
||||
'weight' => '400',
|
||||
'style' => 'normal',
|
||||
],
|
||||
[
|
||||
'path' => '/tmp/listed-font_normal_700.woff2',
|
||||
'weight' => '700',
|
||||
'style' => 'normal',
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($files as $file) {
|
||||
file_put_contents($file['path'], 'wOF2dummy');
|
||||
}
|
||||
|
||||
$font_id = $this->registry->register_font($font_name, $font_slug, $files);
|
||||
|
||||
// Clear cache to test fresh retrieval
|
||||
delete_transient('mlf_imported_fonts_list');
|
||||
|
||||
$fonts = $this->registry->get_imported_fonts();
|
||||
|
||||
$this->assertCount(1, $fonts);
|
||||
$this->assertEquals($font_id, $fonts[0]['id']);
|
||||
$this->assertEquals($font_name, $fonts[0]['name']);
|
||||
$this->assertEquals($font_slug, $fonts[0]['slug']);
|
||||
$this->assertCount(2, $fonts[0]['variants']);
|
||||
|
||||
// Clean up
|
||||
foreach ($files as $file) {
|
||||
unlink($file['path']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test font deletion.
|
||||
*/
|
||||
public function test_delete_font() {
|
||||
$font_name = 'Delete Font';
|
||||
$font_slug = 'delete-font';
|
||||
$files = [
|
||||
[
|
||||
'path' => '/tmp/delete-font_normal_400.woff2',
|
||||
'weight' => '400',
|
||||
'style' => 'normal',
|
||||
],
|
||||
];
|
||||
|
||||
file_put_contents($files[0]['path'], 'wOF2dummy');
|
||||
|
||||
$font_id = $this->registry->register_font($font_name, $font_slug, $files);
|
||||
|
||||
// Delete the font
|
||||
$result = $this->registry->delete_font($font_id);
|
||||
$this->assertTrue($result);
|
||||
|
||||
// Verify font is gone
|
||||
$font = get_post($font_id);
|
||||
$this->assertNull($font);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test delete_font rejects non-existent fonts.
|
||||
*/
|
||||
public function test_delete_font_not_found() {
|
||||
$result = $this->registry->delete_font(99999);
|
||||
$this->assertWPError($result);
|
||||
$this->assertEquals('not_found', $result->get_error_code());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test delete_font rejects fonts not imported by plugin.
|
||||
*/
|
||||
public function test_delete_font_not_ours() {
|
||||
// Create a font family post without our meta
|
||||
$font_id = wp_insert_post([
|
||||
'post_type' => 'wp_font_family',
|
||||
'post_title' => 'Theme Font',
|
||||
'post_status' => 'publish',
|
||||
]);
|
||||
|
||||
$result = $this->registry->delete_font($font_id);
|
||||
$this->assertWPError($result);
|
||||
$this->assertEquals('not_ours', $result->get_error_code());
|
||||
|
||||
// Clean up
|
||||
wp_delete_post($font_id, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that cache is cleared on register.
|
||||
*/
|
||||
public function test_cache_cleared_on_register() {
|
||||
// Set a dummy cache
|
||||
set_transient('mlf_imported_fonts_list', ['cached' => true], 300);
|
||||
|
||||
$font_name = 'Cache Test Font';
|
||||
$font_slug = 'cache-test-font';
|
||||
$files = [
|
||||
[
|
||||
'path' => '/tmp/cache-test-font_normal_400.woff2',
|
||||
'weight' => '400',
|
||||
'style' => 'normal',
|
||||
],
|
||||
];
|
||||
|
||||
file_put_contents($files[0]['path'], 'wOF2dummy');
|
||||
|
||||
$this->registry->register_font($font_name, $font_slug, $files);
|
||||
|
||||
// Cache should be cleared
|
||||
$cached = get_transient('mlf_imported_fonts_list');
|
||||
$this->assertFalse($cached);
|
||||
|
||||
// Clean up
|
||||
unlink($files[0]['path']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that cache is cleared on delete.
|
||||
*/
|
||||
public function test_cache_cleared_on_delete() {
|
||||
$font_name = 'Cache Delete Font';
|
||||
$font_slug = 'cache-delete-font';
|
||||
$files = [
|
||||
[
|
||||
'path' => '/tmp/cache-delete-font_normal_400.woff2',
|
||||
'weight' => '400',
|
||||
'style' => 'normal',
|
||||
],
|
||||
];
|
||||
|
||||
file_put_contents($files[0]['path'], 'wOF2dummy');
|
||||
|
||||
$font_id = $this->registry->register_font($font_name, $font_slug, $files);
|
||||
|
||||
// Set a dummy cache
|
||||
set_transient('mlf_imported_fonts_list', ['cached' => true], 300);
|
||||
|
||||
$this->registry->delete_font($font_id);
|
||||
|
||||
// Cache should be cleared
|
||||
$cached = get_transient('mlf_imported_fonts_list');
|
||||
$this->assertFalse($cached);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test font_exists method.
|
||||
*/
|
||||
public function test_font_exists() {
|
||||
$font_name = 'Exists Font';
|
||||
$font_slug = 'exists-font';
|
||||
$files = [
|
||||
[
|
||||
'path' => '/tmp/exists-font_normal_400.woff2',
|
||||
'weight' => '400',
|
||||
'style' => 'normal',
|
||||
],
|
||||
];
|
||||
|
||||
file_put_contents($files[0]['path'], 'wOF2dummy');
|
||||
|
||||
// Before registration
|
||||
$this->assertFalse($this->registry->font_exists($font_slug));
|
||||
|
||||
// After registration
|
||||
$this->registry->register_font($font_name, $font_slug, $files);
|
||||
$this->assertTrue($this->registry->font_exists($font_slug));
|
||||
|
||||
// Clean up
|
||||
unlink($files[0]['path']);
|
||||
}
|
||||
}
|
||||
177
native/wordpress/maple-fonts-wp/tests/test-rate-limiter.php
Normal file
177
native/wordpress/maple-fonts-wp/tests/test-rate-limiter.php
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
<?php
|
||||
/**
|
||||
* Tests for MLF_Rate_Limiter class.
|
||||
*
|
||||
* @package Maple_Local_Fonts
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class Test_MLF_Rate_Limiter
|
||||
*
|
||||
* @covers MLF_Rate_Limiter
|
||||
*/
|
||||
class Test_MLF_Rate_Limiter extends WP_UnitTestCase {
|
||||
|
||||
/**
|
||||
* Rate limiter instance.
|
||||
*
|
||||
* @var MLF_Rate_Limiter
|
||||
*/
|
||||
private $rate_limiter;
|
||||
|
||||
/**
|
||||
* Set up test fixtures.
|
||||
*/
|
||||
public function set_up() {
|
||||
parent::set_up();
|
||||
$this->rate_limiter = new MLF_Rate_Limiter(3, 60); // 3 requests per 60 seconds
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up after tests.
|
||||
*/
|
||||
public function tear_down() {
|
||||
// Clear any transients
|
||||
$this->rate_limiter->clear('test_action');
|
||||
parent::tear_down();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that first request is not limited.
|
||||
*/
|
||||
public function test_first_request_not_limited() {
|
||||
$this->assertFalse($this->rate_limiter->is_limited('test_action'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that remaining count starts at limit.
|
||||
*/
|
||||
public function test_remaining_starts_at_limit() {
|
||||
$this->assertEquals(3, $this->rate_limiter->get_remaining('test_action'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that check_and_record allows requests within limit.
|
||||
*/
|
||||
public function test_check_and_record_allows_within_limit() {
|
||||
$this->assertTrue($this->rate_limiter->check_and_record('test_action'));
|
||||
$this->assertTrue($this->rate_limiter->check_and_record('test_action'));
|
||||
$this->assertTrue($this->rate_limiter->check_and_record('test_action'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that check_and_record blocks after limit exceeded.
|
||||
*/
|
||||
public function test_check_and_record_blocks_after_limit() {
|
||||
// Use up all allowed requests
|
||||
$this->rate_limiter->check_and_record('test_action');
|
||||
$this->rate_limiter->check_and_record('test_action');
|
||||
$this->rate_limiter->check_and_record('test_action');
|
||||
|
||||
// Next request should be blocked
|
||||
$this->assertFalse($this->rate_limiter->check_and_record('test_action'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that remaining count decreases correctly.
|
||||
*/
|
||||
public function test_remaining_decreases() {
|
||||
$this->rate_limiter->record_request('test_action');
|
||||
$this->assertEquals(2, $this->rate_limiter->get_remaining('test_action'));
|
||||
|
||||
$this->rate_limiter->record_request('test_action');
|
||||
$this->assertEquals(1, $this->rate_limiter->get_remaining('test_action'));
|
||||
|
||||
$this->rate_limiter->record_request('test_action');
|
||||
$this->assertEquals(0, $this->rate_limiter->get_remaining('test_action'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that is_limited returns true after limit exceeded.
|
||||
*/
|
||||
public function test_is_limited_after_exceeding() {
|
||||
$this->rate_limiter->record_request('test_action');
|
||||
$this->rate_limiter->record_request('test_action');
|
||||
$this->rate_limiter->record_request('test_action');
|
||||
|
||||
$this->assertTrue($this->rate_limiter->is_limited('test_action'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that clear resets the rate limit.
|
||||
*/
|
||||
public function test_clear_resets_limit() {
|
||||
// Use up all requests
|
||||
$this->rate_limiter->record_request('test_action');
|
||||
$this->rate_limiter->record_request('test_action');
|
||||
$this->rate_limiter->record_request('test_action');
|
||||
|
||||
$this->assertTrue($this->rate_limiter->is_limited('test_action'));
|
||||
|
||||
// Clear and verify reset
|
||||
$this->rate_limiter->clear('test_action');
|
||||
$this->assertFalse($this->rate_limiter->is_limited('test_action'));
|
||||
$this->assertEquals(3, $this->rate_limiter->get_remaining('test_action'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that different actions are tracked separately.
|
||||
*/
|
||||
public function test_different_actions_tracked_separately() {
|
||||
// Use up all requests for action1
|
||||
$this->rate_limiter->record_request('action1');
|
||||
$this->rate_limiter->record_request('action1');
|
||||
$this->rate_limiter->record_request('action1');
|
||||
|
||||
// action1 should be limited
|
||||
$this->assertTrue($this->rate_limiter->is_limited('action1'));
|
||||
|
||||
// action2 should not be limited
|
||||
$this->assertFalse($this->rate_limiter->is_limited('action2'));
|
||||
$this->assertEquals(3, $this->rate_limiter->get_remaining('action2'));
|
||||
|
||||
// Clean up
|
||||
$this->rate_limiter->clear('action1');
|
||||
$this->rate_limiter->clear('action2');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that logged in user uses user ID for tracking.
|
||||
*/
|
||||
public function test_logged_in_user_tracking() {
|
||||
// Create and log in a user
|
||||
$user_id = $this->factory->user->create(['role' => 'administrator']);
|
||||
wp_set_current_user($user_id);
|
||||
|
||||
// Use up requests
|
||||
$this->rate_limiter->record_request('test_action');
|
||||
$this->rate_limiter->record_request('test_action');
|
||||
$this->rate_limiter->record_request('test_action');
|
||||
|
||||
$this->assertTrue($this->rate_limiter->is_limited('test_action'));
|
||||
|
||||
// Clean up
|
||||
wp_set_current_user(0);
|
||||
$this->rate_limiter->clear('test_action');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test constructor with custom limits.
|
||||
*/
|
||||
public function test_custom_limits() {
|
||||
$custom_limiter = new MLF_Rate_Limiter(5, 120);
|
||||
|
||||
$this->assertEquals(5, $custom_limiter->get_remaining('custom_action'));
|
||||
|
||||
// Use 5 requests
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
$this->assertTrue($custom_limiter->check_and_record('custom_action'));
|
||||
}
|
||||
|
||||
// 6th should be blocked
|
||||
$this->assertFalse($custom_limiter->check_and_record('custom_action'));
|
||||
|
||||
// Clean up
|
||||
$custom_limiter->clear('custom_action');
|
||||
}
|
||||
}
|
||||
387
native/wordpress/maple-fonts-wp/tests/test-rest-controller.php
Normal file
387
native/wordpress/maple-fonts-wp/tests/test-rest-controller.php
Normal file
|
|
@ -0,0 +1,387 @@
|
|||
<?php
|
||||
/**
|
||||
* Tests for MLF_Rest_Controller class.
|
||||
*
|
||||
* @package Maple_Local_Fonts
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class Test_MLF_Rest_Controller
|
||||
*
|
||||
* @covers MLF_Rest_Controller
|
||||
*/
|
||||
class Test_MLF_Rest_Controller extends WP_Test_REST_Controller_Testcase {
|
||||
|
||||
/**
|
||||
* REST controller instance.
|
||||
*
|
||||
* @var MLF_Rest_Controller
|
||||
*/
|
||||
private $controller;
|
||||
|
||||
/**
|
||||
* Admin user ID.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $admin_id;
|
||||
|
||||
/**
|
||||
* Subscriber user ID.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $subscriber_id;
|
||||
|
||||
/**
|
||||
* Set up test fixtures.
|
||||
*/
|
||||
public function set_up() {
|
||||
parent::set_up();
|
||||
|
||||
// Register REST routes
|
||||
$this->controller = new MLF_Rest_Controller();
|
||||
$this->controller->register_routes();
|
||||
|
||||
// Create users
|
||||
$this->admin_id = $this->factory->user->create(['role' => 'administrator']);
|
||||
$this->subscriber_id = $this->factory->user->create(['role' => 'subscriber']);
|
||||
|
||||
// Clear any existing fonts and cache
|
||||
delete_transient('mlf_imported_fonts_list');
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up after tests.
|
||||
*/
|
||||
public function tear_down() {
|
||||
// Clean up test fonts
|
||||
$fonts = get_posts([
|
||||
'post_type' => 'wp_font_family',
|
||||
'posts_per_page' => -1,
|
||||
'meta_key' => '_mlf_imported',
|
||||
'meta_value' => '1',
|
||||
'fields' => 'ids',
|
||||
]);
|
||||
|
||||
foreach ($fonts as $font_id) {
|
||||
wp_delete_post($font_id, true);
|
||||
}
|
||||
|
||||
wp_set_current_user(0);
|
||||
delete_transient('mlf_imported_fonts_list');
|
||||
|
||||
parent::tear_down();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test route registration.
|
||||
*/
|
||||
public function test_register_routes() {
|
||||
$routes = rest_get_server()->get_routes();
|
||||
|
||||
$this->assertArrayHasKey('/mlf/v1/fonts', $routes);
|
||||
$this->assertArrayHasKey('/mlf/v1/fonts/(?P<id>[\d]+)', $routes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_items without authentication.
|
||||
*/
|
||||
public function test_get_items_unauthenticated() {
|
||||
$request = new WP_REST_Request('GET', '/mlf/v1/fonts');
|
||||
$response = rest_get_server()->dispatch($request);
|
||||
|
||||
$this->assertEquals(401, $response->get_status());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_items with subscriber (insufficient permissions).
|
||||
*/
|
||||
public function test_get_items_insufficient_permissions() {
|
||||
wp_set_current_user($this->subscriber_id);
|
||||
|
||||
$request = new WP_REST_Request('GET', '/mlf/v1/fonts');
|
||||
$response = rest_get_server()->dispatch($request);
|
||||
|
||||
$this->assertEquals(403, $response->get_status());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_items with admin.
|
||||
*/
|
||||
public function test_get_items_as_admin() {
|
||||
wp_set_current_user($this->admin_id);
|
||||
|
||||
$request = new WP_REST_Request('GET', '/mlf/v1/fonts');
|
||||
$response = rest_get_server()->dispatch($request);
|
||||
|
||||
$this->assertEquals(200, $response->get_status());
|
||||
$this->assertIsArray($response->get_data());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_items returns installed fonts.
|
||||
*/
|
||||
public function test_get_items_returns_fonts() {
|
||||
wp_set_current_user($this->admin_id);
|
||||
|
||||
// Create a test font
|
||||
$font_id = $this->create_test_font('Test API Font', 'test-api-font');
|
||||
|
||||
$request = new WP_REST_Request('GET', '/mlf/v1/fonts');
|
||||
$response = rest_get_server()->dispatch($request);
|
||||
|
||||
$this->assertEquals(200, $response->get_status());
|
||||
$data = $response->get_data();
|
||||
$this->assertCount(1, $data);
|
||||
$this->assertEquals($font_id, $data[0]['id']);
|
||||
$this->assertEquals('Test API Font', $data[0]['name']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_item.
|
||||
*/
|
||||
public function test_get_item() {
|
||||
wp_set_current_user($this->admin_id);
|
||||
|
||||
$font_id = $this->create_test_font('Single Font', 'single-font');
|
||||
|
||||
$request = new WP_REST_Request('GET', '/mlf/v1/fonts/' . $font_id);
|
||||
$response = rest_get_server()->dispatch($request);
|
||||
|
||||
$this->assertEquals(200, $response->get_status());
|
||||
$data = $response->get_data();
|
||||
$this->assertEquals($font_id, $data['id']);
|
||||
$this->assertEquals('Single Font', $data['name']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_item not found.
|
||||
*/
|
||||
public function test_get_item_not_found() {
|
||||
wp_set_current_user($this->admin_id);
|
||||
|
||||
$request = new WP_REST_Request('GET', '/mlf/v1/fonts/99999');
|
||||
$response = rest_get_server()->dispatch($request);
|
||||
|
||||
$this->assertEquals(404, $response->get_status());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test create_item without authentication.
|
||||
*/
|
||||
public function test_create_item_unauthenticated() {
|
||||
$request = new WP_REST_Request('POST', '/mlf/v1/fonts');
|
||||
$request->set_param('font_name', 'Open Sans');
|
||||
$request->set_param('weights', [400, 700]);
|
||||
$request->set_param('styles', ['normal']);
|
||||
|
||||
$response = rest_get_server()->dispatch($request);
|
||||
|
||||
$this->assertEquals(401, $response->get_status());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test create_item with subscriber.
|
||||
*/
|
||||
public function test_create_item_insufficient_permissions() {
|
||||
wp_set_current_user($this->subscriber_id);
|
||||
|
||||
$request = new WP_REST_Request('POST', '/mlf/v1/fonts');
|
||||
$request->set_param('font_name', 'Open Sans');
|
||||
$request->set_param('weights', [400, 700]);
|
||||
$request->set_param('styles', ['normal']);
|
||||
|
||||
$response = rest_get_server()->dispatch($request);
|
||||
|
||||
$this->assertEquals(403, $response->get_status());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test create_item with invalid font name.
|
||||
*/
|
||||
public function test_create_item_invalid_font_name() {
|
||||
wp_set_current_user($this->admin_id);
|
||||
|
||||
$request = new WP_REST_Request('POST', '/mlf/v1/fonts');
|
||||
$request->set_param('font_name', '<script>alert("xss")</script>');
|
||||
$request->set_param('weights', [400]);
|
||||
$request->set_param('styles', ['normal']);
|
||||
|
||||
$response = rest_get_server()->dispatch($request);
|
||||
|
||||
$this->assertEquals(400, $response->get_status());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test create_item with no weights.
|
||||
*/
|
||||
public function test_create_item_no_weights() {
|
||||
wp_set_current_user($this->admin_id);
|
||||
|
||||
$request = new WP_REST_Request('POST', '/mlf/v1/fonts');
|
||||
$request->set_param('font_name', 'Test Font');
|
||||
$request->set_param('weights', []);
|
||||
$request->set_param('styles', ['normal']);
|
||||
|
||||
$response = rest_get_server()->dispatch($request);
|
||||
|
||||
$this->assertEquals(400, $response->get_status());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test create_item with no styles.
|
||||
*/
|
||||
public function test_create_item_no_styles() {
|
||||
wp_set_current_user($this->admin_id);
|
||||
|
||||
$request = new WP_REST_Request('POST', '/mlf/v1/fonts');
|
||||
$request->set_param('font_name', 'Test Font');
|
||||
$request->set_param('weights', [400]);
|
||||
$request->set_param('styles', []);
|
||||
|
||||
$response = rest_get_server()->dispatch($request);
|
||||
|
||||
$this->assertEquals(400, $response->get_status());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test delete_item without authentication.
|
||||
*/
|
||||
public function test_delete_item_unauthenticated() {
|
||||
$font_id = $this->create_test_font('Delete Font', 'delete-font');
|
||||
|
||||
$request = new WP_REST_Request('DELETE', '/mlf/v1/fonts/' . $font_id);
|
||||
$response = rest_get_server()->dispatch($request);
|
||||
|
||||
$this->assertEquals(401, $response->get_status());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test delete_item with subscriber.
|
||||
*/
|
||||
public function test_delete_item_insufficient_permissions() {
|
||||
$font_id = $this->create_test_font('Delete Font 2', 'delete-font-2');
|
||||
|
||||
wp_set_current_user($this->subscriber_id);
|
||||
|
||||
$request = new WP_REST_Request('DELETE', '/mlf/v1/fonts/' . $font_id);
|
||||
$response = rest_get_server()->dispatch($request);
|
||||
|
||||
$this->assertEquals(403, $response->get_status());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test delete_item as admin.
|
||||
*/
|
||||
public function test_delete_item_as_admin() {
|
||||
$font_id = $this->create_test_font('Delete Font 3', 'delete-font-3');
|
||||
|
||||
wp_set_current_user($this->admin_id);
|
||||
|
||||
$request = new WP_REST_Request('DELETE', '/mlf/v1/fonts/' . $font_id);
|
||||
$response = rest_get_server()->dispatch($request);
|
||||
|
||||
$this->assertEquals(200, $response->get_status());
|
||||
$data = $response->get_data();
|
||||
$this->assertTrue($data['deleted']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test delete_item not found.
|
||||
*/
|
||||
public function test_delete_item_not_found() {
|
||||
wp_set_current_user($this->admin_id);
|
||||
|
||||
$request = new WP_REST_Request('DELETE', '/mlf/v1/fonts/99999');
|
||||
$response = rest_get_server()->dispatch($request);
|
||||
|
||||
$this->assertEquals(404, $response->get_status());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test delete_item for font not imported by plugin.
|
||||
*/
|
||||
public function test_delete_item_not_ours() {
|
||||
wp_set_current_user($this->admin_id);
|
||||
|
||||
// Create a font without our meta
|
||||
$font_id = wp_insert_post([
|
||||
'post_type' => 'wp_font_family',
|
||||
'post_title' => 'Theme Font',
|
||||
'post_status' => 'publish',
|
||||
]);
|
||||
|
||||
$request = new WP_REST_Request('DELETE', '/mlf/v1/fonts/' . $font_id);
|
||||
$response = rest_get_server()->dispatch($request);
|
||||
|
||||
$this->assertEquals(403, $response->get_status());
|
||||
|
||||
// Clean up
|
||||
wp_delete_post($font_id, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test item schema.
|
||||
*/
|
||||
public function test_get_item_schema() {
|
||||
$request = new WP_REST_Request('OPTIONS', '/mlf/v1/fonts');
|
||||
$response = rest_get_server()->dispatch($request);
|
||||
$data = $response->get_data();
|
||||
|
||||
$this->assertArrayHasKey('schema', $data);
|
||||
$this->assertEquals('font', $data['schema']['title']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test context parameter.
|
||||
*/
|
||||
public function test_context_param() {
|
||||
// This test is inherited from WP_Test_REST_Controller_Testcase
|
||||
// We just need to implement it to satisfy the abstract requirement
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to create a test font.
|
||||
*
|
||||
* @param string $name Font name.
|
||||
* @param string $slug Font slug.
|
||||
* @return int Font ID.
|
||||
*/
|
||||
private function create_test_font($name, $slug) {
|
||||
// Create font family post
|
||||
$font_id = wp_insert_post([
|
||||
'post_type' => 'wp_font_family',
|
||||
'post_title' => $name,
|
||||
'post_name' => $slug,
|
||||
'post_status' => 'publish',
|
||||
'post_content' => wp_json_encode([
|
||||
'fontFamily' => $name,
|
||||
'slug' => $slug,
|
||||
]),
|
||||
]);
|
||||
|
||||
// Add our meta marker
|
||||
update_post_meta($font_id, '_mlf_imported', '1');
|
||||
|
||||
// Create a font face
|
||||
$face_id = wp_insert_post([
|
||||
'post_type' => 'wp_font_face',
|
||||
'post_title' => $name . ' 400 normal',
|
||||
'post_status' => 'publish',
|
||||
'post_parent' => $font_id,
|
||||
'post_content' => wp_json_encode([
|
||||
'fontFamily' => $name,
|
||||
'fontWeight' => '400',
|
||||
'fontStyle' => 'normal',
|
||||
'src' => ['file:./fonts/' . $slug . '_normal_400.woff2'],
|
||||
]),
|
||||
]);
|
||||
|
||||
// Clear cache
|
||||
delete_transient('mlf_imported_fonts_list');
|
||||
|
||||
return $font_id;
|
||||
}
|
||||
}
|
||||
123
native/wordpress/maple-fonts-wp/uninstall.php
Normal file
123
native/wordpress/maple-fonts-wp/uninstall.php
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
<?php
|
||||
/**
|
||||
* Uninstall script for Maple Local Fonts.
|
||||
*
|
||||
* This file is executed when the plugin is deleted from WordPress.
|
||||
* It removes all fonts imported by this plugin and cleans up any data.
|
||||
*
|
||||
* @package Maple_Local_Fonts
|
||||
*/
|
||||
|
||||
// If uninstall not called from WordPress, exit.
|
||||
if (!defined('WP_UNINSTALL_PLUGIN')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up all plugin data on uninstall.
|
||||
*
|
||||
* Uses batched processing to prevent memory exhaustion on sites with many fonts.
|
||||
*/
|
||||
function mlf_uninstall() {
|
||||
$font_dir = wp_get_font_dir();
|
||||
$batch_size = 50; // Process 50 fonts at a time to prevent memory issues
|
||||
$processed = 0;
|
||||
$max_iterations = 100; // Safety limit: max 5000 fonts (100 × 50)
|
||||
|
||||
// Process fonts in batches
|
||||
for ($i = 0; $i < $max_iterations; $i++) {
|
||||
$fonts = get_posts([
|
||||
'post_type' => 'wp_font_family',
|
||||
'posts_per_page' => $batch_size,
|
||||
'post_status' => 'any',
|
||||
'meta_key' => '_mlf_imported',
|
||||
'meta_value' => '1',
|
||||
'fields' => 'ids', // Only get IDs for memory efficiency
|
||||
]);
|
||||
|
||||
// No more fonts to process
|
||||
if (empty($fonts)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Get all font faces for this batch in a single query
|
||||
$all_faces = get_posts([
|
||||
'post_type' => 'wp_font_face',
|
||||
'posts_per_page' => $batch_size * 20, // Max ~20 faces per font
|
||||
'post_status' => 'any',
|
||||
'post_parent__in' => $fonts,
|
||||
]);
|
||||
|
||||
// Delete font face files and posts
|
||||
foreach ($all_faces as $face) {
|
||||
$settings = json_decode($face->post_content, true);
|
||||
|
||||
if (isset($settings['src'])) {
|
||||
// Convert file:. URL to path
|
||||
$src = $settings['src'];
|
||||
$src = str_replace('file:./', '', $src);
|
||||
$file_path = trailingslashit($font_dir['path']) . basename($src);
|
||||
|
||||
// Validate path and extension before deletion
|
||||
if (mlf_uninstall_validate_font_path($file_path, $font_dir)
|
||||
&& pathinfo($file_path, PATHINFO_EXTENSION) === 'woff2'
|
||||
&& file_exists($file_path)) {
|
||||
wp_delete_file($file_path);
|
||||
}
|
||||
}
|
||||
|
||||
wp_delete_post($face->ID, true);
|
||||
}
|
||||
|
||||
// Delete family posts
|
||||
foreach ($fonts as $font_id) {
|
||||
wp_delete_post($font_id, true);
|
||||
}
|
||||
|
||||
$processed += count($fonts);
|
||||
|
||||
// Free memory
|
||||
unset($fonts, $all_faces);
|
||||
|
||||
// If we got fewer than batch_size, we're done
|
||||
if (count($fonts) < $batch_size) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear caches
|
||||
delete_transient('wp_font_library_fonts');
|
||||
delete_transient('mlf_imported_fonts_list');
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that a path is within the WordPress fonts directory.
|
||||
*
|
||||
* @param string $path Full path to validate.
|
||||
* @param array $font_dir Font directory info.
|
||||
* @return bool True if path is safe, false otherwise.
|
||||
*/
|
||||
function mlf_uninstall_validate_font_path($path, $font_dir) {
|
||||
$fonts_path = wp_normalize_path(trailingslashit($font_dir['path']));
|
||||
|
||||
// Resolve to real path (handles ../ etc)
|
||||
$real_path = realpath($path);
|
||||
|
||||
// If realpath fails, file doesn't exist yet - validate the directory
|
||||
if ($real_path === false) {
|
||||
$dir = dirname($path);
|
||||
$real_dir = realpath($dir);
|
||||
if ($real_dir === false) {
|
||||
return false;
|
||||
}
|
||||
$real_path = wp_normalize_path($real_dir . '/' . basename($path));
|
||||
} else {
|
||||
$real_path = wp_normalize_path($real_path);
|
||||
}
|
||||
|
||||
// Must be within fonts directory
|
||||
return strpos($real_path, $fonts_path) === 0;
|
||||
}
|
||||
|
||||
// Run uninstall
|
||||
mlf_uninstall();
|
||||
Loading…
Add table
Add a link
Reference in a new issue